module Lib (parLife, life, driver, testDriver, testLifeFunc, testNeighbors, originSquare, neighbors, Cell) where

import Control.Arrow
import Control.Parallel.Strategies
import Data.List
import qualified Data.Set as Set

-- datatypes
type Cell = (Int, Int)
type Board = [Cell]

testDriver::([Cell] -> [Cell]) -> Int -> [Cell] -> [Cell]
testDriver lf num startState = last $ take num $ iterate lf startState
-- driver
driver::([Cell] -> [Cell]) -> Int -> [Cell]
driver lf num = last $ take num $ iterate lf initState
  where
      initState = [(1, 2), (2, 2), (2, 1), (2, 3), (3, 3)]
    -- initState = [(2, 0), (7, 0), (0, 1), (1, 1), (3, 1), (4, 1), (5, 1), (6, 1), (8, 1), (9, 1), (2, 2), (7, 2)]

-- non parallel implementation for benchmarking
life :: [Cell] -> [Cell]
life population = map fst . filter rules . tally $ concatMap neighbors population
  where
    popSet = Set.fromList population
    rules (cl, n) = (cl `Set.member` popSet && n == 3) || n == 4
    tally = map (\x -> (head x, length x)) . (group . sort)

-- See https://lbarasti.com/post/game_of_life/
-- count occurences of each (Int, Int) live cell in populations
-- cells appearing in 3 'neighborhoods' and also present in population will continue to live
-- cells appearing in 4 'neighbhoods' and not present in population will become 'alive'
-- don't count origin cell as that will inflate our numbers
-- use Set to reduce lookup time per Professor Edwards
parLife :: [Cell] -> [Cell]
parLife population = parMap rseq fst . filter rules . tally . concat $ parMap rseq neighbors population
  where
    popSet = Set.fromList population
    rules (cl, n) = (cl `Set.member` popSet && n == 3) || n == 4
    tally = map (\x -> (head x, length x)) . (group . sort)

originSquare::[Cell]
originSquare = [
  (-1, 1), (0, 1), (1, 1),
  (-1, 0), (0, 0), (1, 0),
  (-1,-1), (0,-1), (1,-1)
  ]

-- Get all neighbor coords of given cell
--   [(-1, 1), (0, 1), (1, 1),
--    (-1, 0), (0, 0), (1, 0),
--    (-1,-1), (0,-1), (1,-1)
--]
neighbors :: Cell -> [Cell]
neighbors (x, y) = [(x + dx, y + dy) | (dx, dy) <- originSquare]


testNeighbors::Cell -> [Cell] -> Bool
testNeighbors cl expected = (sort (neighbors cl)) == (sort expected)

testLifeFunc::[Cell] -> ([Cell] -> [Cell]) -> Int -> [Cell] -> Bool
testLifeFunc startState lifeFunc num expectedState = (testDriver lifeFunc num startState) == expectedState




