module Wordify.Rules.Game(Game,
                          makeGame,
                          movesMade,
                          gameStatus,
                          board,
                          bag,
                          dictionary,
                          moveNumber,
                          player1,
                          player2,
                          optionalPlayers,
                          currentPlayer,
                          getPlayer,
                          playerNumber,
                          GameStatus(InProgress, Finished),
                          players,
                          passes,
                          numberOfPlayers,
                          history,
                          History(History)) where

  import Wordify.Rules.Player
  import Wordify.Rules.Board
  import Wordify.Rules.Dictionary
  import Wordify.Rules.LetterBag
  import Wordify.Rules.ScrabbleError
  import Data.Maybe
  import Wordify.Rules.Game.Internal
  import qualified Data.Sequence as Seq
  import qualified Data.Foldable as F
  import Safe
  import Control.Monad
  import Control.Applicative
  import qualified Data.List.Safe as LS

  | 
     Starts a new game . 

     A game has at least 2 players , and 2 optional players ( player 3 and 4 . ) The players should be newly created , 
     as tiles from the letter bag will be distributed to each player . 

     Takes a letter bag and dictionary , which should be localised to the same language . 

     Yields a tuple with the first player and the initial game state . Returns a ' Left ' if there are not enough 
     tiles in the letter bag to distribute to the players . 
  
    Starts a new game. 

    A game has at least 2 players, and 2 optional players (player 3 and 4.) The players should be newly created,
    as tiles from the letter bag will be distributed to each player.

    Takes a letter bag and dictionary, which should be localised to the same language.

    Yields a tuple with the first player and the initial game state. Returns a 'Left' if there are not enough
    tiles in the letter bag to distribute to the players.
  -}
  makeGame :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> Either ScrabbleError Game
  makeGame playing startBag dict =
    do
      (remainingBag, initialPlayers) <- initialisePlayers playing startBag
      case initialPlayers of
        firstPlayer : secondPlayer : optionals ->
         return $ Game firstPlayer secondPlayer (toOptionalPlayers optionals) emptyBoard
          remainingBag dict firstPlayer firstPlayerNumber firstMoveNumber initialPasses initialGameState initialHistory
        _ -> Left $ MiscError "Unexpected error in logic. There were not at least two players. This should never happen."
      where
        initialHistory = History startBag Seq.empty
        firstPlayerNumber = 1
        firstMoveNumber = 1
        initialPasses = 0
        initialGameState = InProgress

        toOptionalPlayers :: [Player] -> Maybe (Player, Maybe Player)
        toOptionalPlayers (x : []) = Just (x, Nothing)
        toOptionalPlayers (x : y : []) = Just (x, Just y)
        toOptionalPlayers _ = Nothing


  initialisePlayers :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Either ScrabbleError (LetterBag, [Player])
  initialisePlayers (play1, play2, maybePlayers) initialBag = 
    let transitions = distributeFinite givePlayerTiles initialBag allPlayers
    in case (join . lastMay) transitions of
      Nothing -> Left $ NotEnoughLettersInStartingBag (bagSize initialBag)
      Just (finalBag, _) -> Right (finalBag, map snd (catMaybes transitions))
    where
      allPlayers = [play1, play2] ++ optionalsToPlayers maybePlayers
      givePlayerTiles currentBag giveTo = 
        (\(newTiles, newBag) -> (newBag, giveTiles giveTo newTiles)) <$> takeLetters currentBag 7

      distributeFinite :: (b -> a -> Maybe (b, a)) -> b -> [a] -> [Maybe (b, a)]
      distributeFinite takeUsing distributeFrom = go takeUsing (Just distributeFrom)
        where
          go :: (b -> a -> Maybe (b,a)) -> Maybe b -> [a] -> [Maybe (b, a)]
          go _ _ [] = []
          go giveBy Nothing (_ : receivers) = Nothing : go giveBy Nothing receivers
          go giveBy (Just distributer) (receiver : receivers) =
            case giveBy distributer receiver of
              Nothing -> Nothing : go giveBy Nothing receivers
              Just (currentDistributer, currentReceiver) -> 
                Just (currentDistributer, currentReceiver) : go giveBy (Just currentDistributer) receivers


  optionalsToPlayers :: Maybe (Player, Maybe Player) -> [Player]
  optionalsToPlayers optionals = case optionals of 
                    Nothing -> []
                    Just (player3, Nothing) -> [player3]
                    Just (player3, Just player4) -> [player3, player4]


  players :: Game -> [Player]
  players game = [player1 game, player2 game] ++ optionalsToPlayers (optionalPlayers game)

  getPlayer :: Game -> Int -> Maybe Player
  getPlayer game playerNumber = (players game) LS.!! (playerNumber - 1)

  numberOfPlayers :: Game -> Int
  numberOfPlayers game = 2 + maybe 0 (\(_, maybePlayer4) -> if isJust maybePlayer4 then 2 else 1) (optionalPlayers game)

  {- |
    Returns a history of the moves made in the game.
  -}
  movesMade :: Game -> [Move]
  movesMade game = 
    case history game of 
      History _ moves -> F.toList moves

 |
    Returns a history of the moves made in the game.
  
module Wordify.Rules.Game(Game,
                          makeGame,
                          movesMade,
                          gameStatus,
                          board,
                          bag,
                          dictionary,
                          moveNumber,
                          player1,
                          player2,
                          optionalPlayers,
                          currentPlayer,
                          getPlayer,
                          playerNumber,
                          GameStatus(InProgress, Finished),
                          players,
                          passes,
                          numberOfPlayers,
                          history,
                          History(History)) where

  import Wordify.Rules.Player
  import Wordify.Rules.Board
  import Wordify.Rules.Dictionary
  import Wordify.Rules.LetterBag
  import Wordify.Rules.ScrabbleError
  import Data.Maybe
  import Wordify.Rules.Game.Internal
  import qualified Data.Sequence as Seq
  import qualified Data.Foldable as F
  import Safe
  import Control.Monad
  import Control.Applicative
  import qualified Data.List.Safe as LS

  | 
     Starts a new game . 

     A game has at least 2 players , and 2 optional players ( player 3 and 4 . ) The players should be newly created , 
     as tiles from the letter bag will be distributed to each player . 

     Takes a letter bag and dictionary , which should be localised to the same language . 

     Yields a tuple with the first player and the initial game state . Returns a ' Left ' if there are not enough 
     tiles in the letter bag to distribute to the players . 
  
    Starts a new game. 

    A game has at least 2 players, and 2 optional players (player 3 and 4.) The players should be newly created,
    as tiles from the letter bag will be distributed to each player.

    Takes a letter bag and dictionary, which should be localised to the same language.

    Yields a tuple with the first player and the initial game state. Returns a 'Left' if there are not enough
    tiles in the letter bag to distribute to the players.
  -}
  makeGame :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> Either ScrabbleError Game
  makeGame playing startBag dict =
    do
      (remainingBag, initialPlayers) <- initialisePlayers playing startBag
      case initialPlayers of
        firstPlayer : secondPlayer : optionals ->
         return $ Game firstPlayer secondPlayer (toOptionalPlayers optionals) emptyBoard
          remainingBag dict firstPlayer firstPlayerNumber firstMoveNumber initialPasses initialGameState initialHistory
        _ -> Left $ MiscError "Unexpected error in logic. There were not at least two players. This should never happen."
      where
        initialHistory = History startBag Seq.empty
        firstPlayerNumber = 1
        firstMoveNumber = 1
        initialPasses = 0
        initialGameState = InProgress

        toOptionalPlayers :: [Player] -> Maybe (Player, Maybe Player)
        toOptionalPlayers (x : []) = Just (x, Nothing)
        toOptionalPlayers (x : y : []) = Just (x, Just y)
        toOptionalPlayers _ = Nothing


  initialisePlayers :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Either ScrabbleError (LetterBag, [Player])
  initialisePlayers (play1, play2, maybePlayers) initialBag = 
    let transitions = distributeFinite givePlayerTiles initialBag allPlayers
    in case (join . lastMay) transitions of
      Nothing -> Left $ NotEnoughLettersInStartingBag (bagSize initialBag)
      Just (finalBag, _) -> Right (finalBag, map snd (catMaybes transitions))
    where
      allPlayers = [play1, play2] ++ optionalsToPlayers maybePlayers
      givePlayerTiles currentBag giveTo = 
        (\(newTiles, newBag) -> (newBag, giveTiles giveTo newTiles)) <$> takeLetters currentBag 7

      distributeFinite :: (b -> a -> Maybe (b, a)) -> b -> [a] -> [Maybe (b, a)]
      distributeFinite takeUsing distributeFrom = go takeUsing (Just distributeFrom)
        where
          go :: (b -> a -> Maybe (b,a)) -> Maybe b -> [a] -> [Maybe (b, a)]
          go _ _ [] = []
          go giveBy Nothing (_ : receivers) = Nothing : go giveBy Nothing receivers
          go giveBy (Just distributer) (receiver : receivers) =
            case giveBy distributer receiver of
              Nothing -> Nothing : go giveBy Nothing receivers
              Just (currentDistributer, currentReceiver) -> 
                Just (currentDistributer, currentReceiver) : go giveBy (Just currentDistributer) receivers


  optionalsToPlayers :: Maybe (Player, Maybe Player) -> [Player]
  optionalsToPlayers optionals = case optionals of 
                    Nothing -> []
                    Just (player3, Nothing) -> [player3]
                    Just (player3, Just player4) -> [player3, player4]


  players :: Game -> [Player]
  players game = [player1 game, player2 game] ++ optionalsToPlayers (optionalPlayers game)

  getPlayer :: Game -> Int -> Maybe Player
  getPlayer game playerNumber = (players game) LS.!! (playerNumber - 1)

  numberOfPlayers :: Game -> Int
  numberOfPlayers game = 2 + maybe 0 (\(_, maybePlayer4) -> if isJust maybePlayer4 then 2 else 1) (optionalPlayers game)

  movesMade :: Game -> [Move]
  movesMade game = 
    case history game of 
      History _ moves -> F.toList moves
