-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE

-- We'd like to parse openscad code, with some improvements, for backwards compatability.

module Graphics.Implicit.ExtOpenScad.Default where

import Graphics.Implicit.Definitions
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Util.OVal
import Graphics.Implicit.ExtOpenScad.Primitives
import Data.Map (Map, fromList)

defaultObjects :: VarLookup -- = Map String OVal
defaultObjects = fromList $ 
	defaultConstants
	++ defaultFunctions
	++ defaultFunctions2
	++ defaultFunctionsSpecial
	++ defaultModules
	++ defaultPolymorphicFunctions

-- Missing standard ones:
-- rand, lookup, 

defaultConstants = map (\(a,b) -> (a, toOObj (b::) ))
	[("pi", pi)]

defaultFunctions = map (\(a,b) -> (a, toOObj ( b ::  -> )))
	[
		("sin",   sin),
		("cos",   cos),
		("tan",   tan),
		("asin",  asin),
		("acos",  acos),
		("atan",  atan),
		("sinh",  sinh),
		("cosh",  cosh),
		("tanh",  tanh),
		("abs",   abs),
		("sign",  signum),
		("floor", fromIntegral . floor ),
		("ceil",  fromIntegral . ceiling ),
		("round", fromIntegral . round ),
		("exp",   exp),
		("ln",    log),
		("log",   log),
		("sign",  signum),
		("sqrt",  sqrt)
	]

defaultFunctions2 = map (\(a,b) -> (a, toOObj (b ::  ->  -> ) ))
	[
		("max", max),
		("min", min),
		("atan2", atan2),
		("pow", (**))
	]

defaultFunctionsSpecial = 
	[
		("map", toOObj $ flip $ 
			(map :: (OVal -> OVal) -> [OVal] -> [OVal] ) 
		)
		
	]


defaultModules =
	map (\(a,b) -> (a, OModule b)) primitives



-- more complicated ones:

defaultPolymorphicFunctions = 
	[ 
		("+", sum),
		("sum", sum),
		("*", prod),
		("prod", prod),
		("/", div),
		("-", toOObj sub), 
		("^", toOObj ((**) ::  ->  -> )), 
		("negate", toOObj negate),
		("index", toOObj index),
		("splice", toOObj osplice),
		("<", toOObj  ((<) ::  ->  -> Bool) ),
		(">", toOObj  ((>) ::  ->  -> Bool) ),
		(">=", toOObj ((>=) ::  ->  -> Bool) ),
		("<=", toOObj ((<=) ::  ->  -> Bool) ),
		("==", toOObj ((==) :: OVal -> OVal -> Bool) ),
		("!=", toOObj ((/=) :: OVal -> OVal -> Bool) ),
		("?", toOObj ( ternary :: Bool -> OVal -> OVal -> OVal) ),
		("&&", toOObj (&&) ),
		("||", toOObj (||) ),
		("!", toOObj not ),
		("list_gen", toOObj list_gen),
		("++", concat),
		("len", toOObj olength),
		("str", toOObj (show :: OVal -> String))
	] where

		-- Some key functions are written as OVals in optimizations attempts

		prod = OFunc $ \x -> case x of
			(OList (x:xs)) -> foldl mult x xs
			(OList [])     -> ONum 1
			a              -> OError ["Product takes a list"]

		mult (ONum a)  (ONum b)  = ONum  (a*b)
		mult (ONum a)  (OList b) = OList (map (mult (ONum a)) b)
		mult (OList a) (ONum b)  = OList (map (mult (ONum b)) a)
		mult a         b         = errorAsAppropriate "multiply" a b

		div = OFunc $ \x -> case x of
			(ONum a) -> OFunc $ \y -> case y of
				(ONum b) -> ONum (a/b)
				b        -> errorAsAppropriate "divide" (ONum a) b
			a -> OFunc $ \y -> case y of
				b -> div' a b

		div' (ONum a)  (ONum b) = ONum  (a/b)
		div' (OList a) (ONum b) = OList (map (\x -> div' x (ONum b)) a)
		div' a         b        = errorAsAppropriate "divide" a b

		omod (ONum a) (ONum b) = ONum $ fromIntegral $ mod (floor a) (floor b)
		omod a        b        = errorAsAppropriate "modulo" a b

		append (OList   a) (OList   b) = OList   $ a++b
		append (OString a) (OString b) = OString $ a++b
		append a           b           = errorAsAppropriate "append" a b

		concat = OFunc $ \x -> case x of
			(OList (x:xs)) -> foldl append x xs
			(OList [])     -> OList []
			_              -> OError ["concat takes a list"]

		sum = OFunc $ \x -> case x of
			(OList (x:xs)) -> foldl add x xs
			(OList [])     -> ONum 0
			a              -> OError ["Product takes a list"]

		add (ONum a) (ONum b) = ONum (a+b)
		add (OList a) (OList b) = OList $ zipWith add a b
		add a b = errorAsAppropriate "add" a b

		sub (ONum a) (ONum b) = ONum (a-b)
		sub (OList a) (OList b) = OList $ zipWith sub a b
		sub a b = errorAsAppropriate "subtract" a b

		negate (ONum n) = ONum (-n)
		negate (OList l) = OList $ map negate l
		negate a = OError ["Can't negate " ++ oTypeStr a ++ "(" ++ show a ++ ")"]

		{-numCompareToExprCompare :: (ℝ -> ℝ -> Bool) -> Oval -> OVal -> Bool
		numCompareToExprCompare f a b =
			case (fromOObj a :: Maybe ℝ, fromOObj b :: Maybe ℝ) of
				(Just a, Just b) -> f a b
				_ -> False-}

		index (OList l) (ONum ind) = 
			let n = floor ind 
			in if n < length l then l !! n else OError ["List accessd out of bounds"]
		index (OString s) (ONum ind) = 
			let n = floor ind 
			in if n < length s then OString [s !! n] else OError ["List accessd out of bounds"]
		index a b = errorAsAppropriate "index" a b

		osplice (OList  list) (ONum a) (    ONum b    ) = 
			OList   $ splice list (floor a) (floor b)
		osplice (OString str) (ONum a) (    ONum b    ) = 
			OString $ splice str  (floor a) (floor b)
		osplice (OList  list) (OUndefined) (ONum b    ) = 
			OList   $ splice list 0 (floor b)
		osplice (OString str) (OUndefined) (ONum b    ) = 
			OString $ splice str  0 (floor b)
		osplice (OList  list) (ONum a) (    OUndefined) = 
			OList   $ splice list (floor a) (length list + 1)
		osplice (OString str) (ONum a) (    OUndefined) = 
			OString $ splice str  (floor a) (length str  + 1)
		osplice (OList  list) (OUndefined) (OUndefined) = 
			OList   $ splice list 0 (length list + 1)
		osplice (OString str) (OUndefined) (OUndefined) = 
			OString $ splice str  0 (length str  + 1)
		osplice _ _ _ = OUndefined

		splice :: [a] -> Int -> Int -> [a]
		splice [] _ _     = []
		splice (l@(x:xs)) a b 
			|    a < 0  =    splice l   (a+n)  b
			|    b < 0  =    splice l    a    (b+n)
			|    a > 0  =    splice xs  (a-1) (b-1)
			|    b > 0  = x:(splice xs   a    (b-1) )
			| otherwise = []
					where n = length l

		errorAsAppropriate _   err@(OError _)   _ = err
		errorAsAppropriate _   _   err@(OError _) = err
		errorAsAppropriate name a b = OError 
			["Can't " ++ name ++ " objects of types " ++ oTypeStr a ++ " and " ++ oTypeStr b ++ "."]

		list_gen :: [] -> Maybe []
		list_gen [a,b] = Just [fromIntegral (ceiling a).. fromIntegral (floor b)]
		list_gen [a, b, c] =
			let
				nr = (c-a)/b
				n  = fromIntegral (floor nr)
			in if nr - n > 0
			then Just 
				[fromIntegral (ceiling a), fromIntegral (ceiling (a+b)).. fromIntegral (floor (c - b*(nr -n)))]
			else Just 
				[fromIntegral (ceiling a), fromIntegral (ceiling (a+b)).. fromIntegral (floor c)]
		list_gen _ = Nothing

		ternary True a b = a
		ternary False a b = b

		olegnth (OString s) = ONum $ fromIntegral $ length s
		olength (OList s)   = ONum $ fromIntegral $ length s
		olength a           = OError ["Can't take length of a " ++ oTypeStr a ++ "."]