module Text.HPaco.Readers.Paco.Expressions
    ( expression
    )
where

import Control.Monad
import Text.HPaco.Readers.Paco.Basics
import Text.HPaco.Readers.Paco.ParserInternals
import Text.HPaco.AST.Statement
import Text.HPaco.AST.Expression

expression = booleanExpression

booleanExpression =
    binaryExpression
        [("&&", OpBooleanAnd),
         ("||", OpBooleanOr),
         ("^^", OpBooleanXor)]
        setOperationExpression

setOperationExpression =
    binaryExpression
        [("in", OpInList),
         ("contains", Flipped OpInList)]
        comparativeExpression

comparativeExpression =
    binaryExpression
        [("==", OpEquals),
         ("!==", OpNotEquals),
         ("=", OpLooseEquals),
         ("!=", OpLooseNotEquals),
         (">=", OpNotLess),
         (">", OpGreater),
         ("<=", OpNotGreater),
         ("<", OpLess)]
        additiveExpression

additiveExpression =
    binaryExpression
        [("+", OpPlus), ("-", OpMinus)]
        multiplicativeExpression

multiplicativeExpression =
    binaryExpression
        [("*", OpMul), ("/", OpDiv), ("%", OpMod)]
        (try traditionalFunctionCallExpression <|> postfixExpression)

binaryExpression :: [(String, BinaryOperator)] -> (Parser Expression) -> Parser Expression
binaryExpression opMap innerParser = do
    let rem :: Parser (BinaryOperator, Expression)
        rem = do
            ss_
            opStr <- foldl1 (<|>) $ map (try . string . fst) opMap
            ss_
            let Just op = lookup opStr opMap
            e <- innerParser
            return (op, e)
    left <- innerParser
    right <- many $ try rem
    return $ foldl combine left right
    where
        combine :: Expression -> (BinaryOperator, Expression) -> Expression
        combine lhs (op, rhs) = BinaryExpression op lhs rhs

traditionalFunctionCallExpression = do
    char '$'
    args <- manySepBy (try expression) ss_
    return $ FunctionCallExpression (head args) (tail args)

postfixExpression = do
    left <- (try prefixExpression <|> simpleExpression)
    postfixes <- many postfix
    return $ foldl combine left postfixes
    where
        combine :: Expression -> (Expression -> Expression) -> Expression
        combine l f = f l

prefixExpression = do
    ss_
    operator <- unaryOperator
    ss_
    expr <- (try prefixExpression <|> simpleExpression)
    return $ UnaryExpression operator expr

unaryOperator = do
    let opMap = [("not", OpNot)]
    opStr <- foldl1 (<|>) $ map (try . string . fst) opMap
    let Just op = lookup opStr opMap
    return op
    
postfix = try memberAccessPostfix
        <|> try indexPostfix
        <|> try functionCallPostfix

memberAccessPostfix :: Parser (Expression -> Expression)
memberAccessPostfix = do
    char '.'
    expr <- StringLiteral `liftM` identifier
    return $ \l -> BinaryExpression OpMember l expr

indexPostfix :: Parser (Expression -> Expression)
indexPostfix = do
    ss_
    char '['
    e <- expression
    char ']'
    ss_
    return $ \l -> BinaryExpression OpMember l e

functionCallPostfix :: Parser (Expression -> Expression)
functionCallPostfix = do
    char '('
    args <- manySepBy (try expression) (try $ ss_ >> char ',' >> ss_)
    ss_
    char ')'
    return $ \l -> FunctionCallExpression l args

simpleExpression :: Parser Expression
simpleExpression  =  floatLiteral
                 <|> intLiteral
                 <|> stringLiteral
                 <|> listExpression
                 <|> alistExpression
                 <|> varRefExpr
                 <|> bracedExpression

bracedExpression :: Parser Expression
bracedExpression = do
    char '('
    ss_
    inner <- expression
    ss_
    char ')'
    return inner

listExpression :: Parser Expression
listExpression = do
    char '['
    ss_
    items <- manySepBy expression (ss_ >> char ',' >> ss_)
    ss_
    optional $ char ',' >> ss_
    char ']'
    return $ ListExpression items

alistExpression :: Parser Expression
alistExpression = do
    char '{'
    ss_
    items <- option [] $ try $ manySepBy elem $ char ','
    ss_
    optional $ char ',' >> ss_
    char '}'
    return $ AListExpression items
    where
        elem :: Parser (Expression, Expression)
        elem = do
            ss_
            key <- expression
            ss_ >> char ':' >> ss_
            value <- expression
            ss_
            return (key, value)

intLiteral :: Parser Expression
intLiteral = do
    sign <- option '+' $ oneOf "+-"
    str <- many1 digit
    let str' = if sign == '-' then sign:str else str
    return . IntLiteral . read $ str'

floatLiteral :: Parser Expression
floatLiteral = do
    str <- (try dpd <|> try pd)
    return . FloatLiteral . read $ str
    where
        dpd = do
            sign <- option '+' $ oneOf "+-"
            intpart <- many1 digit
            char '.'
            fracpart <- many digit
            let str = intpart ++ "." ++ fracpart
            return $ if sign == '-' then sign:str else str
        pd = do
            sign <- option '+' $ oneOf "+-"
            char '.'
            fracpart <- many1 digit
            let str = "0." ++ fracpart
            return $ if sign == '-' then sign:str else str

stringLiteral :: Parser Expression
stringLiteral = do
    str <- anyQuotedString
    return . StringLiteral $ str

varRefExpr :: Parser Expression
varRefExpr = do
    id <- (string "." <|> identifier)
    return $ VariableReference id