-- | Parser for a PascaLigo contract.
module AST.Pascaligo.Parser where

import Data.Maybe (isJust)

import AST.Skeleton

import Duplo.Error
import Duplo.Tree
import Duplo.Pretty

import Product
import Parser
import ParseTree

-- import Debug.Trace

-- example :: FilePath
-- example = "../../../src/test/contracts/arithmetic.ligo"
-- example = "../../../src/test/contracts/address.ligo"
-- example = "../../../src/test/contracts/annotation.ligo"
-- example = "../../../src/test/contracts/amount.ligo"
-- example = "../../../src/test/contracts/attributes.ligo"
-- example = "../../../src/test/contracts/application.ligo"
-- example = "../../../src/test/contracts/assign.ligo"
-- example = "../../../src/test/contracts/big_map.ligo"
-- example = "../../../src/test/contracts/blockless.ligo"
-- example = "../../../src/test/contracts/bad_timestamp.ligo"
-- example = "../../../src/test/contracts/boolean_operators.ligo"
-- example = "../../../src/test/contracts/bitwise_arithmetic.ligo"
-- example = "../../../src/test/contracts/bad_type_operator.ligo"
-- example = "../../../src/test/contracts/blocks.ligo"
-- example = "../../../src/test/contracts/bytes_unpack.ligo"
-- example = "../../../src/test/contracts/balance_constant.ligo"
-- example = "../../../src/test/contracts/blockless.ligo"
-- example = "../../../src/test/contracts/bytes_arithmetic.ligo"
-- example = "../../../src/test/contracts/chain_id.ligo"
-- example = "../../../src/test/contracts/closure-3.ligo"

-- sample' :: FilePath -> IO (LIGO Info)
-- sample' f
--   =   toParseTree (Path f)
--   >>= runParserM . recognise
--   >>= return . fst

-- source' :: FilePath -> IO ()
-- source' f
--   =   toParseTree (Path f)
--   >>= print . pp

-- sample :: IO ()
-- sample
--   =   toParseTree (Path example)
--   >>= runParserM . recognise
--   >>= print . pp . fst

-- source :: IO ()
-- source
--   =   toParseTree (Path example)
--   >>= print . pp

recognise :: RawTree -> ParserM (LIGO Info)
recognise = descent (error "Reasonligo.recognise") $ map usingScope
  [ -- Contract
    Descent do
      boilerplate \case
        "Start" -> RawContract <$> fields "declaration"
        _ -> fallthrough

    -- Expr
  , Descent do
      boilerplate \case
        "let_expr"          -> Let       <$> field  "locals"    <*> field "body"
        "fun_call"          -> Apply     <$> field  "f"         <*> fields "arguments"
        "par_call"          -> Apply     <$> field  "f"         <*> fields "arguments"
        "projection_call"   -> Apply     <$> field  "f"         <*> fields "arguments"
        "Some_call"         -> Apply     <$> field  "constr"    <*> fields "arguments"
        "constr_call"       -> Apply     <$> field  "constr"    <*> fields "arguments"
        "arguments"         -> Tuple     <$> fields "argument"
        "unop"              -> UnOp      <$> field  "negate"    <*> field "arg"
        "binop"             -> BinOp     <$> field  "arg1"      <*> field "op"   <*> field "arg2"
        "block"             -> Seq       <$> fields "statement"
        "clause_block"      -> Seq       <$> fields "statement"
        "list_expr"         -> List      <$> fields "element"
        "annot_expr"        -> Annot     <$> field  "subject"   <*> field "type"
        "conditional"       -> If        <$> field  "selector"  <*> field "then" <*> fieldOpt "else"
        "cond_expr"         -> If        <$> field  "selector"  <*> field "then" <*> fieldOpt "else"
        "assignment"        -> Assign    <$> field  "LHS"       <*> field "RHS"
        "attr_decl"         -> Attrs     <$> fields "attribute"
        "record_expr"       -> Record    <$> fields "assignment"
        "big_map_injection" -> BigMap    <$> fields "binding"
        "map_remove"        -> MapRemove <$> field  "key"       <*> field "container"
        "tuple_expr"        -> Tuple     <$> fields "element"
        "skip"              -> return Skip
        "case_expr"         -> Case      <$> field  "subject"    <*> fields   "case"
        "case_instr"        -> Case      <$> field  "subject"    <*> fields   "case"
        "fun_expr"          -> Lambda    <$> fields "parameters" <*> fieldOpt    "type"  <*> field "body"
        "for_cycle"         -> ForLoop   <$> field  "name"       <*> field    "begin" <*> field "end" <*> fieldOpt "step" <*> field "body"
        "for_box"           -> ForBox    <$> field  "key"        <*> fieldOpt "value" <*> field "kind"  <*> field "collection" <*> field "body"
        "while_loop"        -> WhileLoop <$> field  "breaker"    <*> field    "body"
        "map_injection"     -> Map       <$> fields "binding"
        "list_injection"    -> List      <$> fields "element"
        "set_expr"          -> Set       <$> fields "element"
        "map_patch"         -> MapPatch  <$> field  "container"  <*> fields "binding"
        "set_patch"         -> SetPatch  <$> field  "container"  <*> fields "key"
        "set_remove"        -> SetRemove <$> field  "key"        <*> field  "container"
        "update_record"     -> RecordUpd <$> field  "record"     <*> fields "assignment"
        _                   -> fallthrough

    -- Pattern
  , Descent do
      boilerplate \case
        "user_constr_pattern" -> IsConstr <$> field  "constr" <*> fieldOpt "arguments"
        "tuple_pattern"       -> IsTuple  <$> fields "element"
        "nil"                 -> return $ IsList []
        "list_pattern"        -> IsList   <$> fields "element"
        "cons_pattern"        -> IsCons   <$> field  "head"   <*> field "tail"
        _                     -> fallthrough

    -- Alt
  , Descent do
      boilerplate \case
        "case_clause_expr"  -> Alt <$> field "pattern" <*> field  "body"
        "case_clause_instr" -> Alt <$> field "pattern" <*> field  "body"
        _                   -> fallthrough

    -- FieldAssignment
  , Descent do
      boilerplate \case
        "field_assignment"      -> FieldAssignment <$> field "name" <*> field "_rhs"
        "field_path_assignment" -> FieldAssignment <$> field "lhs"  <*> field "_rhs"
        _                  -> fallthrough

    -- MapBinding
  , Descent do
      boilerplate \case
        "binding" -> MapBinding <$> field "key" <*> field "value"
        _         -> fallthrough

  , Descent do
      boilerplate' \case
        ("negate",     op) -> return $ Op op
        ("adder",      op) -> return $ Op op
        ("multiplier", op) -> return $ Op op
        ("comparison", op) -> return $ Op op
        ("^",          _)  -> return $ Op "^"
        ("#",          _)  -> return $ Op "#"
        _                  -> fallthrough

  , Descent do
      boilerplate \case
        "data_projection" -> QualifiedName <$> field "struct"    <*> fields "index"
        "map_lookup"      -> QualifiedName <$> field "container" <*> fields "index"
        "module_field"    -> QualifiedName <$> field "module"    <*> fields "method"
        _                 -> fallthrough

    -- Literal
  , Descent do
      boilerplate' \case
        ("Int",    i) -> return $ Int i
        ("Nat",    i) -> return $ Nat i
        ("Bytes",  i) -> return $ Bytes i
        ("String", i) -> return $ String i
        ("Tez",    i) -> return $ Tez i
        _             -> fallthrough

    -- Declaration
  , Descent do
      boilerplate \case
        "fun_decl"   -> Function <$> flag "recursive" <*> field "name" <*> fields "parameters" <*> fieldOpt "type" <*> field "body"
        "const_decl" -> Const    <$>             field    "name"       <*> fieldOpt "type" <*> field "value"
        "var_decl"   -> Var      <$>             field    "name"       <*> fieldOpt "type" <*> field "value"
        "type_decl"  -> TypeDecl <$>             field    "typeName"   <*> field "typeValue"
        "include"    -> Include  <$>             field    "filename"
        _            -> fallthrough

    -- Parameters
  , Descent do
      boilerplate \case
        "parameters" -> Parameters <$> fields "parameter"
        _            -> fallthrough

    -- VarDecl
  , Descent do
      boilerplate \case
        "param_decl" -> Decl <$> field "access" <*> field "name" <*> fieldOpt "type"
        _            -> fallthrough

    -- Mutable
  , Descent do
      boilerplate \case
        "const" -> return Immutable
        "var"   -> return Mutable
        _       -> fallthrough

    -- Name
  , Descent do
      boilerplate' \case
        ("Name", n) -> return $ Name n
        ("and", _)  -> return $ Name "and"
        ("or", _)   -> return $ Name "or"
        _           -> fallthrough

    -- Type
  , Descent do
      boilerplate \case
        "fun_type"         -> TArrow   <$> field  "domain"     <*> field "codomain"
        "cartesian"        -> TProduct <$> fields "element"
        "invokeBinary"     -> TApply   <$> field  "typeConstr" <*> fields "arguments"
        "invokeUnary"      -> TApply   <$> field  "typeConstr" <*> fields "arguments"
        "type_tuple"       -> TTuple   <$> fields "element"
        "record_type"      -> TRecord  <$> fields "field"
        "sum_type"         -> TSum     <$> fields "variant"
        "michelsonTypeOr"  -> TOr      <$> field "left_type" <*> field "left_type_name" <*> field "right_type" <*> field "right_type_name"
        "michelsonTypeAnd" -> TAnd     <$> field "left_type" <*> field "left_type_name" <*> field "right_type" <*> field "right_type_name"
        _                 -> fallthrough

    -- Variant
  , Descent do
      boilerplate \case
        "variant" -> Variant <$> field "constructor" <*> fieldOpt "arguments"
        _         -> fallthrough

    -- TField
  , Descent do
      boilerplate \case
        "field_decl" -> TField <$> field "fieldName" <*> field "fieldType"
        _            -> fallthrough

    -- TypeName
  , Descent do
      boilerplate' \case
        ("TypeName", name) -> return $ TypeName name
        ("list",     _)    -> return $ TypeName "list"
        ("big_map",  _)    -> return $ TypeName "big_map"
        ("map",      _)    -> return $ TypeName "map"
        ("set",      _)    -> return $ TypeName "set"
        ("option",   _)    -> return $ TypeName "option"
        ("contract", _)    -> return $ TypeName "contract"
        _                  -> fallthrough

    -- Ctor
  , Descent do
      boilerplate' \case
        ("Name_Capital", name) -> return $ Ctor name
        ("Some", _)            -> return $ Ctor "Some"
        ("Some_pattern", _)    -> return $ Ctor "Some"
        ("None", _)            -> return $ Ctor "None"
        ("True", _)            -> return $ Ctor "True"
        ("False", _)           -> return $ Ctor "False"
        ("Unit", _)            -> return $ Ctor "Unit"
        ("constr", n)          -> return $ Ctor n
        _                      -> fallthrough

    -- FieldName
  , Descent do
      boilerplate' \case
        ("FieldName", name) -> return $ FieldName name
        _                   -> fallthrough

    -- Err
  , Descent do
      \(r :> _, ParseTree _ _ text') -> do
        withComments do
          return (r :> N :> Nil, Err text')

  , Descent do
      \case
        (r :> _, ParseTree "ERROR" _ text') -> do
          return ([] :> r :> Y :> Nil, Err text')

        _ -> fallthrough
  ]