module Ethereum.Executable.Analyze
  ( analyzeMain
  ) where

import Protolude hiding ((<.>))

import Compiler.Hoopl (runSimpleUniqueMonad)

import Data.Time.Clock
import Data.Time.Format

import Ethereum.Analyzer.Debug
import Ethereum.Analyzer.Solidity hiding (value)
import Ethereum.Analyzer.Util

import Options.Applicative
import Options.Applicative.Text

import System.Directory
import System.FilePath

import qualified Data.Text as T

data AnalyzeFlags = AnalyzeFlags
  { astJson :: Text
  , workDir :: Text
  , debug :: Bool
  } deriving (Eq, Show)

analyzeFlags :: Parser AnalyzeFlags
analyzeFlags =
  AnalyzeFlags <$>
  textOption
    (long "astJson" <> value "" <> metavar "PATH" <>
     help "Path to the ast-json file.") <*>
  textOption
    (long "workDir" <> value "work" <> metavar "PATH" <>
     help "Path to the work directory (for outputs and intermediate files).") <*>
  switch (long "debug" <> help "Whether to print debug info")

analyzeMain :: IO ()
analyzeMain = analyze =<< execParser opts
  where
    opts =
      info
        (analyzeFlags <**> helper)
        (fullDesc <>
         progDesc (toS ("Analyze the contract specified at PATH" :: Text)) <>
         header
           (toS ("ea-analyze - CLI interface for ethereum-analyzer" :: Text)))

analyze :: AnalyzeFlags -> IO ()
analyze flags@AnalyzeFlags { astJson = theAstJson
                           , workDir = theWorkDir
                           , debug = _debug
                           } = do
  tmpDirname <- getTmpDirname
  let sessionDir = toS theWorkDir </> toS tmpDirname
  createDirectoryIfMissing True sessionDir
  when _debug $ putText $ show flags
  content <-
    if theAstJson == "" || theAstJson == "-"
      then getContents
      else readFile $ toS theAstJson
  case decodeContracts content of
    Right contracts -> do
      savePrettyContracts contracts (toS $ sessionDir </> "contracts.ir")
      when _debug $ pprintContracts contracts
      saveCfgs contracts (toS $ sessionDir </> "cfgs")
      putText "Findings: \n"
      putText ("\n" `T.intercalate` concatMap findingsFor contracts)
    Left err -> putText err
  return ()

getTmpDirname :: IO Text
getTmpDirname = do
  t <- getCurrentTime
  let formated =
        formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S")) t
  return $ toS formated

savePrettyContracts :: [Contract] -> Text -> IO ()
savePrettyContracts cs filepath = writeFile (toS filepath) (prettyContracts cs)

saveCfgs :: [Contract] -> Text -> IO ()
saveCfgs cs dirpath = do
  createDirectoryIfMissing True (toS dirpath)
  let hcs = runSimpleUniqueMonad $ mapM hoopleOf cs
  _ <- mapM writeContractCfgs hcs
  return ()
  where
    writeContractCfgs hc = do
      _ <-
        mapM (writeFunCfgs (toS dirpath </> toS (hcName hc))) (hcFunctions hc)
      return ()
    writeFunCfgs contractPrefix hf = do
      let dot = toDotText (hfCFG hf)
      writeFile
        (contractPrefix <.> (toS $ unIdfr $ hfName hf) <.> "CFG" <.> ".dot")
        dot