-- | Play tic-tac-toe interactively.
module Data.TicTacToe.Interact
(
  tictactoe
) where

import Data.TicTacToe.Board
import Data.TicTacToe.Position
import Data.TicTacToe.Player
import Data.TicTacToe.GameResult
import Data.Char
import Control.Monad

-- | Play tic-tac-toe interactively.
tictactoe ::
  IO ()
tictactoe =
  gameLoop (\b' -> do surround $ printWithoutPositions b'
                      tictactoe' b') empty

gameLoop ::
  (BoardLike b, Move b to) =>
  (to -> IO ())
  -> b
  -> IO ()
gameLoop k b =
  let p = whoseTurn b
  in do putStrLns
          [
            show p ++ " to move [" ++ [toSymbol p] ++ "]"
          , "  [1-9] to Move"
          , "  q to Quit"
          , "  v to view board positions"
          ]
        putStr "  > "
        c <- getChar
        if c `elem` "vV"
          then
            do surround $ printWithPositions b
               gameLoop k b
          else
            if c `elem` ['1'..'9']
              then
                k (toPosition (digitToInt c) --> b)
              else
                if c `elem` "qQ"
                  then
                    do line
                       putStrLn "Bye!"
                  else
                    do surround $ putStrLn "Invalid selection. Please try again."
                       gameLoop k b

tictactoe' ::
  Board
  -> IO ()
tictactoe' b =
  gameLoop (foldMoveResult
              (do surround $ putStrLn "That position is already taken. Try again."
                  printWithoutPositions b
                  line
                  tictactoe' b)
              (\b' -> do surround $ printWithoutPositions b'
                         tictactoe' b')
              (\b' -> do surround $ printWithoutPositions b'
                         putStrLn (playerGameResult "Player 1 Wins!" "Player 2 Wins!" "Draw" (getResult b'))))
            b

surround ::
  IO ()
  -> IO ()
surround a =
  do nlines 2
     a
     line

putStrLns ::
  [String]
  -> IO ()
putStrLns =
  mapM_ putStrLn

line ::
  IO ()
line =
  nlines 1

nlines ::
  Int
  -> IO ()
nlines n =
  replicateM_ n (putStrLn [])

printWithPositions ::
  BoardLike b =>
  b
  -> IO ()
printWithPositions =
  printPositions (show . fromPosition)

printWithoutPositions ::
  BoardLike b =>
  b
  -> IO ()
printWithoutPositions =
  printPositions (const " ")

printPositions ::
  BoardLike b =>
  (Position -> String)
  -> b
  -> IO ()
printPositions k b =
  printEachPosition (\p -> maybe (k p) (return . toSymbol) (b `playerAt` p))

fromPosition ::
  Position
  -> Int
fromPosition =
  succ . fromEnum

toPosition ::
  Int
  -> Position
toPosition n =
  toEnum (n - 1)