{-# LANGUAGE ViewPatterns, RankNTypes, ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances, OverlappingInstances  #-}

module Graphics.Implicit.ExtOpenScad.Util.OVal where

import Graphics.Implicit.Definitions
import Graphics.Implicit.ExtOpenScad.Definitions
import qualified Control.Monad as Monad
import Data.Maybe (isJust)

-- | We'd like to be able to turn OVals into a given Haskell type
class OTypeMirror a where
	fromOObj :: OVal -> Maybe a
	toOObj :: a -> OVal

instance OTypeMirror OVal where
	fromOObj a = Just a
	toOObj a = a

instance OTypeMirror  where
	fromOObj (ONum n) = Just n
	fromOObj _ = Nothing
	toOObj n = ONum n

instance OTypeMirror  where
	fromOObj (ONum n) = if n == fromIntegral (floor n) then Just (floor n) else Nothing
	fromOObj _ = Nothing
	toOObj n = ONum $ fromIntegral n

instance OTypeMirror Bool where
	fromOObj (OBool b) = Just b
	fromOObj _ = Nothing
	toOObj b = OBool b

instance OTypeMirror String where
	fromOObj (OString str) = Just str
	fromOObj _ = Nothing
	toOObj str = OString str

instance forall a. (OTypeMirror a) => OTypeMirror (Maybe a) where
	fromOObj a = Just $ fromOObj a
	toOObj (Just a) = toOObj a
	toOObj Nothing  = OUndefined

instance forall a. (OTypeMirror a) => OTypeMirror [a] where
	fromOObj (OList list) = Monad.sequence . map fromOObj $ list
	fromOObj _ = Nothing
	toOObj list = OList $ map toOObj list

instance forall a b. (OTypeMirror a, OTypeMirror b) => OTypeMirror (a,b) where
	fromOObj (OList ((fromOObj -> Just a):(fromOObj -> Just b):[])) = Just (a,b)
	fromOObj _ = Nothing
	toOObj (a,b) = OList [toOObj a, toOObj b]


instance forall a b c. (OTypeMirror a, OTypeMirror b, OTypeMirror c) => OTypeMirror (a,b,c) where
	fromOObj (OList ((fromOObj -> Just a):(fromOObj -> Just b):(fromOObj -> Just c):[])) = 
		Just (a,b,c)
	fromOObj _ = Nothing
	toOObj (a,b,c) = OList [toOObj a, toOObj b, toOObj c]

instance forall a b. (OTypeMirror a, OTypeMirror b) => OTypeMirror (a -> b) where
	fromOObj (OFunc f) =  Just $ \input ->
		let
			oInput = toOObj input
			oOutput = f oInput
			output = fromOObj oOutput :: Maybe b
		in case output of
			Just out -> out
			Nothing -> error $ "coercing OVal to a -> b isn't always safe; use a -> Maybe b"
			              ++ " (trace: " ++ show oInput ++ " -> " ++ show oOutput ++ " )"
	fromOObj _ = Nothing
	toOObj f = OFunc $ \oObj -> 
		case fromOObj oObj :: Maybe a of
			Nothing  -> OError ["bad input type"]
			Just obj -> toOObj $ f obj


instance forall a b. (OTypeMirror a, OTypeMirror b) => OTypeMirror (Either a b) where
	fromOObj (fromOObj -> Just (x :: a)) = Just $ Left  x
	fromOObj (fromOObj -> Just (x :: b)) = Just $ Right x
	fromOObj _ = Nothing

	toOObj (Right x) = toOObj x
	toOObj (Left  x) = toOObj x

oTypeStr (OUndefined) = "Undefined"
oTypeStr (OBool   _ ) = "Bool"
oTypeStr (ONum    _ ) = "Number"
oTypeStr (OList   _ ) = "List"
oTypeStr (OString _ ) = "String"
oTypeStr (OFunc   _ ) = "Function"
oTypeStr (OModule _ ) = "Module"
oTypeStr (OError  _ ) = "Error"

getErrors :: OVal -> Maybe String
getErrors (OError er) = Just $ head er
getErrors (OList l)   = Monad.msum $ map getErrors l
getErrors _           = Nothing


type Any = OVal

caseOType = flip ($)

infixr 2 <||>

(<||>) :: forall desiredType out. (OTypeMirror desiredType)
	=> (desiredType -> out) 
	-> (OVal -> out)
	-> (OVal -> out)

(<||>) f g = \input ->
	let
		coerceAttempt = fromOObj input :: Maybe desiredType
	in 
		if isJust coerceAttempt -- ≅ (/= Nothing) but no Eq req
		then f $ (\(Just a) -> a) coerceAttempt
		else g input


divideObjs children = 
	(map fromOObj2 . filter isOObj2 $ children,
	 map fromOObj3 . filter isOObj3 $ children,
	 filter (not . isOObj)          $ children)
		where
			isOObj2 (OObj2 _) = True
			isOObj2    _      = False
			isOObj3 (OObj3 _) = True
			isOObj3    _      = False
			isOObj  (OObj2 _) = True
			isOObj  (OObj3 _) = True
			isOObj     _      = False
			fromOObj2 (OObj2 x) = x
			fromOObj3 (OObj3 x) = x