module Parse.Expr (def,term) where

import Ast
import Context
import Control.Applicative ((<$>), (<*>))
import Control.Monad
import Data.Char (isSymbol, isDigit)
import Data.List (foldl')
import Text.Parsec hiding (newline,spaces)
import Text.Parsec.Indent
import qualified Text.Pandoc as Pan

import Parse.Library
import Parse.Patterns
import Parse.Binops

import Guid
import Types.Types (Type (VarT), Scheme (Forall))

import System.IO.Unsafe


--------  Basic Terms  --------

numTerm :: IParser Expr
numTerm = toExpr <$> (preNum <?> "number")
    where toExpr n | '.' `elem` n = FloatNum (read n)
                   | otherwise = IntNum (read n)
          preNum  = (++) <$> many1 digit <*> option "" postNum
          postNum = do try $ lookAhead (string "." >> digit)
                       string "."
                       ('.':) <$> many1 digit

strTerm :: IParser Expr
strTerm = liftM Str . expecting "string" . betwixt '"' '"' . many $
          backslashed <|> satisfy (/='"')

varTerm :: IParser Expr
varTerm = toVar <$> var <?> "variable"

toVar v = case v of "True"  -> Boolean True
                    "False" -> Boolean False
                    _       -> Var v

chrTerm :: IParser Expr
chrTerm = Chr <$> betwixt '\'' '\'' (backslashed <|> satisfy (/='\''))
          <?> "character"

accessor :: IParser Expr
accessor = do
  start <- getPosition
  lbl <- try (string "." >> rLabel)
  end <- getPosition
  let ctx e = addCtx ("." ++ lbl) (pos start end e)
  return (Lambda "_" (ctx $ Access (ctx $ Var "_") lbl))


--------  Complex Terms  --------

listTerm :: IParser Expr
listTerm =
      (do { try $ string "[markdown|"
          ; md <- filter (/='\r') <$> manyTill anyChar (try $ string "|]")
          ; return . Markdown $ Pan.readMarkdown Pan.defaultParserState md })
  <|> (braces $ choice
       [ try $ do { lo <- expr; whitespace; string ".." ; whitespace
                  ; Range lo <$> expr }
       , do (C _ _ e) <- list <$> commaSep expr
            return e
       ])

parensTerm :: IParser CExpr
parensTerm = parens $ choice
             [ do start <- getPosition
                  op <- try anyOp
                  end <- getPosition
                  let ctxt = pos start end
                  return . ctxt . Lambda "x" . ctxt . Lambda "y" . ctxt $
                         Binop op (ctxt $ Var "x") (ctxt $ Var "y")
             , do start <- getPosition
                  let comma = char ',' <?> "comma ','"
                  commas <- comma >> many (whitespace >> comma)
                  end <- getPosition
                  let vars = map (('v':) . show) [ 0 .. length commas + 1 ]
                      ctxt = pos start end
                  return $ foldr (\x e -> ctxt $ Lambda x e)
                             (ctxt . tuple $ map (ctxt . Var) vars) vars
             , do start <- getPosition
                  es <- commaSep expr
                  end <- getPosition
                  return $ case es of [e] -> e
                                      _   -> pos start end (tuple es)
             ]

recordTerm :: IParser CExpr
recordTerm = brackets $ choice [ misc, addContext record ]
    where field = do
              fDefs <- (:) <$> (PVar <$> rLabel) <*> spacePrefix patternTerm
              whitespace
              e <- string "=" >> whitespace >> expr
              run $ flattenPatterns fDefs e
          extract [ FnDef f args exp ] = return (f,args,exp)
          extract _ = fail "Improperly formed record field."
          record = Record <$> (mapM extract =<< commaSep field)
          change = do
              lbl <- rLabel
              whitespace >> string "<-" >> whitespace
              (,) lbl <$> expr
          remove r = addContext (string "-" >> whitespace >> Remove r <$> rLabel)
          insert r = addContext $ do
                       string "|" >> whitespace
                       Insert r <$> rLabel <*>
                           (whitespace >> string "=" >> whitespace >> expr)
          modify r = addContext
                     (string "|" >> whitespace >> Modify r <$> commaSep1 change)
          misc = try $ do
            record <- addContext (Var <$> rLabel)
            whitespace
            opt <- optionMaybe (remove record)
            whitespace
            case opt of
              Just e  -> try (insert e) <|> return e
              Nothing -> try (insert record) <|> try (modify record)
                        

term :: IParser CExpr
term =  addContext (choice [ numTerm, strTerm, chrTerm, listTerm, accessor ])
    <|> accessible (addContext varTerm <|> parensTerm <|> recordTerm)
    <?> "basic term (4, x, 'c', etc.)"

--------  Applications  --------

appExpr :: IParser CExpr
appExpr = do
  tlist <- spaceSep1 term
  return $ case tlist of
             t:[] -> t
             t:ts -> foldl' (\f x -> epos f x $ App f x) t ts

--------  Normal Expressions  --------

binaryExpr :: IParser CExpr
binaryExpr = binops appExpr anyOp

ifExpr :: IParser Expr
ifExpr = reserved "if" >> whitespace >> (normal <|> multiIf)
    where normal = do e1 <- expr ; whitespace
                      reserved "then" ; whitespace ; e2 <- expr
                      whitespace <?> "an 'else' branch"
                      reserved "else" <?> "an 'else' branch" ; whitespace
                      If e1 e2 <$> expr
          multiIf = (MultiIf <$> spaceSep1 iff)
              where iff = do string "|" ; whitespace
                             b <- expr ; whitespace ; string "->" ; whitespace
                             (,) b <$> expr

lambdaExpr :: IParser CExpr
lambdaExpr = do char '\\' <|> char '\x03BB' <?> "anonymous function"
                whitespace
                pats <- spaceSep1 patternTerm
                whitespace ; arrow ; whitespace
                e <- expr
                return . run $ makeLambda pats e

defSet :: IParser [Def]
defSet = concat <$> block (do d <- assignExpr ; whitespace ; return d)

letExpr :: IParser Expr
letExpr = do
  reserved "let" ; whitespace
  defs <- defSet
  whitespace ; reserved "in" ; whitespace
  Let defs <$> expr

caseExpr :: IParser Expr
caseExpr = do
  reserved "case"; whitespace; e <- expr; whitespace; reserved "of"; whitespace
  Case e <$> (with <|> without)
    where case_ = do p <- patternExpr; whitespace; arrow; whitespace
                     (,) p <$> expr
          with    = brackets (semiSep1 (case_ <?> "cases { x -> ... }"))
          without = block (do c <- case_ ; whitespace ; return c)

expr = addContext (choice [ ifExpr, letExpr, caseExpr ])
    <|> lambdaExpr
    <|> binaryExpr 
    <?> "an expression"

funcDef = try (do p1 <- try patternTerm ; infics p1 <|> func p1)
          <|> ((:[]) <$> patternExpr)
          <?> "the definition of a variable (x = ...)"
    where func p@(PVar v) = (p:) <$> spacePrefix patternTerm
          func p          = do try (lookAhead (whitespace >> string "="))
                               return [p]
          infics p1 = do
            o:p <- try (whitespace >> anyOp)
            p2  <- (whitespace >> patternTerm)
            return $ if o == '`' then [ PVar $ takeWhile (/='`') p, p1, p2 ]
                                 else [ PVar (o:p), p1, p2 ]

assignExpr :: IParser [Def]
assignExpr = withPos $ do
  fDefs <- funcDef
  whitespace
  e <- string "=" >> whitespace >> expr
  run $ flattenPatterns fDefs e

def = map Definition <$> assignExpr


parseDef str =
    case iParse def "" str of
      Right result -> Right result
      Left err -> Left $ "Parse error at " ++ show err