{-
The Hangman module is responsible for controling the internal game
state. Thus, once a letter is guessed, this module validates
the guess and updates the game's interal state with that guess.
-}

module Hangman where

import           Control.Monad.Free
import           Data.Char
import           Data.Maybe
import           Data.Set (empty, insert, member)
import           Types
import           LetterGuesser(pruneCorrect, pruneIncorrect)

-- Get the internal state from RunningState
internalState :: RunningState -> InternalState
internalState (RunningState is) = is

isPlayableLetter :: Char -> Bool
isPlayableLetter c = member (toLower c) alphabetSet

-- Initialize the state with the word being guessed and subset of 
--  the dictionary with words of equal length to the word being guessed
createInitialState :: [Types.Word] -> Types.Word -> RunningState
createInitialState wordDictionary (Word w) = RunningState InternalState {
  wordDict = wordDictionary,
  wordToGuess = w,
  usedLetters = empty,
  currentWord = map (\_ -> Nothing) w
}

-- Update the RunningState with a letter Guess
applyGuess :: RunningState -> Guess -> GameState
applyGuess (RunningState is) g
    | all isJust (currentWord newIs) = Terminal $ Win newIs
    | otherwise = Running $ RunningState newIs
  where newIs = updateState is g

-- Update the InternalState with the new Guess
updateState :: InternalState -> Guess -> InternalState
updateState is (Guess c)
  | c `member` usedLetters is = is
  | any (eqIgnoreCase c) (wordToGuess is) = is {
    wordDict = pruneCorrect is,
    usedLetters = insert c (usedLetters is),
    currentWord = zipWith (getCharMaybe c) (wordToGuess is) (currentWord is)
  }
  | otherwise = is {
    wordDict = pruneIncorrect is c,
    usedLetters = insert c (usedLetters is)
  }
  where
    eqIgnoreCase char c' = char == toLower c'
    -- getCharMaybe returns Maybe of the letter if the guess was the letter, else Nothing
    getCharMaybe _ _ (Just x) = Just x
    getCharMaybe guessedChar char _
      | guessedChar == toLower char = Just char
      | otherwise = Nothing

gameOver :: TerminalState -> HangmanGame ()
gameOver ts = liftF $ GameOver ts

playerTurn :: InternalState -> HangmanGame Guess
playerTurn is = liftF $ PlayerTurn is id

-- Hangman Game loop. Ends once it is in a Terminal state.
playGame :: RunningState -> HangmanGame ()
playGame rs = do
  c <- playerTurn (internalState rs)
  case applyGuess rs c of
    Running rs' -> playGame rs'
    Terminal ts -> gameOver ts
