{-# LANGUAGE QuasiQuotes     #-}
{-# LANGUAGE RecordWildCards #-}

module Main (main) where

import           Universum

import           Data.Text.Read (decimal)
import           Data.Version (showVersion)
import           NeatInterpolation (text)
import           Options.Applicative (Parser, execParser, footerDoc, fullDesc,
                     header, help, helper, info, infoOption, long, metavar,
                     option, optional, progDesc, short)
import           Options.Applicative.Types (readerAsk)
import           Text.PrettyPrint.ANSI.Leijen (Doc)

import           Paths_cardano_sl (version)
import           Pos.Core (makeRedeemAddress)
import           Pos.Core.NetworkMagic (NetworkMagic (..), makeNetworkMagic)
import           Pos.Crypto.Configuration (ProtocolMagic (..),
                     ProtocolMagicId (..), RequiresNetworkMagic (..))
import           Pos.Crypto.Signing (fromAvvmPk)
import           Pos.Util.Util (eitherToThrow)

data AddrConvertOptions = AddrConvertOptions
    { address       :: !(Maybe Text)
    , protocolMagic :: !Text
    }

optionsParser :: Parser AddrConvertOptions
optionsParser = do
    address <- optional $ textOption $
           short   'a'
        <> long    "address"
        <> help    "Address to convert. It must be in base64(url) format."
        <> metavar "STRING"
    pm      <- textOption $
           short   'p'
        <> long    "protocolMagic"
        <> help    "Generate mainnet or testnet address. Valid values include\
                    \ 'mainnet', 'staging', or an Int32 value to indicate a\
                    \ test network."
        <> metavar "STRING"
    return $ AddrConvertOptions address pm
    where
      textOption = option (toText <$> readerAsk)

getAddrConvertOptions :: IO AddrConvertOptions
getAddrConvertOptions = execParser programInfo
  where
    programInfo = info (helper <*> versionOption <*> optionsParser) $
        fullDesc <> progDesc  "Produce public key and write it in stdout."
                 <> header    "Tool to convert vending addresses into mainnet/testnet addresses."
                 <> footerDoc usageExample

    versionOption = infoOption
        ("cardano-addr-convert-" <> showVersion version)
        (long "version" <> help "Show version.")

usageExample :: Maybe Doc
usageExample = (Just . fromString @Doc . toString @Text) [text|
Command example:

  stack exec -- cardano-addr-convert -a 2HF83bvYCTzoCbVta6t64W8rFEnvnkJbIUFoT5tOyoU= -p mainnet

Output example:

  Ae2tdPwUPEZLTt73QktZUahV9FXnBRJNgzpbueBqQKGyPDgPdzPdwVretyk

You can also run it without the address argument to switch to interactive mode.
In this case each entered vending address is echoed with a testnet address.|]

convertAddr :: NetworkMagic -> Text -> IO Text
convertAddr nm addr =
    pretty . (makeRedeemAddress nm) <$>
    (eitherToThrow . fromAvvmPk) (toText addr)

-- Both mainnet & staging use NetworkMainOrStage. We explicity disallow provision of
-- their integer values, so as to avoid confusion with NetworkTestnet.
toNetworkMagic :: Text -> Either Text NetworkMagic
toNetworkMagic txt =
    case txt of
        "mainnet" -> Right NetworkMainOrStage
        "staging" -> Right NetworkMainOrStage

        num -> case decimal num of
            Right (pm, _)
                | pm == stagingProtocolMagic ->
                    Left "Got staging's ProtocolMagic;\
                        \ please enter 'staging' instead"
                | pm == mainnetProtocolMagic ->
                    Left "Got mainnet's ProtocolMagic;\
                        \ please enter 'mainnet' instead"
            Right (ident, _) ->
                Right (makeNetworkMagic (ProtocolMagic (ProtocolMagicId ident)
                                        RequiresMagic))
            Left err -> Left $ toText ("Please enter either 'mainnet', 'staging', or\
                                      \ an Int32 value: " ++ err)

-- As documented [here](https://iohk.myjetbrains.com/youtrack/issue/DEVOPS-844), these are
-- the ProtocolMagic Int32 values of mainnet & staging.
stagingProtocolMagic :: Int32
stagingProtocolMagic = 633343913

mainnetProtocolMagic :: Int32
mainnetProtocolMagic = 764824073

main :: IO ()
main = do
    AddrConvertOptions{..} <- getAddrConvertOptions
    case (address, toNetworkMagic protocolMagic) of
        (_        , Left txt) -> putText txt >> exitFailure
        (Just addr, Right nm) -> convertAddr nm addr >>= putText
        (Nothing  , Right nm) -> forever (getLine >>= convertAddr nm >>= putText)
