{-|
	Defines a state machine of 3D Tic-Tac-Toe.
-}
module Game.TicTacToe3D.TicTacToe3D (
	Team,
	Issue,
	Board,
	Game (..),
	done,
	newGame,
	playGame
) where
	import Control.Monad
	import Data.Functor
	import Data.Monoid
	import Data.Maybe
	import Data.List
	import Data.Foldable as F
	import Data.Tuple.Homogenous
	import Game.TicTacToe3D.Vector3 as V

	{-|
		Restructures a list.

		>>> collapse [1, 2, 3, 4, 5]
		[(1, 5), (2, 4)]
	-}
	collapse :: [a] -> [(a, a)]
	collapse ns = take (halfLen ns) (collapse' ns)
		where halfLen ms = length ms `quot` 2
		      collapse' ms = zip ms (reverse ms)

	{-|
		In the specified dimension,
		generates all the possible directions,
		and pairs up every two of them that face to each other.
	-}
	directions :: Int -> [([Int], [Int])]
	directions i = collapse $ allDirections
		where allDirections = replicateM i [-1, 0, 1]

	{-|
		Simplifies

		> directions 3
	-}
	directions3 :: [Tuple2 I3]
	directions3 = f <$> directions 3 where
		f t = g <$> Tuple2 t where
			g [x, y, z] = (x, y, z)

	{-|
		Retrieves a line to every direction from the given point.
	-}
	explode :: I3 -> [Tuple2 [I3]]
	explode c = (walk c <$>) <$> directions3 where
		walk h i = let j = add h i in j : walk j i where
			add (h, i, j) (k, l, m) = (h + k, i + l, j + m)

	{-|
		Checks if the given point is inside of
		the specified area in every three dimension.
	-}
	withinC :: Int -> Int -> I3 -> Bool
	withinC min max c = 
		F.all f $ Tuple3 c where
			f n = min <= n && n < max

	{-|
		Retrieves all the possible lines that
		intersect at the given point. All those
		lines are within the area
		from 0 to the specified number.
	-}
	explode' :: Int -> I3 -> [[I3]]
	explode' len crd = catMaybes $ do
		Tuple2 (fs, bs) <- explode crd
		let	line = crd : pick fs ++ pick bs where
				pick = takeWhile $ withinC 0 len
		return $ if length line == len then Just line else Nothing

	{-|
		Represents a team.
	-}
	type Team = Bool

	{-|
		Represents a state of one point in a board;
		owned by either team or empty.
	-}
	type Issue = Maybe Team

	{-|
		Represents a tic-tac-toe board with its side length.
	-}
	type Board = (Int, V3 Issue)

	{-|
		Folds a list of 'Issue'.

		> foldI [B, B, B] = B
		> foldI [B, B, R] = D
		> foldI [B, B, _] = D
	-}
	foldI :: [Issue] -> Issue
	foldI []     = Nothing
	foldI (x:xs) = F.foldr add x xs
		where add m n = if m == n then m else Nothing
		-- not Monoid; mappend mempty x /= x

	{-|
		Retrieves the first 'Just' element in a given structure,
		or 'Nothing' if not found any.

		> firstJust [Nothing, Just 1 , Nothing] = Just 1
		> firstJust [Nothing, Nothing, Nothing] = Nothing
	-}
	firstJust :: (Foldable f) => f (Maybe a) -> Maybe a
	firstJust ms = join $ F.find isJust ms

	{-|	
		Retrieves the winner and the owned line.
		Nothing if the game has not ended yet.
	-}
	check :: Board -> I3 -> Maybe ([I3], Team)
	check (i, v) c = firstJust $ do
		l <- explode' i c
		let	j = foldI $ (v V.!) <$> l
		return $ (,) l <$> j

	{-|	
		Represents a result of one team's action.
	-}
	type Result = Maybe (Either [I3] Board)

	{-|	
		Makes the given team play the specified square.
		
		> Just Left [I3]   -- The team has won.
		> Just Right Board -- The game goes on.
		> Nothing          -- The specified square is not playable.
	-}
	play :: Board -> Team -> I3 -> Result
	play (l, v) t c
		| v V.! c /= Nothing = Nothing
		| otherwise = 
			let new = (l, v V.// (c, Just t))
			in Just $ case check new c of
				Just (cs, _) -> Left  cs
				Nothing      -> Right new

	{-|	
		Initializes a board with the given values.
	-}
	initBoard :: Int -> (I3 -> Issue) -> Board
	initBoard i f = (,) i $ V.init i f

	{-|	
		Represents a state of a tic-tac-toe game.
		'Done' represents a game that has finished.
	-}
	data Game = Game Board Team | Done Team [I3]

	{-|	
		An initialized state of a game.
	-}
	newGame :: Game
	newGame = Game newBoard True where
		newBoard = (initBoard 3 $ const Nothing)

	{-|
		Retrieves whether the given game has finished or not.
	-}
	done :: Game -> Bool
	done (Done _ _) = True
	done _ = False

	{-|
		Lets the current team play at the specified square.
	-}
	playGame :: Int -> Game -> Game
	playGame _ g @ (Done _ _) = g
	playGame c g @ (Game b t) =
		case play b t (i3 c) of
			Just (Left cs)  -> Done t cs
			Just (Right b') -> Game b' (not t)
			Nothing         -> g