module Lib.Parser

import Prelude
import Lib.Common
import Data.SortedMap
import Data.String
import Lib.Parser.Impl
import Lib.Syntax
import Lib.Token
import Lib.Types

-- TODO - anywhere this says getPos, we probably have to fix bounds?  It is token sized though.

lazy : ∀ a. (Unit → Parser a) → Parser a
lazy pa = pa MkUnit

ident : Parser String
ident = token Ident <|> token MixFix

uident : Parser String
uident = token UIdent

parenWrap : ∀ a. Parser a -> Parser a
parenWrap pa = do
  symbol "("
  t <- pa
  symbol ")"
  pure t

braces : ∀ a. Parser a -> Parser a
braces pa = do
  symbol "{"
  t <- pa
  symbol "}"
  pure t

dbraces : ∀ a. Parser a -> Parser a
dbraces pa = do
  symbol "{{"
  t <- pa
  symbol "}}"
  pure t


optional : ∀ a. Parser a -> Parser (Maybe a)
optional pa = Just <$> pa <|> pure Nothing

stringLit : Parser Raw
stringLit = do
  fc <- getPos
  t <- token StringKind
  pure $ RLit fc (LString t)


-- typeExpr is term with arrows.
typeExpr : Parser Raw
term : (Parser Raw)

interp : Parser Raw
interp = do
  token StartInterp
  tm <- term
  token EndInterp
  pure tm

interpString : Parser Raw
interpString = do
  ignore $ token StartQuote
  part <- term
  parts <- many (stringLit <|> interp)
  ignore $ token EndQuote
  pure $ foldl append part parts
  where
    append : Raw -> Raw -> Raw
    append t u =
      let fc = getFC t in
      (RApp (fc + getFC u) (RApp fc (RVar fc "_++_") t Explicit) u Explicit)

intLit : Parser Raw
intLit = do
  fc <- getPos
  t <- token Number
  pure $ RLit fc (LInt (stringToInt t))


charLit : Parser Raw
charLit = do
  fc <- getPos
  v <- token Character
  pure $ RLit fc (LChar $ strIndex v 0)

lit : Parser Raw
lit = intLit <|> interpString <|> stringLit <|> charLit



-- helpful when we've got some / many and need FC for each
addPos : ∀ a. Parser a -> Parser (FC × a)
addPos pa = _,_ <$> getPos <*> pa

asAtom : Parser Raw
asAtom = do
  fc <- getPos
  nm <- ident
  asPat <- optional $ keyword "@" *> parenWrap typeExpr
  case asPat of
    Just exp => pure $ RAs fc nm exp
    Nothing  => pure $ RVar fc nm

-- the inside of Raw
recordUpdate : Parser Raw

parenTypeExp : Parser Raw
parenTypeExp = do
  fc <- getPos
  symbol "("
  -- fc' is a little hacky, need bounded or something cleaner
  fc' <- getPos
  Nothing <- optional $ symbol ")"
    | Just tm => do pure $ RImpossible (fc + fc')
  t <- typeExpr
  symbol ")"
  pure t

atom : Parser Raw
atom = do
  pure MkUnit
  RU <$> getPos <* keyword "U"
    -- <|> RVar <$> getPos <*> ident
    <|> asAtom
    <|> RVar <$> getPos <*> uident
    <|> RVar <$> getPos <*> token Projection
    <|> lit
    <|> RImplicit <$> getPos <* keyword "_"
    <|> RHole <$> getPos <* keyword "?"
    <|> parenTypeExp
    <|> recordUpdate

updateClause : Parser UpdateClause
updateClause = do
  fc <- getPos
  nm <- ident
  op <- True <$ symbol ":=" <|> False <$ symbol "$="
  tm <- term
  case op of
    True => pure $ AssignField fc nm tm
    _    => pure $ ModifyField fc nm tm

-- ambiguity vs {a} or {a} -> ... is tough, we can do [] or put a keyword in front.
recordUpdate = do
  fc <- getPos
  symbol "["
  clauses <- sepBy (symbol ";") updateClause
  symbol "]"
  tm <- optional atom
  pure $ RUpdateRec fc clauses tm

-- Argument to a Spine
pArg : Parser (Icit × FC × Raw)
pArg = do
  fc <- getPos
  (\x => Implicit, fc, x) <$> braces typeExpr
    <|> (\x => Auto, fc, x) <$> dbraces typeExpr
    <|> (\x => Explicit, fc, x) <$> atom

AppSpine : U
AppSpine = List (Icit × FC × Raw)

pratt : Operators -> Int -> String -> Raw -> AppSpine -> Parser (Raw × AppSpine)
pratt ops prec stop left spine = do
  (left, spine) <- runPrefix stop left spine
  let (left, spine) = projectHead left spine
  let spine = runProject spine
  case spine of
    Nil => pure (left, Nil)
    ((Explicit, fc, tm@(RVar x nm)) :: rest) =>
        if nm == stop then pure (left,spine) else
        case lookupMap' nm ops of
          Just (MkOp name p fix False rule) => if p < prec
            then pure (left, spine)
            else runRule p fix stop rule (RApp (getFC left + fc) (RVar fc name) left Explicit) rest
          -- The argument is a prefix op
          Just (MkOp name p fix True rule) => do
            (arg, rest) <- runPrefix stop tm rest
            pratt ops prec stop (RApp (getFC left + getFC arg) left arg Explicit) rest
            -- fail "expected operator \{show spine} got \{show name}"
          Nothing =>
            if isPrefixOf "." nm
            then pratt ops prec stop (RApp (getFC left + getFC tm) tm left Explicit) rest
            else pratt ops prec stop (RApp (getFC left + getFC tm) left tm Explicit) rest
    ((icit, fc, tm) :: rest) => pratt ops prec stop (RApp (getFC left + getFC tm) left tm icit) rest
  where
    projectHead : Raw -> AppSpine -> (Raw × AppSpine)
    projectHead t sp@((Explicit, fc', RVar fc nm) :: rest) =
      if isPrefixOf "." nm
        then projectHead (RApp (fc + getFC t) (RVar fc nm) t Explicit) rest
        else (t,sp)
    projectHead t sp = (t, sp)

    -- we need to check left/AppSpine first
    -- we have a case above for when the next token is a projection, but
    -- we need this to make projection bind tighter than app
    runProject : AppSpine -> AppSpine
    runProject (t@(Explicit, fc', tm) :: u@(Explicit, _, RVar fc nm) :: rest) =
      if isPrefixOf "." nm
        then runProject ((Explicit, fc', RApp (fc + getFC tm) (RVar fc nm) tm Explicit) :: rest)
        else (t :: u :: rest)
    runProject tms = tms

    -- left has our partially applied operator and we're picking up args
    -- for the rest of the `_`
    runRule : Int -> Fixity -> String -> List String -> Raw -> AppSpine -> Parser (Raw × AppSpine)
    runRule p fix stop Nil left spine = pure (left, spine)
    runRule p fix stop ("" :: Nil) left spine = do
      let pr = case fix of
                InfixR => p
                _ => p + 1
      case spine of
                ((_, fc, right) :: rest) => do
                  (right, rest) <- pratt ops pr stop right rest
                  pratt ops prec stop (RApp (getFC left + getFC right) left right Explicit) rest
                _ =>
                  -- Trailing operator section, like (2 +)
                  pratt ops prec stop left Nil
                  -- fail "trailing operator"


    runRule p fix stop (nm :: rule) left spine = do
      case spine of
        Nil => fail "short"
        ((_, _, right) :: rest) => do

          (right,rest) <- pratt ops 0 nm right rest -- stop!!
          let ((_,fc',RVar fc name) :: rest) = rest
            | _ => fail "expected \{nm}"
          if name == nm
            then runRule p fix stop rule (RApp (getFC left + getFC right) left right Explicit) rest
            else fail "expected \{nm}"

    -- run any prefix operators
    runPrefix : String -> Raw -> AppSpine -> Parser (Raw × AppSpine)
    runPrefix stop (RVar fc nm) spine =
      case lookupMap' nm ops of
           -- TODO False should be an error here
         Just (MkOp name p fix True rule) => do
            runRule p fix stop rule (RVar fc name) spine
         Just (MkOp name p fix False rule) =>
            -- REVIEW We could also use a lambda with a fresh name
            runRule p fix stop rule (RApp fc (RVar fc "flip") (RVar fc name) Explicit) spine
            -- fail "got infix \{show name} in prefix position"
         _ =>
          pure (left, spine)
    runPrefix stop left spine = pure (left, spine)



parseOp : Parser Raw
parseOp = do
  fc <- getPos
  ops <- getOps
  hd <- atom
  rest <- many pArg
  (res, Nil) <- pratt ops 0 "" hd rest
    | _ => fail "extra stuff"
  pure res

letExpr : Parser Raw
letExpr = do
  keyword "let"
  alts <- startBlock $ someSame $ letAssign
  keyword' "in"
  scope <- typeExpr
  pure $ foldl mkLet scope (reverse alts)
  where
    mkLet : Raw → String × FC × Maybe Raw × Raw → Raw
    mkLet acc (n,fc,ty,v) = RLet fc n (fromMaybe (RImplicit fc) ty) v acc

    letAssign : Parser (Name × FC × Maybe Raw × Raw)
    letAssign = do
      fc <- getPos
      name <- ident
      ty <- optional (keyword ":" *> typeExpr)
      keyword "="
      t <- typeExpr
      pure (name,fc,ty,t)

pLamArg : Parser (Icit × String × Maybe Raw)
pLamArg = impArg <|> autoArg <|> expArg
       <|> (\ x => (Explicit, x, Nothing)) <$> (ident <|> uident)
       <|> keyword "_" *> pure (Explicit, "_", Nothing)
  where
    impArg : Parser (Icit × String × Maybe Raw)
    impArg = do
      nm <- braces (ident <|> uident)
      ty <- optional (symbol ":" >> typeExpr)
      pure (Implicit, nm, ty)

    autoArg : Parser (Icit × String × Maybe Raw)
    autoArg = do
      nm <- dbraces (ident <|> uident)
      ty <- optional (symbol ":" >> typeExpr)
      pure (Auto, nm, ty)

    expArg : Parser (Icit × String × Maybe Raw)
    expArg = do
      nm <- parenWrap (ident <|> uident)
      -- FIXME - this is broken, outside parenWrap, guess I never used it?
      ty <- optional (symbol ":" >> typeExpr)
      pure (Explicit, nm, ty)

lamExpr : Parser Raw
lamExpr = do
  pos <- getPos
  keyword "\\" <|> keyword "λ"
  args <- some $ addPos pLamArg
  keyword "=>"
  scope <- typeExpr
  pure $ foldr mkLam scope args
  where
    mkLam : FC × Icit × Name × Maybe Raw → Raw → Raw
    mkLam (fc, icit, name, ty) sc = RLam fc (BI fc name icit Many) sc


caseAlt : Parser RCaseAlt
caseAlt = do
  pure MkUnit
  pat <- typeExpr
  t <- optional (keyword "=>" >> term)
  pure $ MkAlt pat t


caseExpr : Parser Raw
caseExpr = do
  fc <- getPos
  keyword "case"
  sc <- term
  ty <- optional (keyword ":" *> typeExpr)
  keyword "of"
  alts <- startBlock $ someSame $ caseAlt
  pure $ RCase fc sc ty alts

caseLamExpr : Parser Raw
caseLamExpr = do
  fc <- getPos
  try ((keyword "\\" <|> keyword "λ") *> keyword "case")
  alts <- startBlock $ someSame $ caseAlt
  pure $ RLam fc (BI fc "$case" Explicit Many) $ RCase fc (RVar fc "$case") Nothing alts

doExpr : Parser Raw
doStmt : Parser DoStmt

caseLet : Parser Raw
caseLet = do
  -- look ahead so we can fall back to normal let
  fc <- getPos
  try (keyword "let" >> symbol "(")
  pat <- typeExpr
  symbol ")"
  keyword "="
  sc <- typeExpr
  alts <- startBlock $ manySame $ symbol "|" *> caseAlt
  keyword "in"
  body <- term
  pure $ RCase fc sc Nothing (MkAlt pat (Just body) :: alts)

doCaseLet : Parser DoStmt
doCaseLet = do
  -- look ahead so we can fall back to normal let
  -- Maybe make it work like arrow?
  fc <- getPos
  try (keyword "let" >> symbol "(")
  pat <- typeExpr
  symbol ")"
  keyword "="
  sc <- typeExpr
  alts <- startBlock $ manySame $ symbol "|" *> caseAlt
  bodyFC <- getPos
  body <- RDo <$> getPos <*> someSame doStmt
  pure $ DoExpr fc (RCase fc sc Nothing (MkAlt pat (Just body) :: alts))

doArrow : Parser DoStmt
doArrow = do
  fc <- getPos
  left <- typeExpr
  (Just _) <- optional $ keyword "<-"
    | _ => pure $ DoExpr fc left
  right <- term
  -- FIXME bad error for `| pure MkUnit`, we need to commit if indentation is correct.
  alts <- startBlock $ manySame $ symbol "|" *> caseAlt
  pure $ DoArrow fc left right alts

doLet : Parser DoStmt
doLet = do
  fc <- getPos
  keyword "let"
  nm <- ident
  keyword "="
  tm <- term
  pure $ DoLet fc nm tm

doStmt
  = doCaseLet
  <|> doLet
  <|> doArrow

doExpr = RDo <$> getPos <* keyword "do" <*> (startBlock $ someSame doStmt)

parseIfThen : Parser Raw
parseIfThen = do
  fc <- getPos
  keyword "if"
  a <- term
  keyword "then"
  b <- term
  keyword "else"
  c <- term
  pure $ RIf fc a b c

term' : Parser Raw

term' =  caseExpr
    <|> caseLet
    <|> letExpr
    <|> caseLamExpr
    <|> lamExpr
    <|> doExpr
    <|> parseIfThen
    -- Make this last for better error messages
    <|> parseOp

term = do
  t <- term'
  rest <- many (_,_ <$> getPos <* keyword "$" <*> term')
  pure $ apply t rest
  where
    apply : Raw -> List (FC × Raw) -> Raw
    apply t Nil = t
    apply t ((fc,x) :: xs) =
      let u = apply x xs in
      RApp (getFC t + getFC u) t u Explicit

varname : Parser String
varname = (ident <|> uident <|> keyword "_" *> pure "_")

quantity : Parser Quant
quantity = fromMaybe Many <$> optional (Zero <$ keyword "0")

ebind : Parser Telescope
ebind = do
  -- don't commit until we see the ":"
  (quant, names) <- try $ do
    symbol "("
    quant <- quantity
    names <- some (addPos varname) <* symbol ":"
    pure (quant,names)
  ty <- typeExpr
  symbol ")"
  pure $ map (makeBind quant ty) names
  where
    makeBind : Quant → Raw → FC × String → (BindInfo × Raw)
    makeBind quant ty (pos, name) = (BI pos name Explicit quant, ty)


ibind : Parser Telescope
ibind = do
  -- I've gone back and forth on this, but I think {m a b} is more useful than {Int}
  symbol "{"
  quant <- quantity
  names <- (some (addPos varname))
  ty <- optional (symbol ":" *> typeExpr)
  symbol "}"
  pure $ map (makeBind quant ty) names
  where
    makeBind : Quant → Maybe Raw → FC × String → BindInfo × Raw
    makeBind quant ty (pos, name) = (BI pos name Implicit quant, fromMaybe (RImplicit pos) ty)

abind : Parser Telescope
abind = do
  -- for this, however, it would be nice to allow {{Monad A}}
  symbol "{{"
  name <- optional $ try (addPos varname <* symbol ":")
  ty <- typeExpr
  symbol "}}"
  case name of
    Just (pos,name) => pure ((BI pos name Auto Many, ty) :: Nil)
    Nothing => pure ((BI (getFC ty) "_" Auto Many, ty) :: Nil)

arrow : Parser Unit
arrow = symbol "->" <|> symbol "→"

-- Collect a bunch of binders (A : U) {y : A} -> ...

forAll : Parser Raw
forAll = do
  keyword "forall" <|> keyword "∀"
  all <- some (addPos varname)
  keyword "."
  scope <- typeExpr
  pure $ foldr mkPi scope all
  where
    mkPi : FC × String → Raw → Raw
    mkPi (fc, n) sc = RPi fc (BI fc n Implicit Zero) (RImplicit fc) sc

binders : Parser Raw
binders = do
  binds <- many (abind <|> ibind <|> ebind)
  arrow
  scope <- typeExpr
  pure $ foldr mkBind scope (join binds)
  where
    mkBind : (BindInfo × Raw) -> Raw -> Raw
    mkBind (info, ty) scope = RPi (getFC info) info ty scope

typeExpr
  = binders
  <|> forAll
  <|> (do
        fc <- getPos
        exp <- term
        scope <- optional (arrow *> typeExpr)
        case scope of
          Nothing      => pure exp
          -- consider Maybe String to represent missing
          (Just scope) => pure $ RPi fc (BI fc "_" Explicit Many) exp scope)

-- And top level stuff


parseSig : Parser Decl
parseSig = TypeSig <$> getPos <*> try (some (ident <|> uident <|> token Projection) <* keyword ":") <*> typeExpr

parseImport : Parser Import
parseImport = do
  fc <- getPos
  keyword "import"
  -- TODO revisit when we have parser for qualified names in source
  (nameFC, ident) <- withFC uident
  (restFC,rest) <- withFC $ many $ token Projection
  let name = joinBy "" (ident :: rest)
  pure $ MkImport fc (nameFC + restFC, name)

-- Do we do pattern stuff now? or just name = lambda?
-- TODO multiple names
parseMixfix : Parser Decl
parseMixfix = do
  fc <- getPos
  fix <- InfixL <$ keyword "infixl"
     <|> InfixR <$ keyword "infixr"
     <|> Infix <$ keyword "infix"
  prec <- token Number
  ops <- some $ token MixFix
  for ops $ \ op => addOp op (cast prec) fix
  pure $ PMixFix fc ops (cast prec) fix

getName : Raw -> Parser String
getName (RVar x nm) = pure nm
getName (RApp x t u icit) = getName t
getName tm = fail "bad LHS"



parseDef : Parser Decl
parseDef = do
  fc <- getPos
  t <- typeExpr
  nm <- getName t
  Just _ <- optional $ keyword "="
    -- impossible clause
    | Nothing => pure $ FunDef fc nm ((t,Nothing) :: Nil)
  body <- typeExpr
  wfc <- getPos
  w <- optional $ do
    keyword "where"
    startBlock $ manySame $ (parseSig <|> parseDef)
  let body = maybe body (\ decls => RWhere wfc decls body) w
  -- these get collected later
  pure $ FunDef fc nm ((t, Just body) :: Nil)


parsePType : Parser Decl
parsePType = do
  fc <- getPos
  keyword "ptype"
  id <- uident
  ty <- optional $ do
    keyword ":"
    typeExpr
  pure $ PType fc id ty

parsePFunc : Parser Decl
parsePFunc = do
  fc <- getPos
  keyword "pfunc"
  nm <- ident
  used <- optional (keyword "uses" >> parenWrap (many $ uident <|> ident <|> token MixFix))
  keyword ":"
  ty <- typeExpr
  keyword ":="
  src <- token JSLit
  pure $ PFunc fc nm (fromMaybe Nil used) ty src


parseShortData : Parser Decl
parseShortData = do
  fc <- getPos
  keyword "data"
  lhs <- typeExpr
  keyword "="
  sigs <- sepBy (keyword "|") typeExpr
  pure $ ShortData fc lhs sigs


parseData : Parser Decl
parseData = do
  fc <- getPos
  -- commit when we hit ":"
  name <- try $ (keyword "data" *> withFC (uident <|> token MixFix) <* keyword ":")
  ty <- typeExpr
  Just _ <- optional (keyword "where")
    | _ => pure $ Data fc name ty Nothing
  decls <- startBlock $ manySame $ parseSig
  pure $ Data fc name ty (Just decls)

nakedBind : Parser Telescope
nakedBind = do
  names <- some (addPos varname)
  pure $ map makeBind names
  where
    makeBind : FC × String → (BindInfo × Raw)
    makeBind (pos, name) = (BI pos name Explicit Many, RImplicit pos)

parseRecord : Parser Decl
parseRecord = do
  fc <- getPos
  keyword "record"
  name <- withFC uident
  teles <- many $ ebind <|> nakedBind
  keyword "where"
  cname <- optional $ keyword "constructor" *> withFC (uident <|> token MixFix)
  decls <- startBlock $ manySame $ parseSig
  pure $ Record fc name (join teles) cname decls



parseClass : Parser Decl
parseClass = do
  fc <- getPos
  keyword "class"
  name <- withFC uident
  teles <- many $ ebind <|> nakedBind
  keyword "where"
  decls <- startBlock $ manySame $ parseSig
  pure $ Class fc name (join teles) decls


parseInstance : Parser Decl
parseInstance = do
  fc <- getPos
  keyword "instance"
  ty <- typeExpr
  -- is it a forward declaration
  (Just _) <- optional $ keyword "where"
    | _ => pure $ Instance fc ty Nothing
  decls <- startBlock $ manySame $ parseDef
  pure $ Instance fc ty (Just decls)

-- Not sure what I want here.
-- I can't get a Tm without a type, and then we're covered by the other stuff
parseNorm : Parser Decl
parseNorm = DCheck <$> getPos <* keyword "#check" <*> typeExpr <* keyword ":" <*> typeExpr


parseDecl : Parser Decl
parseDecl = parseMixfix <|> parsePType <|> parsePFunc
  <|> parseNorm <|> parseData <|> parseShortData
  <|> parseClass <|> parseInstance <|> parseRecord
  -- We'll put the backtracing stuff last, but there is a commit issue in parseDef
  <|> parseSig <|> parseDef



parseModHeader : Parser (FC × String)
parseModHeader = do
  sameLevel (keyword "module")
  fc <- getPos
  name <- uident
  rest <- many $ token Projection
  -- FIXME use QName
  let name = joinBy "" (name :: rest)
  pure (fc, name)


parseImports : Parser (List Import)
parseImports = manySame parseImport


parseMod : Parser Module
parseMod = do
  sameLevel (keyword "module")
  name <- uident
  rest <- many $ token Projection
  imports <- manySame parseImport
  decls <- manySame parseDecl
  let name = joinBy "" (name :: rest)
  pure $ MkModule name imports decls
