{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}

module Ethereum.Analyzer.Solidity.Simple
  ( Contract(..)
  , VarDecl(..)
  , Idfr(..)
  , LValue(..)
  , VarType(..)
  , FunDefinition(..)
  , Statement(..)
  , Expression(..)
  , s2sContracts
  , decodeContracts
  ) where

import Protolude hiding (show)

import Compiler.Hoopl
import Ethereum.Analyzer.Common
import Ethereum.Analyzer.Solidity.AstJson
import GHC.Show (Show(..))
import qualified Text.PrettyPrint as PP
import qualified Text.PrettyPrint.GenericPretty as GP

decodeContracts :: Text -> Either Text [Contract]
decodeContracts astJsonText = do
  solNodes <- decodeSoleNodes (toS astJsonText)
  let mContracts = mapM s2sContracts solNodes
  let contracts = concat $ runSimpleUniqueMonad mContracts
  return contracts

data Contract = Contract
  { cName :: Text
  , cStateVars :: [VarDecl]
  , cFunctions :: [FunDefinition]
  } deriving (Eq, Generic, Show, GP.Out)

data VarDecl = VarDecl
  { vName :: Idfr
  , vType :: VarType
  } deriving (Eq, Generic, Show, GP.Out)

newtype Idfr =
  Idfr Text
  deriving (Eq, Generic, Show, GP.Out)

data LValue
  = JustId Idfr
  | Index { iArray :: LValue
         ,  iIndex :: LValue}
  | Member { mObj :: LValue
          ,  mField :: Idfr}
  | Tuple [LValue]
  deriving (Eq, Generic, Show, GP.Out)

data VarType
  = Int256
  | Uint256
  | Address
  | Mapping VarType
            VarType
  | Unknown Text
  deriving (Eq, Generic, Show, GP.Out)

data FunDefinition = FunDefinition
  { fName :: Idfr
  , fParams :: [VarDecl]
  , fReturns :: [VarDecl]
  , fBody :: [Statement]
  } deriving (Eq, Generic, Show, GP.Out)

data Statement
  = StLocalVarDecl VarDecl
  | StAssign LValue
             Expression
  | StIf LValue
         [Statement]
         [Statement]
  | StLoop [Statement]
  | StBreak
  | StContinue
  | StReturn [LValue]
  | StDelete LValue
  | StTodo Text
  | StThrow
  deriving (Eq, Generic, Show, GP.Out)

data Expression
  = ExpUnary Text
             LValue
  | ExpBin Text
           LValue
           LValue
  | ExpLiteral Text
  | ExpLval LValue
  | ExpCall LValue
            [LValue]
  deriving (Eq, Generic, Show, GP.Out)

instance GP.Out Text where
  doc = PP.quotes . PP.text . toS
  docPrec _ = GP.doc

instance GP.Out SolNode

s2sContracts
  :: UniqueMonad m
  => SolNode -> m [Contract]
s2sContracts SolNode {_AST = Just n} = s2sContracts n
s2sContracts SolNode {name = Just "SourceUnit", children = Just sChildren} =
  concat <$> mapM s2sContracts sChildren
s2sContracts SolNode { name = Just "ContractDefinition"
                     , children = Just vChildren
                     , attributes = Just SolNode {name = Just cName}
                     } = do
  (vars, funs) <- s2sVarsFuns vChildren
  return [Contract cName vars funs]
  where
    s2sVarsFuns
      :: UniqueMonad m
      => [SolNode] -> m ([VarDecl], [FunDefinition])
    s2sVarsFuns [] = return ([], [])
    s2sVarsFuns (h:t) = do
      (vars', funs') <- s2sVarsFuns t
      hFuns <- s2sFuns h
      return (s2sVarDecls h <> vars', hFuns <> funs')
s2sContracts _ = return []

-- s2sContracts n = unexpectedPanic n
s2sVarDecls :: SolNode -> [VarDecl]
s2sVarDecls SolNode { name = Just "VariableDeclaration"
                    , attributes = Just SolNode { name = Just vName
                                                , _type = Just vType
                                                }
                    } = [VarDecl (Idfr vName) (Unknown vType)]
s2sVarDecls SolNode {name = Just "ParameterList", children = Just pChildren} =
  concatMap s2sVarDecls pChildren
s2sVarDecls _ = []

s2sFuns
  :: UniqueMonad m
  => SolNode -> m [FunDefinition]
s2sFuns SolNode { name = Just "FunctionDefinition"
                , children = Just [params, returns, body]
                , attributes = Just SolNode {name = Just fName}
                } = do
  sBody <- s2sStatements body
  return
    [ FunDefinition
        (Idfr fName)
        (s2sVarDecls params)
        (s2sVarDecls returns)
        sBody
    ]
s2sFuns _ = return []

s2sStatements
  :: UniqueMonad m
  => SolNode -> m [Statement]
s2sStatements SolNode {name = Just "Block", children = Just sChildren} =
  concat <$> mapM s2sStatements sChildren
s2sStatements SolNode { name = Just "ExpressionStatement"
                      , children = Just sChildren
                      } = concat <$> mapM s2sStatements sChildren
s2sStatements SolNode { name = Just "Assignment"
                      , children = Just [lval, rval]
                      , attributes = Just SolNode {operator = Just op}
                      } = do
  (prelval, simpleLval) <- s2sLval lval
  (prerval, simpleRval) <- s2sLval rval
  return $ prerval <> prelval <> [StAssign simpleLval $ ExpLval simpleRval]
s2sStatements e@SolNode {name = Just "Return", children = ch} = do
  let sChildren = fromMaybe [] ch
  presAndRvals <- mapM s2sLval sChildren
  let prerval = concatMap fst presAndRvals
  let simpleRvals = map snd presAndRvals
  return $ prerval <> [StReturn simpleRvals]
s2sStatements e@SolNode { name = Just "UnaryOperation"
                        , children = Just [op1]
                        , attributes = Just SolNode {operator = Just "delete"}
                        } = do
  (preOp1, lvalOp1) <- s2sLval op1
  newVar <- uniqueVar
  return $ preOp1 <> [StDelete (JustId $ Idfr newVar)]
s2sStatements e@SolNode { name = Just "UnaryOperation"
                        , children = Just [SolNode { name = Just "Identifier"
                                                   , attributes = Just SolNode {value = Just idName}
                                                   }]
                        , attributes = Just SolNode {operator = Just "++"}
                        } = do
  let idfr = JustId $ Idfr idName
  newVar <- uniqueVar
  let newidfr = JustId $ Idfr newVar
  return
    [StAssign newidfr $ ExpLiteral "1", StAssign idfr $ ExpBin "+" idfr newidfr]
s2sStatements SolNode { name = Just "UnaryOperation"
                      , children = Just [SolNode { name = Just "Identifier"
                                                 , attributes = Just SolNode {value = Just idName}
                                                 }]
                      , attributes = Just SolNode {operator = Just "--"}
                      } = do
  let idfr = JustId $ Idfr idName
  newVar <- uniqueVar
  let newidfr = JustId $ Idfr newVar
  return
    [StAssign newidfr $ ExpLiteral "1", StAssign idfr $ ExpBin "-" idfr newidfr]
s2sStatements SolNode { name = Just "IfStatement"
                      , children = Just [cond, thenBr]
                      } = do
  (precond, lvalcond) <- s2sLval cond
  thenSts <- s2sStatements thenBr
  return $ precond <> [StIf lvalcond thenSts []]
s2sStatements SolNode { name = Just "IfStatement"
                      , children = Just [cond, thenBr, elseBr]
                      } = do
  (precond, lvalcond) <- s2sLval cond
  thenSts <- s2sStatements thenBr
  elseSts <- s2sStatements elseBr
  return $ precond <> [StIf lvalcond thenSts elseSts]
s2sStatements SolNode { name = Just "WhileStatement"
                      , children = Just [cond, body]
                      } = do
  (precond, lvalcond) <- s2sLval cond
  bodySts <- s2sStatements body
  return [StLoop (precond <> [StIf lvalcond bodySts [StBreak]])]
s2sStatements SolNode { name = Just "DoWhileStatement"
                      , children = Just [cond, body]
                      } = do
  (precond, lvalcond) <- s2sLval cond
  bodySts <- s2sStatements body
  return [StLoop (bodySts <> precond <> [StIf lvalcond [StContinue] [StBreak]])]
s2sStatements SolNode { name = Just "ForStatement"
                      , children = Just [inits, cond, iters, body]
                      } = do
  initSts <- s2sStatements inits
  (precond, lvalcond) <- s2sLval cond
  iterSts <- s2sStatements iters
  bodySts <- s2sStatements body
  return $
    initSts <>
    [StLoop (precond <> [StIf lvalcond (bodySts <> iterSts) [StBreak]])]
s2sStatements SolNode {name = Just "Break"} = return [StBreak]
s2sStatements SolNode {name = Just "Continue"} = return [StContinue]
s2sStatements SolNode { name = Just "VariableDeclarationStatement"
                      , children = Just [vdec@SolNode {name = Just "VariableDeclaration"}, vinit]
                      } = do
  let varDecl@(VarDecl vId _):_ =
        s2sVarDecls vdec -- because VariableDeclaration only emits one decl
  (preDef, lvalDef) <- s2sLval vinit
  return $
    preDef <> [StLocalVarDecl varDecl] <>
    [StAssign (JustId vId) (ExpLval lvalDef)]
s2sStatements n@SolNode {name = Just "FunctionCall"} = do
  (precall, lvalcall) <- s2sLval n
  return $ precall <> [StAssign (JustId $ Idfr "_") (ExpLval lvalcall)]
s2sStatements SolNode {name = Just "VariableDeclarationStatement"}
  -- TODO(zchn): Handle this properly.
 = return []
s2sStatements SolNode {name = Just "Throw"} = return [StThrow]
s2sStatements s = unimplementedPanic s {children = Nothing}

s2sLval
  :: UniqueMonad m
  => SolNode -> m ([Statement], LValue)
s2sLval SolNode { name = Just "Identifier"
                , attributes = Just SolNode {value = Just idName}
                } = return ([], JustId (Idfr idName))
s2sLval SolNode { name = Just "MemberAccess"
                , children = Just [obj]
                , attributes = Just SolNode { _type = Just _
                                            , member_name = Just mName
                                            }
                } = do
  (prelval, simpleLval) <- s2sLval obj
  return (prelval, Member simpleLval (Idfr mName))
s2sLval n@SolNode {name = Just "IndexAccess", children = Just (c1:ctail)} = do
  (prelval, simpleLval) <- s2sLval c1
  (presub, simpleLvalSub) <- handleSubscription simpleLval ctail
  return (presub <> prelval, Index simpleLval simpleLvalSub)
  where
    handleSubscription
      :: UniqueMonad m
      => LValue -> [SolNode] -> m ([Statement], LValue)
    handleSubscription lv [] = unexpectedPanic lv
    handleSubscription lv [subNode] = do
      (presub', simpleLvalSub') <- s2sLval subNode
      return (presub', Index lv simpleLvalSub')
    handleSubscription lv (subNode:t) = do
      (presub', simpleLvalSub') <- handleSubscription lv [subNode]
      (presub'', simpleLvalSub'') <- handleSubscription simpleLvalSub' t
      return (presub'' <> presub', simpleLvalSub'')
s2sLval SolNode { name = Just "UnaryOperation"
                , children = Just [op1]
                , attributes = Just SolNode {operator = Just vOp}
                } = do
  (preOp1, lvalOp1) <- s2sLval op1
  newVar <- uniqueVar
  return
    ( preOp1 <> [StAssign (JustId $ Idfr newVar) (ExpUnary vOp lvalOp1)]
    , JustId $ Idfr newVar)
s2sLval SolNode { name = Just "BinaryOperation"
                , children = Just [op1, op2]
                , attributes = Just SolNode {operator = Just vOp}
                } = do
  (preOp1, lvalOp1) <- s2sLval op1
  (preOp2, lvalOp2) <- s2sLval op2
  newVar <- uniqueVar
  return
    ( preOp1 <> preOp2 <>
      [StAssign (JustId $ Idfr newVar) (ExpBin vOp lvalOp1 lvalOp2)]
    , JustId $ Idfr newVar)
s2sLval SolNode { name = Just "Conditional"
                , children = Just [cond, opThen, opElse]
                } = do
  (preCond, lvalCond) <- s2sLval cond
  (preOpThen, lvalOpThen) <- s2sLval opThen
  (preOpElse, lvalOpElse) <- s2sLval opElse
  opVar <- uniqueVar
  return
    ( preCond <>
      [ StIf
          lvalCond
          (preOpThen <> [StAssign (JustId $ Idfr opVar) (ExpLval lvalOpThen)])
          (preOpElse <> [StAssign (JustId $ Idfr opVar) (ExpLval lvalOpElse)])
      ]
    , JustId $ Idfr opVar)
s2sLval SolNode {name = Just "FunctionCall", children = Just (func:params)} = do
  (preFun, lvalFun) <- s2sLval func
  preAndlvals <- mapM s2sLval params
  let preArgs = concatMap fst preAndlvals -- TODO(zchn): reverse?
  let lvalArgs = map snd preAndlvals
  newVar <- uniqueVar
  return
    ( preArgs <> preFun <>
      [StAssign (JustId $ Idfr newVar) (ExpCall lvalFun lvalArgs)]
    , JustId $ Idfr newVar)
s2sLval SolNode { name = Just "Literal"
                , attributes = Just SolNode {value = Just vValue}
                } = do
  newVar <- uniqueVar
  return
    ( [StAssign (JustId $ Idfr newVar) (ExpLiteral vValue)]
    , JustId $ Idfr newVar)
s2sLval SolNode { name = Just "ElementaryTypeNameExpression"
                , attributes = Just SolNode {value = Just v}
                } = return ([], JustId $ Idfr v)
-- For TupleExpression @ solc-0.4.11
s2sLval SolNode {name = Just "TupleExpression", children = Just elems} = do
  preAndlvals <- mapM s2sLval elems
  let preArgs = concatMap fst preAndlvals -- TODO(zchn): reverse?
  let lvalArgs = map snd preAndlvals
  return (preArgs, Tuple lvalArgs)
-- For TupleExpression @ solc-0.4.17
s2sLval SolNode { name = Just "TupleExpression"
                , attributes = Just SolNode {components = Just maybeComps}
                } = do
  let comps = catMaybes maybeComps
  preAndlvals <- mapM s2sLval comps
  let preArgs = concatMap fst preAndlvals -- TODO(zchn): reverse?
  let lvalArgs = map snd preAndlvals
  return (preArgs, Tuple lvalArgs)
s2sLval n = unimplementedPanic n {children = Nothing}

uniqueVar
  :: UniqueMonad m
  => m Text
uniqueVar = ("v" <>) . toS . show <$> freshUnique

-- lastLvalOf :: [Statement] -> LValue
-- lastLvalOf [_, StAssign lval _] = lval
-- lastLvalOf (_ : t) = lastLvalOf t
-- lastLvalOf _ = errorLValue
errorLValue = JustId (Idfr "ERROR!")