module Board
  ( Color(..)
  , Point(..)
  , Board(..)
  , initBoard
  , initCol
  , getPoint
  , isEmpty
  , isValid
  , addPointToBoard
  , addPoint
  , filterBoard
  , oppositeColor
  , isEmptyBoard
  , isOver
  , getCurPoint
  , printBoard
  , diagonals
  ) where

import Data.List
import Point
import Color

data Board = Board{row::Int, col::Int, points::[[Point]]}

instance Show Board where
  show (Board _ _ points) = intercalate "\n" $ map show points

instance Eq Board where
  (Board r1 c1 points1) == (Board r2 c2 points2) = (r1 == r2 && c1 == c2 && points1 == points2)

initBoard :: Int -> Int -> Board
initBoard row col = Board row col points
    where
        points = [initCol x col | x <- [1..row]]

initCol :: Int -> Int -> [Point]
initCol row col = if col > 0 then (initCol row (col - 1)) ++ [Point Empty (row,col)] else []

printBoard :: Board -> IO()
printBoard board = putStrLn $ show board

-- get point from a position
getPoint:: Board -> (Int,Int) -> Point
getPoint (Board _ _ points) (x,y) = (points !! (x - 1)) !! (y - 1)

-- check if a position is empty
isEmpty:: Point -> Board -> Bool
isEmpty (Point _ (x,y)) (Board row col points) = color == Empty
    where 
        (Point color (_, _)) = getPoint (Board row col points) (x, y)

-- check if the position we choose is in the board
isValid:: Point -> Board -> Bool
isValid (Point _ (x,y)) (Board row col _) = if (x > 0 && x <= row && y > 0 && y <= col) then True else False

addPointToBoard::Point -> Board -> Board
addPointToBoard (Point color (x,y)) (Board row col points)
    | (isValid (Point color (x,y)) (Board row col points) && isEmpty (Point color (x,y)) (Board row col points) ) = 
        addPoint (Point color (x,y)) (Board row col points)
    | otherwise = (Board row col points)

addPoint :: Point -> Board -> Board
addPoint (Point color (x,y)) (Board row col points) = Board row col newPoints
    where
        newPoints = upperRows ++ (leftCells ++ (Point color (x, y) : rightCells)) : lowerRows
        (upperRows, thisRow:lowerRows) = splitAt (x - 1) points
        (leftCells, _:rightCells) = splitAt (y - 1) thisRow


checkRow :: [Point] -> Color -> Int -> Int
checkRow [] prevColor count
    | count == 5 && prevColor == Black = 1
    | count == 5 && prevColor == White = 2
    | otherwise = 0
checkRow (x:xs) prevColor count
    | prevColor == Empty = checkRow xs color 1
    | prevColor == color && count == 4 = 
        if color == Black
        then 1
        else 2
    | prevColor == color && count < 4 = checkRow xs color (count + 1)
    | otherwise = 0
    where
        (Point color _) = x

diagonals :: [[a]] -> [[a]]
diagonals = tail . go [] where
    go b es_ = [h | h:_ <- b] : case es_ of
        []   -> transpose ts
        e:es -> go (e:ts) es
        where ts = [t | _:t <- b]

isOver::Board -> Bool
isOver (Board a b points) = 
    if (sum [(checkRow  (points !! x) Empty 1) | x <- [0..b-1] ]/= 0) then True else
        if (sum [(checkRow (( (transpose . reverse) points) !! x) Empty 1) | x <- [0..a-1] ]/= 0) then True else
            if (sum [(checkRow ((diagonals points) !! x) Empty 1) | x <- [0..b-1] ] /= 0) then True else
                if (sum [(checkRow ((diagonals ( (transpose . reverse) points)) !! x) Empty 1) | x <- [0..a-1] ] /= 0) then True else False


filterBoard :: Board -> Color -> [Point]
filterBoard (Board _ _ points) color =
  [point | rows <- points, point <- rows, isSameColor point]
  where
    isSameColor (Point c (_,_)) = c == color

oppositeColor :: Color -> Color
oppositeColor color
  | color == White = Black
  | color == Black = White
  | otherwise = error "Invalid opposite color"

isEmptyBoard :: Board -> Bool
isEmptyBoard (Board row col points) = Board row col points == initBoard row col

flatten :: [[a]] -> [a]
flatten xs = (\z n -> foldr (flip (foldr z)) n xs) (:) []

getCurPoint :: Board -> Board -> [Point]
getCurPoint (Board _ _ points1) (Board _ _ points2) = flatten points2 \\ flatten points1