import qualified Data.List as L
import System.IO
import System.Random

data CoinToss = H | T deriving (Read, Show, Eq)

parseToss :: String -> [CoinToss]
parseToss [] = []
parseToss (s:sx)
  | s == 'h' || s == 'H' = H: parseToss sx
  | s == 't' || s == 'T' = T: parseToss sx
  | otherwise = parseToss sx

notToss :: CoinToss -> CoinToss
notToss H = T
notToss T = H

instance Random CoinToss where
  random g = let (b, gb) = random g in (if b then H else T, gb)
  randomR = undefined

prompt :: (Read a) => String -> String -> (String -> Maybe a) -> IO a
prompt msg err parse = do
  putStrLn msg
  line <- getLine
  let ans = parse line
  case ans of
    Nothing   -> do
      putStrLn err
      prompt msg err parse
    Just ansB -> return ansB

showCat :: (Show a) => [a] -> String
showCat = concatMap show

data Winner = Player | CPU


runToss :: (RandomGen g) => [CoinToss] -> [CoinToss] -> g -> ([CoinToss], Winner)
runToss player cpu gen =
  let stream = randoms gen
      run ss@(s:sx)
        | L.isPrefixOf player ss = player
        | L.isPrefixOf cpu ss    = cpu
        | otherwise              = s: run sx
      winner = run stream
  in if L.isSuffixOf player winner
     then (winner, Player)
     else (winner, CPU)

game :: (RandomGen g, Num a, Show a) => Bool -> a -> a -> g -> IO ()
game cpuTurn playerScore cpuScore gen = do
  putStrLn $ "\nThe current score is CPU: " ++ show cpuScore
    ++ ", You: " ++ show playerScore

  let (genA, genB) = split gen
      promptPlayer check =
        prompt "Pick 3 coin sides: " "Invalid input." $ \s ->
          let tosses = parseToss s in
          if check tosses then Just tosses else Nothing
      promptCpu x  = putStrLn $ "I have chosen: " ++ showCat x

  (tosses, winner) <-
    if cpuTurn
    then do
      let cpuChoice = take 3 $ randoms gen
      promptCpu cpuChoice
      playerChoice <- promptPlayer $ \n -> n /= cpuChoice && 3 == length n
      return $ runToss playerChoice cpuChoice genA
    else do
      playerChoice <- promptPlayer $ \n -> 3 == length n
      let cpuChoice = case playerChoice of [a,b,_] -> [notToss b, a, b]
      promptCpu cpuChoice
      return $ runToss playerChoice cpuChoice genA

  putStrLn $ "The sequence tossed was: " ++ showCat tosses

  case winner of
    Player -> do
      putStrLn "You win!"
      game (not cpuTurn) (playerScore + 1) cpuScore genB
    CPU -> do
      putStrLn "I win!"
      game (not cpuTurn) playerScore (cpuScore + 1) genB

main :: IO ()
main = do
  hSetBuffering stdin LineBuffering
  stdgen <- getStdGen
  let (cpuFirst, genA) = random stdgen
  game cpuFirst 0 0 genA