import Data.List (intersperse)

import System.Random (randomRIO)

import Data.Array (Array, (!), (//), array, bounds)

import Control.Monad (zipWithM_, replicateM, foldM, when)

type Board = Array (Char, Char) Int

flp :: Int -> Int
flp 0 = 1
flp 1 = 0

numRows, numCols :: Board -> String
numRows t =
  let ((a, _), (b, _)) = bounds t
  in [a .. b]

numCols t =
  let ((_, a), (_, b)) = bounds t
  in [a .. b]

flipRow, flipCol :: Board -> Char -> Board
flipRow t r =
  let e =
        [ (ix, flp (t ! ix))
        | ix <- zip (repeat r) (numCols t) ]
  in t // e

flipCol t c =
  let e =
        [ (ix, flp (t ! ix))
        | ix <- zip (numRows t) (repeat c) ]
  in t // e

printBoard :: Board -> IO ()
printBoard t = do
  let rows = numRows t
      cols = numCols t
      f 0 = '0'
      f 1 = '1'
      p r xs = putStrLn $ [r, ' '] ++ intersperse ' ' (map f xs)
  putStrLn $ "  " ++ intersperse ' ' cols
  zipWithM_
    p
    rows
    [ [ t ! (y, x)
      | x <- cols ]
    | y <- rows ]



setupGame :: Char -> Char -> IO (Board, Board)
setupGame sizey sizex
                
 = do
  let mk rc = (\v -> (rc, v)) <$> randomRIO (0, 1)
      rows = ['a' .. sizey]
      cols = ['1' .. sizex]
  goal <-
    array (('a', '1'), (sizey, sizex)) <$>
    mapM
      mk
      [ (r, c)
      | r <- rows 
      , c <- cols ]
  start <-
    do let change :: Board -> Int -> IO Board
           
           change t 0 = flipRow t <$> randomRIO ('a', sizey)
           
           change t 1 = flipCol t <$> randomRIO ('1', sizex)
       numMoves <- randomRIO (3, 15) 
       
       moves <- replicateM numMoves $ randomRIO (0, 1)
       
       foldM change goal moves
  if goal /= start 
    then return (goal, start) 
    else setupGame sizey sizex 

main :: IO ()
main = do
  putStrLn "Select a board size (1 - 9).\nPress any other key to exit."
  sizec <- getChar
  when (sizec `elem` ['1' .. '9']) $
    do let size = read [sizec] - 1
       (g, s) <- setupGame (['a' ..] !! size) (['1' ..] !! size)
       turns g s 0
  where
    turns goal current moves = do
      putStrLn "\nGoal:"
      printBoard goal
      putStrLn "\nBoard:"
      printBoard current
      when (moves > 0) $
        putStrLn $ "\nYou've made " ++ show moves ++ " moves so far."
      putStrLn $
        "\nFlip a row (" ++
        numRows current ++ ") or a column (" ++ numCols current ++ ")"
      v <- getChar
      if v `elem` numRows current
        then check $ flipRow current v
        else if v `elem` numCols current
               then check $ flipCol current v
               else tryAgain
      where
        check t =
          if t == goal
            then putStrLn $ "\nYou've won in " ++ show (moves + 1) ++ " moves!"
            else turns goal t (moves + 1)
        tryAgain = do
          putStrLn ": Invalid row or column."
          turns goal current moves