-- | Main entry point for the console interface to the game.
module Hs2048.Main
    ( direction
    , getChars
    , getMove
    , play
    ) where

import qualified Hs2048.Board     as B
import qualified Hs2048.Direction as D
import qualified Hs2048.Game      as G
import           Hs2048.Renderer  (renderGame)
import           System.IO        (BufferMode (NoBuffering), hSetBuffering,
                                   hSetEcho, stdin)
import qualified System.Random    as R

{- |
    Converts a string into a direction.

    >>> direction "\ESC[D"
    Just West
    >>> direction "<"
    Nothing
-}
direction :: String -> Maybe D.Direction
direction "\ESC[D" = Just D.West
direction "\ESC[B" = Just D.South
direction "\ESC[C" = Just D.East
direction "\ESC[A" = Just D.North
direction _ = Nothing

{- |
    Gets up to three characters from standard input. If the input corresponds
    to an arrow key, it will be returned. Otherwise 'Nothing' will be returned.
    Will only consume enough input to determine if the input is an arrow key or
    not.
-}
getChars :: IO (Maybe String)
getChars = do
    a <- getChar
    if a /= '\ESC' then return Nothing else do
        b <- getChar
        if b /= '[' then return Nothing else do
            c <- getChar
            return $ if c `elem` "ABCD"
                then Just [a, b, c]
                else Nothing

{- |
    Reads from standard input and converts it into a direction. See 'getChars'.
-}
getMove :: IO (Maybe D.Direction)
getMove = fmap (maybe Nothing direction) getChars

{- |
    Plays the game.
-}
play :: R.RandomGen r => (B.Board, r) -> IO ()
play (b, r) = do
    hSetBuffering stdin NoBuffering
    hSetEcho stdin False

    putStr (renderGame b)

    if G.hasWon b then putStrLn "You won!" else do
        if G.isOver b then putStrLn "You lost." else do
            m <- getMove
            case m of
                Nothing -> putStrLn "Unknown move." >> play (b, r)
                Just d -> if B.canMove b d
                    then putStrLn (D.render d) >> play (G.addRandomTile (B.move b d) r)
                    else putStrLn "Invalid move." >> play (b, r)