{-# LANGUAGE ViewPatterns, ScopedTypeVariables #-}

module Graphics.Implicit.ExtOpenScad.Eval.Statement where

import Graphics.Implicit.Definitions
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Util.OVal
import Graphics.Implicit.ExtOpenScad.Util.ArgParser
import Graphics.Implicit.ExtOpenScad.Util.StateC
import Graphics.Implicit.ExtOpenScad.Eval.Expr
import Graphics.Implicit.ExtOpenScad.Parser.Statement (parseProgram)


import qualified Data.Maybe as Maybe
import qualified Data.List as List
import qualified Data.Map as Map
import           Data.Map (Map)
import qualified Control.Monad as Monad
import qualified Control.Monad.State as State
import           Control.Monad.State (State,StateT, get, put, modify, liftIO)
import qualified System.FilePath as FilePath


runStatementI :: StatementI -> StateC ()

runStatementI (StatementI lineN (pat := expr)) = do
	val <- evalExpr expr
	let posMatch = matchPat pat val
	case (getErrors val, posMatch) of
		(Just err,  _ ) -> errorC lineN err
		(_, Just match) -> modifyVarLookup $ Map.union match
		(_,   Nothing ) -> errorC lineN "pattern match failed in assignment"

runStatementI (StatementI lineN (Echo exprs)) = do
	let
		show2 (OString s) = s
		show2 x = show x
	vals <- mapM evalExpr exprs
	case getErrors (OList vals) of
		Nothing  -> liftIO $ putStrLn $ concat $ map show2 vals
		Just err -> errorC lineN err

runStatementI (StatementI lineN (For pat expr loopContent)) = do
	val <- evalExpr expr
	case (getErrors val, val) of
		(Just err, _)      -> errorC lineN err
		(_, OList vals) -> Monad.forM_ vals $ \v ->
			case matchPat pat v of
				Just match -> do
					modifyVarLookup $ Map.union match
					runSuite loopContent
				Nothing -> return ()
		_ -> return ()

runStatementI (StatementI lineN (If expr a b)) = do
	val <- evalExpr expr
	case (getErrors val, val) of
		(Just err,  _  )  -> errorC lineN ("In conditional expression of if statement: " ++ err)
		(_, OBool True )  -> runSuite a
		(_, OBool False)  -> runSuite b
		_                 -> return ()

runStatementI (StatementI lineN (NewModule name argTemplate suite)) = do
	argTemplate' <- Monad.forM argTemplate $ \(name, defexpr) -> do
		defval <- mapMaybeM evalExpr defexpr 
		return (name, defval)
	(varlookup, _, path, _, _) <- get
	runStatementI $ StatementI lineN $ (Name name :=) $ LitE $ OModule $ \vals -> do 
		newNameVals <- Monad.forM argTemplate' $ \(name, maybeDef) -> do
			val <- case maybeDef of
				Just def -> argument name `defaultTo` def
				Nothing  -> argument name
			return (name, val)
		let
			children = ONum $ fromIntegral $ length vals
			child = OModule $ \vals -> do
				n ::  <- argument "n";
				return $ return $ return $ 
					if n <= length vals
						then vals !! n
						else OUndefined
			childBox = OFunc $ \n -> case fromOObj n :: Maybe  of
				Just n  | n < length vals -> case vals !! n of
					-- _ -> toOObj $ getBox3 obj3
					-- _ -> toOObj $ getBox2 obj2
					_ -> OUndefined
				_ -> OUndefined
			newNameVals' = newNameVals ++ [("children", children),("child", child), ("childBox", childBox)]
			varlookup' = Map.union (Map.fromList newNameVals) varlookup
			suiteVals  = runSuiteCapture varlookup' path suite
		return suiteVals

runStatementI (StatementI lineN (ModuleCall name argsExpr suite)) = do
		maybeMod  <- lookupVar name
		(varlookup, _, path, _, _) <- get
		childVals <- fmap reverse $ liftIO $ runSuiteCapture varlookup path suite
		argsVal   <- Monad.forM argsExpr $ \(posName, expr) -> do
			val <- evalExpr expr
			return (posName, val)
		newVals <- case maybeMod of
			Just (OModule mod) -> liftIO ioNewVals  where
				argparser = mod childVals
				ioNewVals = case fst $ argMap argsVal argparser of
					Just iovals -> iovals
					Nothing     -> return []
			Just foo            -> do
					case getErrors foo of
						Just err -> errorC lineN err
						Nothing  -> errorC lineN $ "Object called not module!"
					return []
			Nothing -> do
				errorC lineN $ "Module " ++ name ++ " not in scope."
				return []
		pushVals newVals

runStatementI (StatementI lineN (Include name injectVals)) = do
	name' <- getRelPath name
	content <- liftIO $ readFile name'
	case parseProgram name content of
		Left e -> liftIO $ putStrLn $ "Error parsing " ++ name ++ ":" ++ show e
		Right sts -> withPathShiftedBy (FilePath.takeDirectory name) $ do
			vals <- getVals
			putVals []
			runSuite sts
			vals' <- getVals
			if injectVals then putVals (vals' ++ vals) else putVals vals



runSuite :: [StatementI] -> StateC ()
runSuite stmts = Monad.mapM_ runStatementI stmts

runSuiteCapture :: VarLookup -> FilePath -> [StatementI] -> IO [OVal]
runSuiteCapture varlookup path suite = do
	(res, state) <- State.runStateT 
		(runSuite suite >> getVals)
		(varlookup, [], path, (), () )
	return res