{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies     #-}

module Math.REPL.Evaluator.Cmd (evalCmd) where

--------------------------------------------------------------------------------

import           Math.REPL.Evaluator.Expr      (evalExpr)
import           Math.REPL.Evaluator.Func      (execFunc)
import           Math.REPL.Help                (help)
import           Math.REPL.Prim.Bindings       (Bindings, addFun, addVar,
                                                 display)
import           Math.REPL.Prim.Cmd            (Cmd (..))
import           Math.REPL.Prim.Definitions    (defBinds)
import           Math.REPL.Prim.Expr           (Expr (Message, Constant))
import           Math.REPL.Prim.Function       (testFunc)
import           Math.REPL.Prim.Result         (Result (..))

--------------------------------------------------------------------------------

import           Math.REPL.Prim.Bindings       (getFun)
import           Math.REPL.Prim.Function       (Function, apply, arity)

import           Graphics.Rendering.Plot.Gtk.UI (plotWithArity)
import           Graphics.UI.Gtk                (initGUI, mainGUI)

import           Data.List                      (foldl1')
import           Data.Maybe

--------------------------------------------------------------------------------

evalCmd :: Bindings -> Cmd -> Result
evalCmd _ Help    = Text . unlines $ help
evalCmd b Display = Text . unlines . display $ b
evalCmd _ Reset   = NewBindings defBinds
evalCmd b (Assign s e)   =
    case evalExpr b e of
      Message xxs -> Error . unlines $ xxs
      Constant c  -> NewBindings . addVar (s, c) $ b
      _           -> Error " ~~ Erroneous Result ~~ "
evalCmd b fun@(Func i _ _) = let f = execFunc b fun
                             in case testFunc f 0 of
                                  Nothing -> NewBindings . addFun (i, f) $ b
                                  Just ms -> Error . unlines $ ms

--------------------------------------------------------------------------------

evalCmd b (Plot s rs) =
    case getFun s b of
      Nothing -> Error $ "Unknown function: " ++ s
      Just f  -> let pairMap fn (x, y) = (fn x, fn y)
                     rangeExps = map (pairMap (evalExpr b)) rs
                     rangeErrs = map (uncurry grabMessage) rangeExps
                     allErrors = foldl1' grabMessage rangeErrs
                     ranges    = map (pairMap (\(Constant c) -> c)) rangeExps
                     fromMsg   = (\(Message ms) -> ms)
                 in if null . fromMsg $ allErrors
                    then plotFunction f ranges
                    else Error . unlines . fromMsg $ allErrors

grabMessage :: Expr -> Expr -> Expr
grabMessage (Message m1)   (Message m2) = Message (m1 ++ m2)
grabMessage m1@(Message _) _            = m1
grabMessage _            m2@(Message _) = m2
grabMessage _              _            = Message []

plotFunction :: Function -> [(Double, Double)] -> Result
plotFunction f ranges =
    if length ranges /= arity f
    then Error $ "Invalid parameters: " ++
             "Required " ++ show (arity f) ++ " range(s). " ++
             "Provided " ++ show (length ranges) ++ "."
    else Action $ do
      _ <- initGUI
      fromJust $ plotWithArity (arity f) ((\(Right x) -> x) . apply f) ranges
      mainGUI

--------------------------------------------------------------------------------
