{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.Bitcoin.Miniscript.Parser (
    miniscriptParser,
    parseMiniscript,
) where

import Control.Applicative ((<|>))
import Control.Monad (void)
import Data.Attoparsec.Text (Parser)
import qualified Data.Attoparsec.Text as A
import Data.Text (Text, pack)
import Haskoin.Constants (Network)

import Language.Bitcoin.Miniscript.Syntax (
    Miniscript (..),
    Value (..),
 )
import Language.Bitcoin.Script.Descriptors.Parser (keyDescriptorParser)
import Language.Bitcoin.Utils (
    alphanum,
    application,
    argList,
    comma,
    hex,
    spacePadded,
 )

parseMiniscript :: Network -> Text -> Either String Miniscript
parseMiniscript net = A.parseOnly $ miniscriptParser net

miniscriptParser :: Network -> Parser Miniscript
miniscriptParser net = annotP expression <|> expression
  where
    expression =
        keyP <|> keyCP <|> keyHP <|> keyHCP <|> olderP <|> afterP
            <|> sha256P
            <|> ripemd160P
            <|> hash256P
            <|> hash160P
            <|> andOrP
            <|> andVP
            <|> andBP
            <|> orBP
            <|> orCP
            <|> orDP
            <|> orIP
            <|> threshP
            <|> multiP
            <|> numberP
            <|> trueP
            <|> falseP
            <|> bytesP
            <|> keyDescriptorP
            <|> letP
            <|> varP

    trueP = Boolean True <$ A.char '1'
    falseP = Boolean False <$ A.char '0'

    numberP = Number <$> A.decimal
    bytesP = Bytes <$> hex

    keyDescriptorP = KeyDesc <$> keyDescriptorParser net

    keyP = Key <$> application "pk_k" atomicKeyDescP
    keyCP = AnnC . Key <$> application "pk" atomicKeyDescP

    keyHP = KeyH <$> application "pk_h" atomicKeyDescP
    keyHCP = AnnC . KeyH <$> application "pkh" atomicKeyDescP

    olderP = Older <$> application "older" atomicNumberP
    afterP = After <$> application "after" atomicNumberP

    sha256P = Sha256 <$> application "sha256" atomicBytesP
    ripemd160P = Ripemd160 <$> application "ripemd160" atomicBytesP
    hash256P = Hash256 <$> application "hash256" atomicBytesP
    hash160P = Hash160 <$> application "hash160" atomicBytesP

    andOrP =
        application "andor" $
            AndOr <$> mp
                <*> comma mp
                <*> comma mp

    andVP = application "and_v" $ AndV <$> mp <*> comma mp
    andBP = application "and_b" $ AndB <$> mp <*> comma mp
    orBP = application "or_b" $ OrB <$> mp <*> comma mp
    orCP = application "or_c" $ OrC <$> mp <*> comma mp
    orDP = application "or_d" $ OrD <$> mp <*> comma mp
    orIP = application "or_i" $ OrI <$> mp <*> comma mp

    varP = Var <$> varIdentP
    varIdentP = pack <$> A.many' (alphanum <|> A.char '_')

    letP = do
        void $ A.string "let"
        Let <$> spacePadded varIdentP
            <*> (A.char '=' >> spacePadded mp)
            <*> (A.string "in" >> spacePadded mp)

    threshP =
        application "thresh" $
            Thresh <$> atomicNumberP <*> comma mp <*> comma (argList mp)

    multiP =
        application "multi" $
            Multi <$> atomicNumberP <*> comma (argList atomicKeyDescP)

    atomicNumberP = (Lit <$> A.decimal) <|> (Variable <$> varIdentP)
    atomicBytesP = (Lit <$> hex) <|> (Variable <$> varIdentP)
    atomicKeyDescP = (Lit <$> keyDescriptorParser net) <|> (Variable <$> varIdentP)

    annotP p = do
        anns <- calcAnnotation <$> annPrefixP
        anns <$> p

    annPrefixP = A.many' (spacePadded $ A.satisfy isAnn) <* spacePadded (A.char ':')

    calcAnnotation = flip $ foldr toAnn

    toAnn = \case
        'a' -> AnnA
        's' -> AnnS
        'c' -> AnnC
        'd' -> AnnD
        'v' -> AnnV
        'j' -> AnnJ
        'n' -> AnnN
        't' -> (`AndV` Boolean True)
        'l' -> OrI (Boolean False)
        'u' -> (`OrI` Boolean False)
        _ -> error "unexpected annotation"

    isAnn = A.inClass "asctdvjnlu"

    mp = miniscriptParser net
