{-# LANGUAGE PatternGuards, ParallelListComp, BangPatterns #-}

module World where
import Cell
import System.Random
import Control.Monad
import Graphics.Gloss
import Graphics.Gloss.Interface.Pure.Simulate
import qualified Data.Vector	as Vec
type Vec	= Vec.Vector

-- Index ----------------------------------------------------------------------
-- | An index into the vector holding all the cells.
type Index	= Int

-- | The x y coordinate of a cell.
type Coord	= (Int, Int)

indexOfCoord :: World -> Coord -> Index
indexOfCoord world (x, y)	
	= x + y * (worldWidth world)

coordOfIndex :: World -> Index -> Coord
coordOfIndex world i		
	= ( i `mod` worldWidth world
	  , i `div` worldWidth world)


-- World ----------------------------------------------------------------------
data World 	
	= World
	{ worldCells		:: Vec Cell 
	, worldWidth		:: Int 
	, worldHeight		:: Int 

	-- | Width and height of each cell.
	, worldCellSize		:: Int

	-- | Number of pixels to leave between each cell.
	, worldCellSpace	:: Int

	-- | Cells less than this age are drawn with the color ramp
	, worldCellOldAge	:: Int

	-- | Seconds to wait between each simulation step.
	, worldSimulationPeriod	:: Float 
	
	-- | Time that has elapsed since we drew the last step
	, worldElapsedTime	:: Float }


-- | Make a new world of a particular size.
randomWorld :: (Int, Int) -> IO World
randomWorld (width, height)
 = do	bools	<- replicateM (width * height) randomIO 
	return	$ World
		{ worldCells		= Vec.fromList $ map cellOfBool bools
		, worldWidth		= width
		, worldHeight		= height
		, worldCellSize		= 5
		, worldCellSpace	= 1 
		, worldCellOldAge	= 20
		, worldSimulationPeriod	= 0.1 
		, worldElapsedTime	= 0 }


-- | Convert a bool to a live or dead cell.
cellOfBool :: Bool -> Cell
cellOfBool b
 = case b of
	True	-> CellAlive 0
	False	-> CellDead


-- | Get the cell at a particular coordinate in the world.
getCell :: World -> Coord -> Cell
getCell world coord@(x, y)
	| x < 0 || x >= worldWidth  world	= CellDead
	| y < 0 || y >= worldHeight world	= CellDead

	| otherwise		
	= worldCells world Vec.! indexOfCoord world coord 


-- | Get the neighbourhood of cells aroudn this coordinate.
getNeighbourhood :: World -> Coord -> [Cell]
getNeighbourhood world (ix, iy)
 = let	indexes	= [ (x, y) 
			| x <- [ix - 1 .. ix + 1]
			, y <- [iy - 1 .. iy + 1]
			, not (x == ix && y == iy) ]
   in	map (getCell world) indexes


-- | Compute the next cell state depending on its neighbours.
stepCell :: Cell -> [Cell] -> Cell
stepCell cell neighbours
 = let 	live	= length (filter isAlive neighbours)
   in	case cell of
	 CellAlive age	-> if elem live [2, 3] then CellAlive (age + 1) else CellDead
	 CellDead	-> if live == 3        then CellAlive 0         else CellDead


-- | Compute the next state of the cell at this index in the world.
stepIndex :: World -> Int -> Cell -> Cell
stepIndex world index cell
 = let	coord	= coordOfIndex world index
	neigh	= getNeighbourhood world coord
   in	stepCell cell neigh

		
-- | Compute the next world state.
stepWorld :: World -> World
stepWorld world
	= world { worldCells = Vec.imap (stepIndex world) (worldCells world) }

		
-- | Simulation function for worlds.
simulateWorld :: ViewPort -> Float -> World -> World
simulateWorld _ time world 

	-- If enough time has passed then it's time to step the world.
	| worldElapsedTime world >= (worldSimulationPeriod world)
	= let world'	= stepWorld world
	  in  world' { worldElapsedTime = 0 }
	
	-- Wait some more.
	| otherwise
	= world { worldElapsedTime = worldElapsedTime world + time }


