{-|
Module: UserInterface
Description: Provides various functions for building the user interface.
Copyright: (c) Marcel Moosbrugger, 2017
License     : MIT

Provides various functions for building the user interface of the
sudoku application.
-}
module UserInterface (
      BuilderCastException(..)
    , SudokuUI
    , window
    , menu
    , gameButtons
    , cells
    , popover
    , numberButtons
    , inputClear
    , inputSolve
    , solveButton
    , checkButton
    , menuButton
    , buildSudokuUI
    , writePopoverRelativeCell
    , solveAll
    , solvePopoverRelativeCell
    , checkAll
    , cellsBindHandlers
    , numbersBindHandlers
    , gameButtonsBindHandlers
    , showMenu
    ) where

import           Control.Concurrent     (forkIO, threadDelay)
import           Control.Exception
import           Control.Monad.IO.Class
import           Data.GI.Base
import qualified Data.Text              as T
import           Data.Typeable
import           GI.Gtk
import           Paths_hsudoku
import           Sudoku.Loader
import           Sudoku.Solver
import           Sudoku.Type

-- | Thrown when 'castB' fails get an object.
data BuilderCastException = UnknownIdException String deriving (Show, Typeable)

instance Exception BuilderCastException

-- | Alias type for a sudoku cell
type Cell = Button
-- | Alias type for all sudoku cells
type Cells = [Cell]
-- | Alias type for the game menu
type GameMenu = Widget

-- | A type containing all handles of widgets necessary for the user interface.
data SudokuUI = SudokuUI { window        :: Window
                         , menu          :: GameMenu
                         , gameButtons   :: [Button]
                         , cells         :: Cells
                         , popover       :: Popover
                         , numberButtons :: [Button]
                         , inputClear    :: Button
                         , inputSolve    :: Button
                         , solveButton   :: Button
                         , checkButton   :: Button
                         , menuButton    :: Button
                         }


-- | Builds the sudoku-ui from a gui-file for which the path is given.
buildSudokuUI :: IO SudokuUI
buildSudokuUI = do
    uiFile            <- T.pack <$> getDataFileName "gui/hsudoku.ui"
    (window, builder) <- buildMainWindow "mainWindow" uiFile
    menu              <- builderGetTyped builder "menu" Widget
    gameButtons       <- builderGetsTyped builder gameButtonNames Button
    cells             <- builderGetsTyped builder cellNames Button
    popover           <- builderGetTyped builder "inputPopover" Popover
    numberButtons     <- builderGetsTyped builder numberNames Button
    inputClear        <- builderGetTyped builder "inputClear" Button
    inputSolve        <- builderGetTyped builder "inputSolve" Button
    solveButton       <- builderGetTyped builder "solveButton" Button
    checkButton       <- builderGetTyped builder "checkButton" Button
    menuButton        <- builderGetTyped builder "menuButton" Button
    pure $ SudokuUI window menu gameButtons cells popover
                    numberButtons inputClear inputSolve solveButton
                    checkButton menuButton

-- | The ids of the sudoku cells in the ui file.
cellNames :: [T.Text]
cellNames = map (T.pack . (++) "cell") $ map show [1..81]

-- | The ids of the inputs for the numbers in the ui file.
numberNames :: [T.Text]
numberNames = map (T.pack . (++) "input") $ map show [1..9]

-- | The ids of the buttons which start a new game
gameButtonNames :: [T.Text]
gameButtonNames = map (T.pack . (++) "game" . show) [Easy ..]

-- | Takes a builder and returns the object with a given name
--   typed as a given gtype.
builderGetTyped :: (IsBuilder a, GObject o, MonadIO m) => a -> T.Text -> (ManagedPtr o -> o) -> m o
builderGetTyped builder ident gtype =
    liftIO $ do
        o <- builderGetObject builder ident
        case o of
            Just a  -> unsafeCastTo gtype a
            Nothing -> throw $ UnknownIdException $ T.unpack ident

-- | Same as builderGetTyped for a list of names.
builderGetsTyped :: (GObject a, IsBuilder b, MonadIO m) => b -> [T.Text] -> (ManagedPtr a -> a) -> m [a]
builderGetsTyped b is t = sequence $ map (\i -> builderGetTyped b i t) is

-- | Builds the main application window from a xml definition file for which the
--   path is given.
buildMainWindow :: MonadIO m => T.Text -> T.Text -> m (Window, Builder)
buildMainWindow name path = liftIO $ do
    builder <- builderNewFromFile path
    window  <- builderGetTyped builder name Window
    on window #destroy mainQuit
    cssFile <- T.pack <$> getDataFileName "gui/theme.css"
    windowAddCss window cssFile
    pure (window, builder)

-- | Adds to a given window a css file for which the path is given.
windowAddCss :: (MonadIO m, IsWindow a) => a -> T.Text -> m ()
windowAddCss window path = liftIO $ do
    screen <- windowGetScreen window
    cssProvider <- cssProviderNew
    cssProviderLoadFromPath cssProvider path
    styleContextAddProviderForScreen screen cssProvider 1000

-- | Writes a character into a sudoku cell.
writeCell :: Cell -> Char -> IO ()
writeCell cell char = #setLabel cell (T.singleton char)

-- | Writes a charachter into a cell which is associated to a given popover
--   The popover gets closed afterwards.
writePopoverRelativeCell :: Popover -> Char -> IO ()
writePopoverRelativeCell popover char = do
    widget <- #getRelativeTo popover
    cell   <- unsafeCastTo Button widget
    writeCell cell char
    #hide popover

-- | Solves a given cell.
solveCell :: Cell -> IO ()
solveCell cell = do
    char <- T.head <$> #getName cell
    writeCell cell char

-- | Solves all given cells.
solveAll :: Cells -> IO ()
solveAll = mapM_ solveCell

-- | Solves the cell currently relative to the popover.
solvePopoverRelativeCell :: Popover -> IO ()
solvePopoverRelativeCell popover = do
    cell <- #getRelativeTo popover >>= unsafeCastTo Button
    solveCell cell
    #hide popover

-- | Binds the signal handlers to buttons.
cellsBindHandlers :: Cells -> Popover -> IO ()
cellsBindHandlers cells popover = mapM_ (\c -> do
            on c #focusInEvent  $ focusInHandler c
        ) cells
    where focusInHandler c _ = do cellShowPopover c popover; pure False

-- | Checks and returns if a given cell contains the correct value.
--   If the value is not correct the cell gets visually marked.
checkCell :: Cell -> IO Bool
checkCell cell = do
    solution <- T.head <$> (toWidget cell >>= #getName)
    actual <- T.head <$> #getLabel cell
    let isCorrect = actual == solution
    style <- #getStyleContext cell
    if not isCorrect
        then #addClass style "incorrect"
        else pure ()
    forkIO $ threadDelay 800000 >> #removeClass style "incorrect"
    pure isCorrect

-- | Checks if all given cells contain the correct value.
--   Visually marks the correct or incorrect cells.
checkAll :: Cells -> IO ()
checkAll cells = do
    allAreCorrect <- and <$> mapM checkCell cells
    if allAreCorrect
        then mapM_ (\cell -> do
            style <- #getStyleContext cell
            #addClass style "correct"
            forkIO $ threadDelay 800000 >> #removeClass style "correct"
        ) cells
        else pure ()

-- | Associates the popover to a given button and shows the popover.
cellShowPopover :: Cell -> Popover -> IO ()
cellShowPopover cell popover = do
    popover `set` [#relativeTo := cell]
    #show popover

-- | Binds the signal handlers to a list of number buttons.
numbersBindHandlers :: [Button] -> Popover -> IO ()
numbersBindHandlers buttons popover = mapM_ (\b -> do
            on b #clicked $ numberButtonInsert b popover
        ) buttons

-- | Inserts the content of a number button to a cell associated to the popover.
numberButtonInsert :: Button -> Popover -> IO ()
numberButtonInsert button popover = do
    label <- #getLabel button
    writePopoverRelativeCell popover $ T.head label

-- | Writes a sudoku into a list of buttons.
writeSudoku :: Cells -> Sudoku -> IO ()
writeSudoku cells sudoku = do
    let sudokuChars = toString sudoku
    sequence_ $ zipWith (\c sc -> do
            writeCell c sc
            if sc == blankval
                then c `set` [#sensitive := True]
                else c `set` [#sensitive := False]
        ) cells sudokuChars

-- | Stores a given solution in the names of the passed cells.
writeSolution :: Cells -> Sudoku -> IO ()
writeSolution cells sudoku = do
    let sudokuChars = toString sudoku
    sequence_ $ zipWith (\c sc -> do
            #setName c (T.singleton sc)
        ) cells sudokuChars

-- | Binds the signal handlers to the game buttons in the menu.
gameButtonsBindHandlers :: [Button] -> Cells -> Widget -> IO ()
gameButtonsBindHandlers buttons cells menu = do
    mapM_ (\button -> do
            label <- #getLabel button
            let d = read . T.unpack $ label
            on button #clicked $ newGame d cells menu
        ) buttons

-- | Prepares a new game in the UI.
newGame :: Difficulty -> Cells -> GameMenu -> IO ()
newGame d cells menu = do
    Just sudoku <- loadSudoku d
    let Just solution = head <$> solveSudoku sudoku
    writeSudoku cells sudoku
    writeSolution cells solution
    #hide menu

-- | Shows the menu and ensures that the popover is hidden.
showMenu :: GameMenu -> Popover -> IO ()
showMenu menu popover = do
    #hide popover
    popover `set` [#relativeTo := menu]
    #show menu