module Graphics.Implicit.ExtOpenScad.Parser.Expr where

import Graphics.Implicit.Definitions
import Text.ParserCombinators.Parsec  hiding (State)
import Text.ParserCombinators.Parsec.Expr
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Parser.Util

variable :: GenParser Char st Expr
variable = fmap Var variableSymb

literal :: GenParser Char st Expr
literal = 
	try ( (string "true" >> return (LitE $ OBool True) )
		<|> (string "false" >> return (LitE $ OBool False) )
		<?> "boolean" )
	<|> try ( try (do
			a <- many1 digit
			char '.'
			b <- many digit
			return $ LitE $ ONum (read (a ++ "." ++ b) :: )
		) <|>  (do
			a <- many1 digit
			return $ LitE $ ONum (read a :: )
		) <?> "number" )
	<|> try ( ( do
		string "\""
		strlit <-  many $ try (string "\\\"" >> return '\"') <|> try (string "\\n" >> return '\n') <|> ( noneOf "\"\n")
		string "\""
		return $ LitE $ OString strlit
	) <?> "string" )
	<?> "literal"

-- We represent the priority or 'fixity' of different types of expressions
-- by the Int argument

expression :: Int -> GenParser Char st Expr
expression n@12 = (try literal) <|> (try variable )
	<|> (try (do -- ( 1 + 5 )
		string "("
		expr <- expression 0
		string ")"
		return expr
	) <?> "bracketed expression" )
	<|> ( try ( do -- [ 3, a, a+1, b, a*b ]
		string "["
		exprs <- sepBy (expression 0) (char ',' )
		string "]"
		return $ ListE exprs
	)<|> try ( do -- ( 1,2,3 )
		string "("
		exprs <- sepBy (expression 0) (char ',' )
		string ")"
		return $ ListE exprs
	) <|> ( do -- eg.  [ a : 1 : a + 10 ]
		string "["
		exprs <- sepBy (expression 0) (char ':' )
		string "]"
		return $ collector "list_gen" exprs
	)<?> "vector/list" )
expression n@11 = 
	let
		posMatch a =
			(try $ do
				x <- a
				return $ Just x
			) <|> (return Nothing)
		modifier = 
			(try $ (do
				genSpace
				string "("
				genSpace
				args <- sepBy 
					(expression 0) 
					(try $ genSpace >> char ',' >> genSpace)
				genSpace
				string ")"
				genSpace
				return $ \f -> f :$ args
			<?> "function application"
			)) <|> (try $ (do
				genSpace
				string "["
				i <- pad $ expression 0
				string "]"
				genSpace
				return $ \l -> Var "index" :$ [l, i]
			<?> "list indexing"
			)) <|> (try $ ( do
				string "["
				genSpace
				start <- posMatch $ expression 0
				genSpace
				char ':'
				genSpace
				end   <- posMatch $ expression 0
				genSpace
				string "]"
				return $ case (start, end) of
					(Nothing, Nothing) -> id
					(Just s,  Nothing)  -> \l -> Var "splice" :$ [l, s, LitE OUndefined ]
					(Nothing, Just e )  -> \l -> Var "splice" :$ [l, LitE $ ONum 0, e]
					(Just s,  Just e )  -> \l -> Var "splice" :$ [l, s, e]
			<?> "list splicing"))
		
	in ( try( do 
		obj <- expression $ n+1
		genSpace
		mods <- modifier `sepBy` (genSpace)
		genSpace
		return $ foldl (\a b -> b a) obj mods
		) <?> "list splicing" )
	<|> try (expression $ n+1 )
expression n@10 = 
	let
		negate x = Var "negate" :$ [x]
	in try (do
		char '-'
		genSpace
		expr <- expression $ n+1
		return $ negate expr
	) <|> try (do
		char '+'
		genSpace
		expr <- expression $ n+1
		return expr
	) <|> try (expression $ n+1)
expression n@9 = try (( do 
		a <- expression (n+1)
		genSpace
		string "^"
		genSpace
		b <- expression n;
		return $ Var "^" :$ [a,b]
	) <?> "exponentiation")
	<|> try (expression $ n+1)
expression n@8 = 
	let 
		div  a b = Var "/" :$ [a, b]
	in try (( do 
		-- outer list is multiplication, inner division. objects are 
		-- expressions and take a varlookup to evaluate.
		-- eg. "1*2*3/4/5*6*7/8"
		--     [[vl→1],[vl→2],[vl→3,vl→4,vl→5],[vl→6],[vl→7,vl→8]]
		exprs <- sepBy1 (sepBy1 (pad $ expression $ n+1) 
			(try $ genSpace >> char '/' >> genSpace )) 
			(try $ genSpace >> char '*' >> genSpace)
		--     [[1],[2],[3,4,5],[6],[7,8]]
		--     [ 1,  2,  3/4/5,  6,  7/8 ]
		--       1 * 2 * 3/4/5 * 6 * 7/8 
		return $ collector "*" $ map (foldl1 div) exprs
	) <?> "multiplication/division")
	<|>try (expression $ n+1)
expression n@7 =
	let 
		mod  a b = Var "%" :$ [a, b]
	in try (( do 
		exprs <- sepBy1 (expression $ n+1) (try $ genSpace >> string "%" >> genSpace)
		return $ foldl1 mod exprs
	) <?> "modulo") 
	<|>try (expression $ n+1)
expression n@6 =
	try (( do 
		exprs <- sepBy1 (expression $ n+1) (try $ genSpace >> string "++" >> genSpace)
		return $ collector "++" exprs
	) <?> "append") 
	<|>try (expression $ n+1)

expression n@5 =
	let 
		sub a b = Var "-" :$ [a, b]
	in try (( do 
		-- Similar to multiply & divide
		-- eg. "1+2+3-4-5+6-7" 
		--     [[1],[2],[3,4,5],[6,7]]
		exprs <- sepBy1 (sepBy1 (pad $ expression $ n+1) 
			(try $ genSpace >> char '-' >> genSpace )) 
			(try $ genSpace >> char '+' >> genSpace)
		return $ collector "+" $ map (foldl1 sub) exprs
	) <?> "addition/subtraction")
	<|>try (expression $ n+1)
expression n@4 = 
	try ( do
		firstExpr <- expression $ n+1
		otherComparisonsExpr <- many $ do
			comparison <-
				    (try $ string "==" >> return (Var "==") )
				<|> (try $ string "!=" >> return (Var "!=") )
				<|> (try $ string ">=" >> return (Var ">=") )
				<|> (try $ string "<=" >> return (Var "<=") )
				<|> (try $ string ">"  >> return (Var ">")  )
				<|> (try $ string "<"  >> return (Var "<")  )
			expr <- expression $ n+1
			return (comparison, expr) 
		let
			(comparisons, otherExprs) = unzip otherComparisonsExpr
			exprs = firstExpr:otherExprs
		return $ case comparisons of 
			[]  -> firstExpr
			[x] -> x :$ exprs
			_   -> collector "all" [(comparisons!!n) :$ [exprs!!n, exprs!!(n+1)] | n <- [0.. length comparisons - 1] ]
	)<|> try (expression $ n+1)
expression n@3 =
	try (( do
		string "!"
		genSpace
		a <- expression $ n+1
		return $ Var "!" :$ [a]
		)<?> "logical-not")
	<|> try (expression $ n+1)
expression n@2 = 
	try (( do 
		a <- expression (n+1)
		genSpace
		string "&&"
		genSpace
		b <- expression n
		return $ Var "&&" :$ [a,b]
	)<?> "logical-and")
	<|> try (( do
		a <- expression $ n+1
		genSpace
		string "||"
		genSpace
		b <- expression n
		return $ Var "||" :$ [a,b]
		)<?> "logical-or")
	<|> try (expression $ n+1)
expression n@1 = 
	try (( do 
		a <- expression (n+1)
		genSpace
		string "?"
		genSpace
		b <- expression n
		genSpace
		string ":"
		genSpace
		c <- expression n
		return $ Var "?" :$ [a,b,c]
	) <?> "ternary")
	<|> try (expression $ n+1)
expression n@0 = try (do { genSpace; expr <- expression $ n+1; genSpace; return expr}) <|> try (expression $ n+1)