module Game where
import Deal
import Data.List (elemIndex, inits)
import Control.Monad (guard)
import Control.Parallel.Strategies (parMap, rpar)

{-

game.hs
by Joe Huang (jch2220)

4995 Final Project

Descriptions:
This file describes the freecell game structure. Here it defines the 
data structure for the game component, show (print) functions, loading
functions to load the deal from deal.hs, game functions that provide 
all possible move given a game status, apply functions that apply move
to a game status, and some helper functions. 

The basic rule of the Freecell is as followed (from the reference):

    In a game of FreeCell solitaire, there are 8 columns, 4 foundations 
    spaces, and 4 free cells. At the beginning of each game, columns 1 
    through 4 are dealt 7 cards each; columns 5 through 8 are dealt 6 cards each.
    The four foundation spaces are used to store the cards from each 
    suit (Hearts, Diamonds, Spades and Clubs). Cards must be placed in
    the foundation of their suit in ascending order from the ace to the
    king, but once a card has been placed in the foundation it cannot be
    removed. The free cells start empty and can hold a single card of 
    any suit as long as the cell is empty. Cards can be placed in the 
    columns onto cards that are of the opposite suit color and one value higher. 

    Stacks of cards can be moved as long as the stack is alternating suit
    colors and is stacked in ascending order. To move a stack of cards
    of size n, the number of open free cells and the number of empty 
    columns must add up to n-1. The objective of the game is to move
    all 52 cards into the foundation spaces. To complete the objective 
    the cards must be organized in a way that allows the cards to be placed
    into the foundations, thus solving the game. 

Reference:
Freecell Solitaire Optimization: http://people.uncw.edu/tagliarinig/Courses/380/S2015%20papers%20and%20presentations/Freecell%20Opt-Beasley%20Brown/FreeCell_csc380.pdf

-}

-- -----------------------------------------------------------------------------
--                              Data Structures
-- -----------------------------------------------------------------------------

data Rank = Ace | Two | Three | Four | Five | Six | Seven | Eight | Nine |
            Ten | Jack | Queen | King 
    deriving (Eq,Ord,Enum,Bounded,Show,Read)
    
data Suit = Club | Diamond | Heart | Spade
    deriving (Eq,Ord,Enum,Bounded,Show,Read)

data Card = Card Rank Suit
    deriving (Eq,Ord,Bounded,Show,Read)

data Cascade = Cascade [Card]
    deriving (Show,Read,Eq,Ord)

data Cascades = Cascades [Cascade]
    deriving (Eq, Ord)

instance Show Cascades where 
   show (Cascades ccs) = unlines $ (parMap rpar) unwords $ showCascades (Cascades ccs)

data Freecell = Freecell [Card]
    deriving (Eq, Ord)

instance Show Freecell where
    show (Freecell cards) = unwords $ (parMap rpar) showCard cards

data Foundation = Foundation [Int]
    deriving (Show, Read, Eq, Ord)

data Game = Game {cascades :: Cascades, freecell :: Freecell, 
                  foundation :: Foundation }
    deriving (Eq, Ord)

instance Show Game where 
    show (Game ccs fc (Foundation fd)) = unlines ["FC: " ++ show fc, 
                                     "FD: " ++ show fd, show ccs]

data Stack = Stack [Card] 
    deriving (Show, Read, Eq, Ord)

data Move = MoveCasToFC Card Int 
          | MoveCasToFoun Card Int 
          | MoveCasToCas Card Int Int
          | MoveFCToCas Card Int 
          | MoveFCToFoun Card
          | MoveMult Card Stack Int Int
    deriving (Show, Read, Eq, Ord)

-- -----------------------------------------------------------------------------
--                              Load functions
-- -----------------------------------------------------------------------------

loadRank :: Char -> Rank
loadRank rank =
    case rank of 
        'A' -> Ace
        '2' -> Two
        '3' -> Three
        '4' -> Four
        '5' -> Five
        '6' -> Six
        '7' -> Seven
        '8' -> Eight
        '9' -> Nine
        'T' -> Ten
        'J' -> Jack
        'Q' -> Queen
        'K' -> King
        _ -> error "Invalid input for suit"

loadSuit :: Char -> Suit 
loadSuit suit =
    case suit of 
        'C' -> Club
        'D' -> Diamond
        'H' -> Heart
        'S' -> Spade
        _ -> error "Invalid input for suit"

loadCard :: String -> Card 
loadCard card 
    | length card /= 2 = error "Invalid input for card"
    | otherwise = Card (loadRank (head card)) (loadSuit (last card))

loadCascade :: [String] -> Cascade
loadCascade cascade = Cascade ((parMap rpar) loadCard cascade)

loadCascades :: [[String]] -> Cascades 
loadCascades ccs = Cascades ((parMap rpar) loadCascade $ foldl combineListofLists [] 
                                             $ (parMap rpar) breakListofList ccs)

-- -----------------------------------------------------------------------------
--                              Show functions
-- ----------------------------------------------------------------------------- 

showRank :: Rank -> Char 
showRank rank =
    case rank of 
        Ace -> 'A'
        Two -> '2'
        Three -> '3'
        Four -> '4'
        Five -> '5'
        Six -> '6'
        Seven -> '7'
        Eight -> '8'
        Nine -> '9'
        Ten -> 'T'
        Jack -> 'J'
        Queen -> 'Q'
        King -> 'K'

showSuit :: Suit -> Char 
showSuit suit =
    case suit of 
        Club -> 'C'
        Heart -> 'H'
        Spade -> 'S'
        Diamond -> 'D'

showCard :: Card -> String
showCard (Card rank suit) = [showRank rank, showSuit suit] 

-- function to make cascade from column into row so showCascades can combine them 
showCascade :: Cascade -> Int -> Int -> [[String]]
showCascade (Cascade cards) l maxSpace
    | l == maxSpace = [[]] 
    | length cards == 0 && l < maxSpace = ["  "]:(showCascade (Cascade []) (l + 1) maxSpace)
    | otherwise = [showCard (head cards)]:(showCascade (Cascade (tail cards)) (l+1) maxSpace)

-- function to show cascades by combining cascade as row 
showCascades :: Cascades -> [[String]]
showCascades (Cascades ccs) = [(show i ++ " ") | i <- [0..7::Int]]:
    (foldl combineListofLists [] 
           $ (parMap rpar) (\x->showCascade x 0 (getMaxLengthCascasde (Cascades ccs) 0 )) ccs )

-- -----------------------------------------------------------------------------
--                              Game functions
-- -----------------------------------------------------------------------------

-- initialize a game given a deal number
initializeGame :: Int -> Game
initializeGame gameNumber = Game (loadCascades $ separateDealRow $ startDeal gameNumber) 
                                 (Freecell [])
                                 (Foundation [0,0,0,0])

-- check if the game is finished 
-- ex: isFinished (Game (Cascades []) (Freecell []) (Foundation [13,13,13]))
isFinished :: Game -> Bool 
isFinished (Game (Cascades ccs) (Freecell fc) (Foundation fd)) = 
    (checkAllCascadeEmpty (Cascades ccs)) && 
    (length fc == 0) && (and $ (parMap rpar) (\x -> x == 13) fd)

-- given a game, get all Move 
getAllMove :: Game -> [Move]
getAllMove game = do
    (getAllCasToCas game) ++ (getAllCasToFC game) ++ (getAllCasToFoun game) 
    ++ (getAllFCToCas game) ++ (getAllMoveMult game) ++ (getAllFCToFoun game)

-- given a game, return all possible MoveMult moves 
getAllMoveMult :: Game -> [Move]
getAllMoveMult (Game (Cascades ccs) fc fd) =
    concat $ 
        (parMap rpar) (\x-> getMoveMult (Game (Cascades ccs) fc fd) x) 
            (getAllValidStack (Game (Cascades ccs) fc fd))

-- given a game and a stack, return all possible MoveMult 
-- ex: getAllMoveMult (Game (Cascades [Cascade [Card Two Spade, Card Ace Heart], Cascade [Card Three Diamond]]) (Freecell []) (Foundation []))
getMoveMult :: Game -> (Int, Stack) -> [Move]
getMoveMult (Game (Cascades _) _ _) (_, Stack []) = error "getMoveMult: empty stack"
getMoveMult (Game (Cascades ccs) _ _) (from,(Stack (card:cards))) = 
    do 
        (Cascade cc) <- ccs 
        let Just index = elemIndex (Cascade cc) ccs
        guard ((length cc > 0 && checkValidNextCard card (last cc)) || (length cc == 0))
        return (MoveMult card (Stack (card:cards)) from index)

-- given a game, return all valid stacks whose size is one less than the free space available
-- ex: getAllValidStack (Game (Cascades [(Cascade [Card Two Spade, Card Ace Diamond])]) (Freecell [Card Ace Spade, Card Queen Spade, Card Two Club]) (Foundation [])) 
getAllValidStack :: Game -> [(Int, Stack)]
getAllValidStack (Game (Cascades ccs) fc fd) = 
    if freespace >= 1 then concat $ (parMap rpar) (\(Cascade x) -> 
                                        if (length x > 1) 
                                            then (parMap rpar) 
                                                 (\s -> ((getCascadeIndex (Cascade x) (Cascades ccs)), s)) 
                                                 $ (getValidStack (Cascade x) freespace [])
                                        else []) 
                                    ccs
    else []
    where freespace = ((checkFreeSpace (Game (Cascades ccs) fc fd)))

-- given a game and a cascade, return all valid stack assume that limit is 
-- greater than 1 and length of cascade is greather than 1
-- ex: getValidStack (Cascade [Card Two Spade, Card Three Diamond, Card King Heart, Card Queen Spade, Card Jack Diamond]) 3 []
getValidStack :: Cascade -> Int -> [Card] -> [Stack]
getValidStack (Cascade cc) limit cards
    | length cc > 1 && length cards - 1 <= limit && checkValidStack ((last cc):cards) = 
        getValidStack (Cascade (init cc)) limit ((last cc):cards)
    | length cc == 1 && length cards - 1 <= limit && checkValidStack ((last cc):cards) = 
        getValidStack (Cascade []) limit ((last cc):cards)
    | otherwise = if length cards > 1 
                    then (parMap rpar) Stack 
                                     $ drop 2 
                                     $ (parMap rpar) reverse 
                                     $ inits (reverse cards) 
                  else []
                   
-- given a list of cards, check if they are alternating color and in decending order 
checkValidStack :: [Card] -> Bool 
checkValidStack [] = error "checkValidStack: should never feed empty" 
checkValidStack [_] = True 
checkValidStack [c1,c2] = checkValidNextCard c2 c1 
checkValidStack cs = (checkValidNextCard (last cs) (last (init cs))) 
                     && checkValidStack (init cs)

-- given a game, calculate number of free spaces to move 
checkFreeSpace :: Game -> Int 
checkFreeSpace (Game ccs fc _) = checkFreeCascade ccs + checkFreecell fc

-- check if cascades have free cascade 
checkFreeCascade :: Cascades -> Int 
checkFreeCascade (Cascades []) = 0
checkFreeCascade (Cascades ((Cascade cc):ccs)) = 
    if cc == [] then (1 + checkFreeCascade (Cascades ccs)) 
    else checkFreeCascade (Cascades ccs)

-- check if freecell has free space
checkFreecell :: Freecell -> Int 
checkFreecell (Freecell fc) 
    | length fc >= 4 = 0 
    | otherwise = (4 - length fc)

-- given a game, return all possible MoveFCToFoun 
-- ex: getAllFCToFoun (Game (Cascades []) (Freecell [Card Ace Spade]) (Foundation [0,0,0,0])
getAllFCToFoun :: Game -> [Move]
getAllFCToFoun (Game ccs (Freecell fc) fd) = 
    concat $ (parMap rpar) (\x -> getFCToFoun (Game ccs (Freecell fc) fd) x) fc

-- given a card (from FC) and a game, return all possible MoveFCToFoun move
getFCToFoun :: Game -> Card -> [Move]
getFCToFoun (Game _ _ (Foundation fd)) (Card rank suit) =
    if ((fd !! index) == (rankIndex)) then [MoveFCToFoun (Card rank suit)] else []
    where Just index = elemIndex suit [minBound..maxBound::Suit]
          Just rankIndex = elemIndex rank [minBound..maxBound::Rank]

-- given a game, return all possible MoveFCToCas moves 
-- ex: getAllFCToCas (Game (Cascades [Cascade [Card Two Spade]]) (Freecell [Card Ace Diamond]) (Foundation [0,0,0,0]))
getAllFCToCas :: Game -> [Move]
getAllFCToCas (Game ccs (Freecell fc) fd) = 
    concat [getFCToCas c (Game ccs (Freecell fc) fd) | c <- fc]

-- given a card (from FC) and a game, return all possible MoveFCToCas move 
getFCToCas :: Card -> Game -> [Move]
getFCToCas card (Game (Cascades ccs) _ _) = 
    do 
        (Cascade cc) <- ccs
        let Just index = elemIndex (Cascade cc) ccs
        guard ((length cc > 0 && checkValidNextCard card (last cc)) || (length cc == 0))
        return (MoveFCToCas card index)

-- given a game, return all possible MoveCasToFC moves
-- ex: getAllCasToFC (Game (Cascades [Cascade [Card Ace Spade],Cascade [Card Two Diamond]]) (Freecell []) (Foundation []))
getAllCasToFC :: Game -> [Move]
getAllCasToFC (Game (Cascades ccs) fc fd) = 
    concat $ (parMap rpar) (\(Cascade x) -> if length x > 0 then  
                                  getCasToFC (last x) 
                                             (getCascadeIndex (Cascade x) (Cascades ccs)) 
                                             (Game (Cascades ccs) fc fd)
                                  else [])
             ccs

-- given a card and a game, return all possible MoveCasToFC move 
getCasToFC :: Card -> Int -> Game -> [Move]
getCasToFC card casNum (Game _ fc _) =
    if checkFreecell fc > 0 then [MoveCasToFC card casNum] else [] 

-- given a game, return all possible MoveCasToFoun moves 
-- ex: getAllCasToFoun (Game (Cascades [Cascade [Card Ace Spade],Cascade [Card Two Diamond]]) (Freecell []) (Foundation [0,0,0,0]))
getAllCasToFoun :: Game -> [Move]
getAllCasToFoun (Game (Cascades ccs) fc fd) =
    concat $ (parMap rpar) (\(Cascade x) -> if length x > 0 then  
                                  getCasToFoun (last x)
                                               (getCascadeIndex (Cascade x) (Cascades ccs)) 
                                               (Game (Cascades ccs) fc fd)
                                  else [])
             ccs

-- given a card and a game, return all possible CasToFound move 
getCasToFoun :: Card -> Int -> Game -> [Move]
getCasToFoun (Card rank suit) casNum (Game _ _ (Foundation fd)) = 
    if ((fd !! index) == (rankIndex)) then [(MoveCasToFoun (Card rank suit) casNum)] else []
    where Just index = elemIndex suit [minBound..maxBound::Suit]
          Just rankIndex = elemIndex rank [minBound..maxBound::Rank]

-- given a game, get all possible MoveCasToCas moves 
-- ex: getAllCasToCas (Game (Cascades [Cascade [Card Ace Spade],Cascade [Card Two Diamond]]) (Freecell []) (Foundation [0,0,0,0]))
getAllCasToCas :: Game -> [Move]
getAllCasToCas (Game (Cascades ccs) fc fd) =
    concat $ (parMap rpar) (\(Cascade x) -> if length x > 0 then  
                                  getCasToCas (last x)
                                              (getCascadeIndex (Cascade x) (Cascades ccs)) 
                                              (Game (Cascades ccs) fc fd)
                                  else [])
             ccs

-- given a card and a game, return all possible CasToCas move
getCasToCas :: Card -> Int -> Game -> [Move]
getCasToCas card casNum (Game (Cascades ccs) _ _) = 
    do 
        (Cascade cc) <- ccs
        let Just index = elemIndex (Cascade cc) ccs
        guard (length cc > 0 && checkValidNextCard card (last cc) || (length cc == 0))
        return (MoveCasToCas card casNum index)

-- check if the first card can be placed under the second card 
checkValidNextCard :: Card -> Card -> Bool
checkValidNextCard c1 c2 = (not $ checkSameColor c1 c2) && (checkRightRank c2 c1) 

-- check if two cards have the same color
checkSameColor :: Card -> Card -> Bool 
checkSameColor (Card _ s1) (Card _ s2)
    =  (s1 `elem` redSuits && s2 `elem` redSuits) 
    || (s1 `notElem` redSuits && s2 `notElem` redSuits)
        where redSuits = [Heart, Diamond] 

-- check if the first card is one higher than the second
checkRightRank :: Card -> Card -> Bool 
checkRightRank (Card r1 _) (Card r2 _) = ri1 == (ri2 + 1)
                                         where rankList = [minBound..maxBound::Rank]
                                               Just ri1 = elemIndex r1 rankList 
                                               Just ri2 = elemIndex r2 rankList

-- -----------------------------------------------------------------------------
--                              Applying functions
-- -----------------------------------------------------------------------------

-- given a game and a move, apply the move 
applyMove :: Game -> Move -> Game 
applyMove game move =
    case move of 
        MoveCasToCas _ _ _ -> applyMoveCasToCas game move 
        MoveCasToFC _ _ -> applyMoveCasToFC game move 
        MoveCasToFoun _ _ -> applyMoveCasToFoun game move
        MoveMult _ _ _ _-> applyMoveMult game move
        MoveFCToFoun _ -> applyMoveFCToFoun game move
        MoveFCToCas _ _ -> applyMoveFCToCas game move

-- given a game and a MoveMult, apply the move 
-- ex: applyMoveMult (Game (Cascades [Cascade [Card Two Spade, Card Ace Diamond], Cascade []]) (Freecell []) (Foundation [])) (MoveMult (Card Two Spade) (Stack [Card Two Spade, Card Ace Diamond]) 0 1)
applyMoveMult :: Game -> Move -> Game 
applyMoveMult (Game (Cascades ccs) fc fd) move =
    case move of 
        (MoveMult card stack from to) -> 
            (Game (Cascades (addStackToCascades 
                (removeStackFromCascades ccs 0 from card) 0 to stack)) fc fd)
        _ -> error "Wrong move type"

-- given a game adn a MoveCasToCas, apply the move 
applyMoveCasToCas :: Game -> Move -> Game 
applyMoveCasToCas (Game (Cascades ccs) fc fd) move =
    case move of 
        (MoveCasToCas card from to) -> (Game (Cascades 
            (addCardToCascade (removeCardFromCascade ccs 0 from) 0 to card)) fc fd)
        _ -> error "Wrong move type"
    
-- given a game and a MoveCasToFC, apply the move 
applyMoveCasToFC :: Game -> Move -> Game
applyMoveCasToFC (Game (Cascades ccs) (Freecell fc) fd) move =
    case move of 
        (MoveCasToFC card from) -> (Game (Cascades 
            (removeCardFromCascade ccs 0 from)) (Freecell (card:fc)) fd)
        _ -> error "Wrong move type"

-- given a game and a MoveCasToFoun, apply the move 
applyMoveCasToFoun :: Game -> Move -> Game
applyMoveCasToFoun _ (MoveCasToFC _ _) = error "Wrong move type"
applyMoveCasToFoun _ (MoveCasToCas _ _ _)= error "Wrong move type"
applyMoveCasToFoun _ (MoveFCToCas _ _) = error "Wrong move type"
applyMoveCasToFoun _ (MoveMult _ _ _ _) = error "Wrong move type"
applyMoveCasToFoun _ (MoveFCToFoun _) = error "Wrong move type"
applyMoveCasToFoun (Game (Cascades ccs) fc (Foundation fd)) (MoveCasToFoun (Card _ suit) from) =
    (Game (Cascades (removeCardFromCascade ccs 0 from)) fc (Foundation newFd))
    where Just suitIndex = elemIndex suit [minBound..maxBound::Suit]
          newFd = (take suitIndex fd) ++ [(fd !! suitIndex) + 1] ++ (drop (suitIndex + 1) fd)

-- given a game and a MoveFCToFoun, apply the move 
applyMoveFCToFoun :: Game -> Move -> Game 
applyMoveFCToFoun _ (MoveCasToFC _ _) = error "Wrong move type"
applyMoveFCToFoun _ (MoveCasToFoun _ _)= error "Wrong move type"
applyMoveFCToFoun _ (MoveCasToCas _ _ _)= error "Wrong move type"
applyMoveFCToFoun _ (MoveFCToCas _ _)= error "Wrong move type"
applyMoveFCToFoun _ (MoveMult _ _ _ _) = error "Wrong move type"
applyMoveFCToFoun (Game (Cascades ccs) fc (Foundation fd)) (MoveFCToFoun (Card rank suit)) = 
    (Game (Cascades ccs) (removeCardFromFC fc (Card rank suit)) (Foundation newFd) )
    where Just suitIndex = elemIndex suit [minBound..maxBound::Suit]
          newFd = (take suitIndex fd) ++ [(fd !! suitIndex) + 1] ++ (drop (suitIndex + 1) fd)

-- given a game and a MoveFCToCas, apply the move 
applyMoveFCToCas :: Game -> Move -> Game 
applyMoveFCToCas (Game (Cascades ccs) fc fd) move = 
    case move of 
        (MoveFCToCas card to) -> (Game (Cascades (addCardToCascade ccs 0 to card)) (removeCardFromFC fc card) fd)
        _ -> error "Wrong move type"

-- -----------------------------------------------------------------------------
--                            Apply Helper functions
-- -----------------------------------------------------------------------------

-- given a cascades, a current index (which should always start with 0), 
-- a from index, remove the first card from the cascade 
-- ex: removeCardFromCascade [Cascade [Card Ace Spade], Cascade [Card Two Diamond]] 0 1
removeCardFromCascade :: [Cascade] -> Int -> Int -> [Cascade]
removeCardFromCascade [] _ _ = error "removeCardFromCascade: empty cascade"
removeCardFromCascade ((Cascade cc):ccs) cur from 
    | cur < from = (Cascade cc):(removeCardFromCascade ccs (cur + 1) from) 
    | cur == from = (Cascade (init cc)):ccs 
    | otherwise = error "RemoveCardFromCascade error"
    
-- given a cascades, a current index (which should always start with 0), 
-- a to index, and a card, add the card to that cascade 
-- ex: addCardToCascade [Cascade [Card Ace Spade], Cascade [Card Two Diamond]] 0 1 (Card Three Heart)
addCardToCascade :: [Cascade] -> Int -> Int -> Card -> [Cascade]
addCardToCascade [] _ _ _ = error "addCardToCascade: empty cascade"
addCardToCascade ((Cascade cc):ccs) cur to card 
    | cur < to = (Cascade cc):(addCardToCascade ccs (cur + 1) to card)
    | cur == to = (Cascade (cc ++ [card])):ccs 
    | otherwise = error "AddCardToCascade error"

-- given a freecell, a card, remove the card from the freecell 
-- ex: removeCardFromFC (Freecell [Card Ace Spade, Card Two Spade]) (Card Ace Spade)
removeCardFromFC :: Freecell -> Card -> Freecell 
removeCardFromFC (Freecell fc) card = 
    case index of 
        Just i -> (Freecell ((take i fc) ++ (drop (i + 1) fc)))
        Nothing -> error "removeCardFromFC: card was not found"
    where index = elemIndex card fc

-- given a cascades, a current index, a from index, and card 
-- remove all the cards from that stack 
removeStackFromCascades :: [Cascade] -> Int -> Int -> Card -> [Cascade]
removeStackFromCascades [] _ _ _ = error "removeStackFromCascades: empty cascade"
removeStackFromCascades ((Cascade cc):ccs) cur from card 
    | cur < from = (Cascade cc):(removeStackFromCascades ccs (cur + 1) from card)
    | cur == from = (Cascade $ removeStackFromCascade cc card):ccs
    | otherwise = error "removeStackFromCascades: error"

-- given a cascade, a card, return cascade with all cards removed
-- after that card
removeStackFromCascade :: [Card] -> Card -> [Card] 
removeStackFromCascade cc card =
    case index of
        Just i -> (take i cc)
        Nothing -> error "removeStackFromCascade: error"
    where index = elemIndex card cc

addStackToCascades :: [Cascade] -> Int -> Int -> Stack -> [Cascade]
addStackToCascades [] _ _ _ = error "addStackToCascades: empty cascade"
addStackToCascades ((Cascade cc):ccs) cur to (Stack cards) 
    | cur < to = (Cascade cc):(addStackToCascades ccs (cur + 1) to (Stack cards))
    | cur == to = (Cascade (cc ++ cards)):ccs
    | otherwise = error "addStackToCascades: error"

-- -----------------------------------------------------------------------------
--                              Helper functions
-- -----------------------------------------------------------------------------

-- break a list into a list of lists with individual element in a list 
breakListofList :: [a] -> [[a]]
breakListofList a
    | length a == 0 = []
    | otherwise = [head a] : breakListofList (tail a)

-- for printing: combine two list of lists into one list of lists by merging at each index
combineListofListsForPrint :: [[String]] -> [[String]] -> [[String]]
combineListofListsForPrint a b 
    | length a == 0 && length b == 0 = []
    | length a == 0 = (["  "] ++ head b):(combineListofListsForPrint [] (tail b))
    | length b == 0 = (head a ++ ["  "]):(combineListofListsForPrint (tail a) [])
    | otherwise = (head a ++ head b):(combineListofListsForPrint (tail a) (tail b))

-- combine two list of lists into one 
combineListofLists :: [[String]] -> [[String]] -> [[String]]
combineListofLists a b 
    | length a == 0 = b
    | length b == 0 = a
    | otherwise = (head a ++ head b):(combineListofLists (tail a) (tail b))

-- get the index of a cascade 
getCascadeIndex :: Cascade -> Cascades -> Int 
getCascadeIndex cc (Cascades ccs) = 
    case index of 
        Just i -> i 
        Nothing -> error "Should never put in non-existing cascade"
    where index = elemIndex cc ccs

-- get the max length of current cascades 
getMaxLengthCascasde :: Cascades -> Int -> Int
getMaxLengthCascasde (Cascades []) maxLength = maxLength 
getMaxLengthCascasde (Cascades ((Cascade cc):ccs)) maxLength
    | length cc > maxLength = getMaxLengthCascasde (Cascades ccs) (length cc) 
    | otherwise = getMaxLengthCascasde (Cascades ccs) maxLength

-- check if all the cascades are empty 
checkAllCascadeEmpty :: Cascades -> Bool 
checkAllCascadeEmpty (Cascades []) = True 
checkAllCascadeEmpty (Cascades ((Cascade cc):ccs)) 
    | length cc == 0 = checkAllCascadeEmpty (Cascades ccs)
    | otherwise = False