-- | This module contains various datatypes and functions which are
-- common for contract registry packages (e.g. morley-ledgers and morley-multisig).
module Lorentz.ContractRegistry
  ( ContractInfo (..)
  , ContractRegistry (..)
  , getContract
  , printContractFromRegistryDoc
  , (?::)
  -- Common CLI stuff
  , CmdLnArgs (..)
  , argParser
  ) where

import qualified Data.Map as Map
import qualified Data.Text.Lazy.IO as TL
import Fmt (Buildable(..), blockListF, nameF, (+|), (|+))
import qualified Options.Applicative as Opt

import Lorentz.Base
import Lorentz.Constraints
import Lorentz.Doc
import Util.IO

data ContractInfo =
  forall cp st.
    (NiceParameterFull cp, NiceStorage st) =>
  ContractInfo
  { ciContract :: Contract cp st
  , ciIsDocumented :: Bool
  }

(?::) :: Text -> a -> (Text, a)
(?::) = (,)

newtype ContractRegistry = ContractRegistry
  { unContractRegistry :: Map Text ContractInfo }

getContract :: Text -> ContractRegistry -> Either String ContractInfo
getContract name registry =
  case Map.lookup name (unContractRegistry registry) of
    Nothing ->
      Left $ "No contract with name '" +| name |+ "' found\n" +| registry |+ ""
    Just c -> Right c

instance Buildable ContractRegistry where
  build registry =
    nameF "Available contracts" (blockListF $ keys (unContractRegistry registry))

printContractFromRegistryDoc :: Text -> ContractRegistry -> Maybe FilePath -> IO ()
printContractFromRegistryDoc name contracts mOutput = do
  ContractInfo{..} <- either die pure $ getContract name contracts
  let writeFunc = case mOutput of
        Nothing -> TL.putStrLn
        Just "def" -> writeFileUtf8 $ toString name <> ".md"
        Just output -> writeFileUtf8 output
  if ciIsDocumented
  then writeFunc $ contractDocToMarkdown $ buildLorentzDoc ciContract
  else die "This contract is not documented"

data CmdLnArgs
  = List
  | Print Text (Maybe FilePath) Bool
  | Document Text (Maybe FilePath)

argParser :: Opt.Parser CmdLnArgs
argParser = Opt.subparser $ mconcat
  [ listSubCmd
  , printSubCmd
  , documentSubCmd
  ]
  where
    mkCommandParser commandName parser desc =
      Opt.command commandName $
      Opt.info (Opt.helper <*> parser) $
      Opt.progDesc desc

    listSubCmd =
      mkCommandParser "list"
      (pure List)
      "Show all available contracts"

    printSubCmd =
      mkCommandParser "print"
      (Print <$> printOptions <*> outputOptions <*> onelineOption)
      "Dump a contract in form of Michelson code"

    documentSubCmd =
      mkCommandParser "document"
      (Document <$> printOptions <*> outputOptions)
      "Dump contract documentation in Markdown"

    printOptions = Opt.strOption $ mconcat
      [ Opt.short 'n'
      , Opt.long "name"
      , Opt.metavar "IDENTIFIER"
      , Opt.help "Name of a contract returned by `list` command."
      ]

    outputOptions = optional . Opt.strOption $ mconcat
      [ Opt.short 'o'
      , Opt.long "output"
      , Opt.metavar "FILEPATH"
      , Opt.help "File to use as output. If not specified, stdout is used."
      ]

    onelineOption :: Opt.Parser Bool
    onelineOption = Opt.switch (
      Opt.long "oneline" <>
      Opt.help "Force single line output")