{-# LANGUAGE ViewPatterns, RankNTypes, ScopedTypeVariables #-}
module Graphics.Implicit.ExtOpenScad.Util.ArgParser where

import Graphics.Implicit.Definitions
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Util.OVal
import qualified Control.Exception as Ex
import qualified Data.Map   as Map
import qualified Data.Maybe as Maybe
import Control.Monad

instance Monad ArgParser where

	-- return is easy: if we want an ArgParser that just gives us a, that is 
	-- ArgParserTerminator a
	return a = APTerminator a

	-- Now things get more interesting. We need to describe how (>>=) works.
	-- Let's get the hard ones out of the way first.
	-- ArgParser actually 
	(AP str fallback doc f) >>= g = AP str fallback doc (\a -> (f a) >>= g)
	(APFailIf b errmsg child) >>= g = APFailIf b errmsg (child >>= g)
	-- These next to is easy, they just pass the work along to their child
	(APExample str child) >>= g = APExample str (child >>= g)
	(APTest str tests child) >>= g = APTest str tests (child >>= g)
	-- And an ArgParserTerminator happily gives away the value it contains
	(APTerminator a) >>= g = g a
	(APBranch bs) >>= g = APBranch $ map (>>= g) bs

instance MonadPlus ArgParser where
	mzero = APFailIf True "" undefined
	mplus (APBranch as) (APBranch bs) = APBranch ( as  ++  bs )
	mplus (APBranch as) b             = APBranch ( as  ++ [b] )
	mplus a             (APBranch bs) = APBranch ( [a] ++  bs )
	mplus a             b             = APBranch [ a   ,   b  ]

-- * ArgParser building functions

-- ** argument and combinators

argument :: forall desiredType. (OTypeMirror desiredType) => String -> ArgParser desiredType
argument name = 
	AP name Nothing "" $ \oObjVal -> do
		let
			val = fromOObj oObjVal :: Maybe desiredType
			errmsg = case oObjVal of
				OError errs -> "error in computing value for arugment " ++ name
				             ++ ": " ++ concat errs
				_   ->  "arg " ++ show oObjVal ++ " not compatible with " ++ name
		-- Using /= Nothing would require Eq desiredType
		APFailIf (Maybe.isNothing val) errmsg $ APTerminator $ (\(Just a) -> a) val

doc (AP name defMaybeVal _ next) newDoc = AP name defMaybeVal newDoc next

defaultTo :: forall a. (OTypeMirror a) => ArgParser a -> a -> ArgParser a
defaultTo (AP name oldDefMaybeVal doc next) newDefVal = 
	AP name (Just $ toOObj newDefVal) doc next

-- ** example

example :: String -> ArgParser ()
example str = APExample str (return ())

-- * test and combinators

test :: String -> ArgParser ()
test str = APTest str [] (return ())

eulerCharacteristic :: ArgParser a -> Int -> ArgParser a
eulerCharacteristic (APTest str tests child) χ =
	APTest str ((EulerCharacteristic χ) : tests) child

-- * Tools for handeling ArgParsers

-- | Apply arguments to an ArgParser

argMap :: 
	[(Maybe String,  OVal)]      -- ^ arguments
	-> ArgParser a              -- ^ ArgParser to apply them to
	-> (Maybe a, [String])      -- ^ (result, error messages)

argMap args = argMap2 unnamedArgs (Map.fromList namedArgs) where
	unnamedArgs = map snd $ filter (Maybe.isNothing . fst) args
	namedArgs   = map (\(a,b) -> (Maybe.fromJust a, b)) $ filter (Maybe.isJust . fst) args


argMap2 :: [OVal] -> Map.Map String OVal -> ArgParser a -> (Maybe a, [String])

argMap2 uArgs nArgs (APBranch branches) =
	foldl1 merge solutions where
		solutions = map (argMap2 uArgs nArgs) branches
		merge a@(Just _, []) _ = a
		merge _ b@(Just _, []) = b
		merge a@(Just _, _) _ = a
		merge (Nothing, _)  a = a

argMap2 unnamedArgs namedArgs (AP name fallback _ f) = 
	case Map.lookup name namedArgs of
		Just a -> argMap2 
			unnamedArgs 
			(Map.delete name namedArgs) 
			(f a)
		Nothing -> case unnamedArgs of
			x:xs -> argMap2 xs namedArgs (f x)
			[]   -> case fallback of
				Just b  -> argMap2 [] namedArgs (f b)
				Nothing -> (Nothing, ["No value and no default for argument " ++ name])

argMap2 a b (APTerminator val) = 
	(Just val,
		if not (null a && Map.null b)
		then ["unused arguments"]
		else []
	)

argMap2 a b (APFailIf test err child) = 
	if test 
	then (Nothing, [err])
	else argMap2 a b child

argMap2 a b (APExample str child) = argMap2 a b child

argMap2 a b (APTest str tests child) = argMap2 a b child


{-
-- | We need a format to extract documentation into
data Doc = Doc String [DocPart]
             deriving (Show)

data DocPart = ExampleDoc String
             | ArgumentDoc String (Maybe String) String
             deriving (Show)


--   Here there be dragons!
--   Because we made this a Monad instead of applicative functor, there's now sane way to do this.
--   We give undefined (= an error) and let laziness prevent if from ever being touched.
--   We're using IO so that we can catch an error if this backfires.
--   If so, we *back off*.

-- | Extract Documentation from an ArgParser

getArgParserDocs :: 
	(ArgParser a)    -- ^ ArgParser
	-> IO [DocPart]  -- ^ Docs (sadly IO wrapped)

getArgParserDocs (ArgParser name fallback doc fnext) = 
	do
		otherDocs <- Ex.catch (getArgParserDocs $ fnext undefined) (\(e :: Ex.SomeException) -> return [])
		return $ (ArgumentDoc name (fmap show fallback) doc):otherDocs

getArgParserDocs (ArgParserExample str child) =
	do
		childResults <- getArgParserDocs child
		return $ (ExampleDoc str) : childResults

-- We try to look at as little as possible, to avoid the risk of triggering an error.
-- Yay laziness!

getArgParserDocs (ArgParserTest   _ _ child ) = getArgParserDocs child
getArgParserDocs (ArgParserFailIf _ _ child ) = getArgParserDocs child

-- To look at this one would almost certainly be death (exception)
getArgParserDocs (ArgParserTerminator _ ) = return []

-}