content: {- |
  Module     : Sample.Governor.Initialize
  Maintainer : connor@mlabs.city
  Description: Generate sample data for testing the functionalities of minting GST.
  
  Sample and utilities for testing the functionalities of minting GST.
  -}
  module Sample.Governor.Initialize (
    mintGST,
    Parameters (..),
    totallyValidParameters,
    invalidDatumTimingConfigParameters,
    invalidDatumMaxTimeRangeWidthParameters,
    invalidDatumThresholdsParameters,
    withoutGovernorDatumParameters,
    witnessNotPresentedParameters,
    mintMoreThanOneGSTParameters,
    mintGSTWithNoneEmptyNameParameters,
    mkTestCase,
  ) where
  
  import Agora.Bootstrap (agoraScripts)
  import Agora.Governor (Governor (..), GovernorDatum (..))
  import Agora.Linker (linker)
  import Agora.Proposal (ProposalId (..), ProposalThresholds (..))
  import Agora.Proposal.Time (
    MaxTimeRangeWidth (MaxTimeRangeWidth),
    ProposalTimingConfig (ProposalTimingConfig),
   )
  import Data.Default (Default (..))
  import Data.Map (Map, (!))
  import Data.Text (Text)
  import Optics (view)
  import Plutarch (Script)
  import Plutarch.Api.V2 (scriptHash)
  import Plutarch.Context (
    input,
    mint,
    output,
    pubKey,
    script,
    signedWith,
    txId,
    withDatum,
    withRef,
    withValue,
   )
  import PlutusLedgerApi.V1.Value (AssetClass (..))
  import PlutusLedgerApi.V1.Value qualified as Value
  import PlutusLedgerApi.V2 (
    CurrencySymbol (CurrencySymbol),
    ScriptHash,
    TxOutRef (TxOutRef),
    getScriptHash,
   )
  import Sample.Shared (
    deterministicTracingConfig,
    minAda,
   )
  import Sample.Shared qualified as Shared
  import ScriptExport.ScriptInfo (runLinker)
  import Test.Specification (SpecificationTree, testPolicy)
  import Test.Util (CombinableBuilder, mkMinting, pubKeyHashes, sortValue)
  
  -- | The parameters that control the generation of the transaction.
  data Parameters = Parameters
    { datumThresholdsValid :: Bool
    -- ^ Whether the 'GovernorDatum.proposalThresholds' field of the output
    --    governor datum is valid or not.
    , datumMaxTimeRangeWidthValid :: Bool
    -- ^ Whether the 'GovernorDatum.maximumCreatedProposalsPerStake'field of the
    --    output governor datum is valid or not.
    , datumTimingConfigValid :: Bool
    -- ^ Whether the 'GovernorDatum.proposalTimings'field  of the output
    --    governor datum is valid or not.
    , withGovernorDatum :: Bool
    -- ^ Whether the output GST UTxO will carry the governor datum.
    , presentWitness :: Bool
    -- ^ Whether to spend the UTxO referenced by 'Governor.gstOutRef'.
    , mintMoreThanOneStateToken :: Bool
    -- ^ More than one GST will be minted if this is set to true.
    , mintStateTokenWithName :: Bool
    -- ^ The token name of the GST won't be empty if this is set to true.
    }
  
  --------------------------------------------------------------------------------
  
  validGovernorOutputDatum :: GovernorDatum
  validGovernorOutputDatum =
    GovernorDatum
      { proposalThresholds = def
      , nextProposalId = ProposalId 0
      , proposalTimings = def
      , createProposalTimeRangeMaxWidth = def
      , maximumCreatedProposalsPerStake = 3
      }
  
  invalidProposalThresholds :: ProposalThresholds
  invalidProposalThresholds = ProposalThresholds (-1) (-1) (-1) (-1) (-1)
  
  invalidMaxTimeRangeWidth :: MaxTimeRangeWidth
  invalidMaxTimeRangeWidth = MaxTimeRangeWidth 0
  
  invalidProposalTimings :: ProposalTimingConfig
  invalidProposalTimings = ProposalTimingConfig (-1) (-1) (-1) (-1) (-1) (-1)
  
  witnessRef :: TxOutRef
  witnessRef = TxOutRef "b0353c22b0bd6c5296a8eef160ba25d90b5dc82a9bb8bdaa6823ffc19515d6ad" 0
  
  governor :: Governor
  governor =
    Shared.governor
      { gstOutRef = witnessRef
      }
  
  scripts :: Map Text Script
  scripts =
    either
      (error . show)
      (fmap (view #script) . view #scripts)
      ( runLinker
          linker
          (agoraScripts deterministicTracingConfig)
          governor
      )
  
  govPolicy :: Script
  govPolicy = scripts ! "agora:governorPolicy"
  
  govValidator :: Script
  govValidator = scripts ! "agora:governorValidator"
  
  govSymbol :: CurrencySymbol
  govSymbol = CurrencySymbol . getScriptHash $ scriptHash govPolicy
  
  govAssetClass :: AssetClass
  govAssetClass = AssetClass (govSymbol, "")
  
  govScriptHash :: ScriptHash
  govScriptHash = scriptHash govValidator
  
  --------------------------------------------------------------------------------
  
  mintGST :: forall b. CombinableBuilder b => Parameters -> b
  mintGST ps = builder
    where
      gstAC =
        if ps.mintStateTokenWithName
          then AssetClass (govSymbol, "12345")
          else govAssetClass
      gstCount =
        if ps.mintMoreThanOneStateToken
          then 10
          else 1
      gst = Value.assetClassValue gstAC gstCount
  
      ---
  
      governorOutputDatum =
        let th =
              if ps.datumThresholdsValid
                then def
                else invalidProposalThresholds
            trw =
              if ps.datumMaxTimeRangeWidthValid
                then def
                else invalidMaxTimeRangeWidth
            ptc =
              if ps.datumTimingConfigValid
                then def
                else invalidProposalTimings
         in validGovernorOutputDatum
              { proposalThresholds = th
              , proposalTimings = ptc
              , createProposalTimeRangeMaxWidth = trw
              }
  
      governorValue = sortValue $ gst <> minAda
  
      ---
  
      witnessValue = minAda
      witnessPubKey = head pubKeyHashes
  
      ---
  
      witnessBuilder =
        if ps.presentWitness
          then
            mconcat
              [ input $
                  mconcat
                    [ pubKey witnessPubKey
                    , withValue witnessValue
                    , withRef witnessRef
                    ]
              , output $
                  mconcat
                    [ pubKey witnessPubKey
                    , withValue witnessValue
                    ]
              ]
          else mempty
  
      ---
  
      govBuilder =
        let datum =
              if ps.withGovernorDatum
                then withDatum governorOutputDatum
                else mempty
         in output $
              mconcat
                [ script govScriptHash
                , withValue governorValue
                , datum
                ]
      --
      builder =
        mconcat
          [ txId "986b756ffb1c9839fc8d0b22a308ac91d5b5d0ebbfa683a47588c8a5cf70b5af"
          , signedWith (pubKeyHashes !! 1)
          , mint gst
          , govBuilder
          , witnessBuilder
          ]
  
  --------------------------------------------------------------------------------
  
  totallyValidParameters :: Parameters
  totallyValidParameters =
    Parameters
      { datumThresholdsValid = True
      , datumMaxTimeRangeWidthValid = True
      , datumTimingConfigValid = True
      , withGovernorDatum = True
      , presentWitness = True
      , mintMoreThanOneStateToken = False
      , mintStateTokenWithName = False
      }
  
  invalidDatumThresholdsParameters :: Parameters
  invalidDatumThresholdsParameters =
    totallyValidParameters
      { datumThresholdsValid = False
      }
  
  invalidDatumMaxTimeRangeWidthParameters :: Parameters
  invalidDatumMaxTimeRangeWidthParameters =
    totallyValidParameters
      { datumMaxTimeRangeWidthValid = False
      }
  
  invalidDatumTimingConfigParameters :: Parameters
  invalidDatumTimingConfigParameters =
    totallyValidParameters
      { datumTimingConfigValid = False
      }
  
  withoutGovernorDatumParameters :: Parameters
  withoutGovernorDatumParameters =
    totallyValidParameters
      { withGovernorDatum = False
      }
  
  witnessNotPresentedParameters :: Parameters
  witnessNotPresentedParameters =
    totallyValidParameters
      { presentWitness = False
      }
  
  mintMoreThanOneGSTParameters :: Parameters
  mintMoreThanOneGSTParameters =
    totallyValidParameters
      { mintMoreThanOneStateToken = True
      }
  
  mintGSTWithNoneEmptyNameParameters :: Parameters
  mintGSTWithNoneEmptyNameParameters =
    totallyValidParameters
      { mintStateTokenWithName = True
      }
  
  --------------------------------------------------------------------------------
  
  {- | Create a test tree that runs the governor policy to test the initialization
        of the governor.
  -}
  mkTestCase :: String -> Parameters -> Bool -> SpecificationTree
  mkTestCase name ps valid =
    testPolicy
      valid
      name
      govPolicy
      ()
      (mkMinting mintGST ps govSymbol)
  