-- | This module exposes the notion of redeemers used whenever a script in
-- invoked in a 'Cooked.Skeleton.TxSkel'.
module Cooked.Skeleton.Redeemer
  ( -- * Type constraints
    RedeemerConstrs,

    -- * Data types
    TxSkelRedeemer (..),

    -- * Optics
    txSkelRedeemerMReferenceInputL,
    txSkelRedeemerReferenceInputAT,
    txSkelRedeemerAutoFillL,
    txSkelRedeemerTypedAT,
    txSkelRedeemerBuiltinDataL,

    -- * Smart constructors
    someTxSkelRedeemer,
    someTxSkelRedeemerNoAutoFill,
    emptyTxSkelRedeemer,
    emptyTxSkelRedeemerNoAutoFill,

    -- * Utilities
    fillReferenceInput,
  )
where

import Cooked.Pretty.Class
import Cooked.Pretty.Plutus ()
import Data.Typeable (Typeable, cast)
import Optics.Core
import Optics.TH
import PlutusLedgerApi.V3 qualified as Api
import PlutusTx.Prelude qualified as PlutusTx

-- * Types and constraints for redeemers used in skeletons

-- | These are the constraints that must be satisfied by the inner content of a
-- redeemer, that is the actual data that will be passed to the script as its
-- redeemer during during validation
type RedeemerConstrs redeemer =
  ( Api.ToData redeemer,
    Api.FromData redeemer,
    Show redeemer,
    PrettyCooked redeemer,
    PlutusTx.Eq redeemer,
    Typeable redeemer
  )

-- | A bundle around a redeemer which allows to provide a reference input in
-- which the script associated with the redeemer can be found
data TxSkelRedeemer where
  TxSkelRedeemer ::
    (RedeemerConstrs redeemer) =>
    { -- | The redeemer data with which the script will be executed
      ()
txSkelRedeemerContent :: redeemer,
      -- | An optional reference input containing the script to execute. During
      -- transaction generation, this reference input will only be translated
      -- into a Cardano reference input if it does not appear in regular inputs.
      TxSkelRedeemer -> Maybe TxOutRef
txSkelRedeemerReferenceInput :: Maybe Api.TxOutRef,
      -- | Whether the reference input can be automatically assigned. This will
      -- only trigger if 'txSkelRedeemerReferenceInput' is 'Nothing'
      TxSkelRedeemer -> Bool
txSkelRedeemerAutoFill :: Bool
    } ->
    TxSkelRedeemer

deriving instance (Show TxSkelRedeemer)

instance Eq TxSkelRedeemer where
  (TxSkelRedeemer redeemer
red Maybe TxOutRef
mRefIn Bool
af) == :: TxSkelRedeemer -> TxSkelRedeemer -> Bool
== TxSkelRedeemer redeemer
red' Maybe TxOutRef
mRefIn' Bool
af' =
    (redeemer -> BuiltinData
forall a. ToData a => a -> BuiltinData
Api.toBuiltinData redeemer
red, Maybe TxOutRef
mRefIn, Bool
af) (BuiltinData, Maybe TxOutRef, Bool)
-> (BuiltinData, Maybe TxOutRef, Bool) -> Bool
forall a. Eq a => a -> a -> Bool
== (redeemer -> BuiltinData
forall a. ToData a => a -> BuiltinData
Api.toBuiltinData redeemer
red', Maybe TxOutRef
mRefIn', Bool
af')

instance Ord TxSkelRedeemer where
  compare :: TxSkelRedeemer -> TxSkelRedeemer -> Ordering
compare (TxSkelRedeemer redeemer
red Maybe TxOutRef
mRefIn Bool
af) (TxSkelRedeemer redeemer
red' Maybe TxOutRef
mRefIn' Bool
af') =
    (BuiltinData, Maybe TxOutRef, Bool)
-> (BuiltinData, Maybe TxOutRef, Bool) -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (redeemer -> BuiltinData
forall a. ToData a => a -> BuiltinData
Api.toBuiltinData redeemer
red, Maybe TxOutRef
mRefIn, Bool
af) (redeemer -> BuiltinData
forall a. ToData a => a -> BuiltinData
Api.toBuiltinData redeemer
red', Maybe TxOutRef
mRefIn', Bool
af')

-- * Navigating within a 'TxSkelRedeemer'

-- | Focuses on the possible reference input from a redeemer
makeLensesFor [("txSkelRedeemerReferenceInput", "txSkelRedeemerMReferenceInputL")] ''TxSkelRedeemer

-- | Focuses on the reference input form a redeemer
txSkelRedeemerReferenceInputAT :: AffineTraversal' TxSkelRedeemer Api.TxOutRef
txSkelRedeemerReferenceInputAT :: AffineTraversal' TxSkelRedeemer TxOutRef
txSkelRedeemerReferenceInputAT = Lens' TxSkelRedeemer (Maybe TxOutRef)
txSkelRedeemerMReferenceInputL Lens' TxSkelRedeemer (Maybe TxOutRef)
-> Optic
     A_Prism NoIx (Maybe TxOutRef) (Maybe TxOutRef) TxOutRef TxOutRef
-> AffineTraversal' TxSkelRedeemer TxOutRef
forall k l m (is :: IxList) (js :: IxList) (ks :: IxList) s t u v a
       b.
(JoinKinds k l m, AppendIndices is js ks) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Optic
  A_Prism NoIx (Maybe TxOutRef) (Maybe TxOutRef) TxOutRef TxOutRef
forall a b. Prism (Maybe a) (Maybe b) a b
_Just

-- | Sets or gets the autofill property from a redeemer
makeLensesFor [("txSkelRedeemerAutoFill", "txSkelRedeemerAutoFillL")] ''TxSkelRedeemer

-- | Extracts, or sets, the typed redeemer of a 'TxSkelRedeemer'. This is
-- attempted in two ways: first, we try to simply cast the content, and then, if
-- it fails, we serialise the content and then attempt to deserialise it to the
-- right type. This second case is specifically useful when the current content
-- is an 'Api.BuiltinData' itself directly, but it can also be used in the
-- cornercase when both types have compatible serialized representation.
txSkelRedeemerTypedAT :: (RedeemerConstrs a, RedeemerConstrs b) => AffineTraversal TxSkelRedeemer TxSkelRedeemer a b
txSkelRedeemerTypedAT :: forall a b.
(RedeemerConstrs a, RedeemerConstrs b) =>
AffineTraversal TxSkelRedeemer TxSkelRedeemer a b
txSkelRedeemerTypedAT =
  (TxSkelRedeemer -> Either TxSkelRedeemer a)
-> (TxSkelRedeemer -> b -> TxSkelRedeemer)
-> AffineTraversal TxSkelRedeemer TxSkelRedeemer a b
forall s t a b.
(s -> Either t a) -> (s -> b -> t) -> AffineTraversal s t a b
atraversal
    ( \case
        (TxSkelRedeemer redeemer
content Maybe TxOutRef
_ Bool
_) | Just a
content' <- redeemer -> Maybe a
forall a b. (Typeable a, Typeable b) => a -> Maybe b
cast redeemer
content -> a -> Either TxSkelRedeemer a
forall a b. b -> Either a b
Right a
content'
        (TxSkelRedeemer redeemer
content Maybe TxOutRef
_ Bool
_) | Just a
content' <- BuiltinData -> Maybe a
forall a. FromData a => BuiltinData -> Maybe a
Api.fromBuiltinData (BuiltinData -> Maybe a) -> BuiltinData -> Maybe a
forall a b. (a -> b) -> a -> b
$ redeemer -> BuiltinData
forall a. ToData a => a -> BuiltinData
Api.toBuiltinData redeemer
content -> a -> Either TxSkelRedeemer a
forall a b. b -> Either a b
Right a
content'
        TxSkelRedeemer
txSkelRed -> TxSkelRedeemer -> Either TxSkelRedeemer a
forall a b. a -> Either a b
Left TxSkelRedeemer
txSkelRed
    )
    (\TxSkelRedeemer
red b
content -> TxSkelRedeemer
red {txSkelRedeemerContent = content})

-- | Extracts, or sets, the redeemer content as an `Api.BuiltinData`
txSkelRedeemerBuiltinDataL :: Lens' TxSkelRedeemer Api.BuiltinData
txSkelRedeemerBuiltinDataL :: Lens' TxSkelRedeemer BuiltinData
txSkelRedeemerBuiltinDataL =
  (TxSkelRedeemer -> BuiltinData)
-> (TxSkelRedeemer -> BuiltinData -> TxSkelRedeemer)
-> Lens' TxSkelRedeemer BuiltinData
forall s a b t. (s -> a) -> (s -> b -> t) -> Lens s t a b
lens
    (\(TxSkelRedeemer redeemer
content Maybe TxOutRef
_ Bool
_) -> redeemer -> BuiltinData
forall a. ToData a => a -> BuiltinData
Api.toBuiltinData redeemer
content)
    (\TxSkelRedeemer
txSkelRed BuiltinData
bData -> TxSkelRedeemer
txSkelRed {txSkelRedeemerContent = bData})

-- * Building 'TxSkelRedeemer's

-- | Creates a 'TxSkelRedeemer' from an inner content with no reference input
someTxSkelRedeemer :: (RedeemerConstrs redeemer) => redeemer -> TxSkelRedeemer
someTxSkelRedeemer :: forall redeemer.
RedeemerConstrs redeemer =>
redeemer -> TxSkelRedeemer
someTxSkelRedeemer redeemer
red = redeemer -> Maybe TxOutRef -> Bool -> TxSkelRedeemer
forall redeemer.
RedeemerConstrs redeemer =>
redeemer -> Maybe TxOutRef -> Bool -> TxSkelRedeemer
TxSkelRedeemer redeemer
red Maybe TxOutRef
forall a. Maybe a
Nothing Bool
True

-- | Creates a 'TxSkelRedeemer' from an inner content with no reference input,
-- while not allowing it to be automatically assigned
someTxSkelRedeemerNoAutoFill :: (RedeemerConstrs redeemer) => redeemer -> TxSkelRedeemer
someTxSkelRedeemerNoAutoFill :: forall redeemer.
RedeemerConstrs redeemer =>
redeemer -> TxSkelRedeemer
someTxSkelRedeemerNoAutoFill redeemer
red = redeemer -> Maybe TxOutRef -> Bool -> TxSkelRedeemer
forall redeemer.
RedeemerConstrs redeemer =>
redeemer -> Maybe TxOutRef -> Bool -> TxSkelRedeemer
TxSkelRedeemer redeemer
red Maybe TxOutRef
forall a. Maybe a
Nothing Bool
False

-- | Creates a 'TxSkelRedeemer' without an inner content nor a reference input
emptyTxSkelRedeemer :: TxSkelRedeemer
emptyTxSkelRedeemer :: TxSkelRedeemer
emptyTxSkelRedeemer = () -> TxSkelRedeemer
forall redeemer.
RedeemerConstrs redeemer =>
redeemer -> TxSkelRedeemer
someTxSkelRedeemer ()

-- | Creates a 'TxSkelRedeemer' with no inner content and no reference input,
-- while dissallowing it to be automatically assinged
emptyTxSkelRedeemerNoAutoFill :: TxSkelRedeemer
emptyTxSkelRedeemerNoAutoFill :: TxSkelRedeemer
emptyTxSkelRedeemerNoAutoFill = () -> TxSkelRedeemer
forall redeemer.
RedeemerConstrs redeemer =>
redeemer -> TxSkelRedeemer
someTxSkelRedeemerNoAutoFill ()

-- * Additional helpers

-- | Attaches a reference input to this 'TxSkelRedeemer' when none is already
-- attached. Is meant to be used by the automated attachment process during
-- transaction generation.
fillReferenceInput :: Api.TxOutRef -> TxSkelRedeemer -> TxSkelRedeemer
fillReferenceInput :: TxOutRef -> TxSkelRedeemer -> TxSkelRedeemer
fillReferenceInput TxOutRef
refInput = Lens' TxSkelRedeemer (Maybe TxOutRef)
-> (Maybe TxOutRef -> Maybe TxOutRef)
-> TxSkelRedeemer
-> TxSkelRedeemer
forall k (is :: IxList) s t a b.
Is k A_Setter =>
Optic k is s t a b -> (a -> b) -> s -> t
over Lens' TxSkelRedeemer (Maybe TxOutRef)
txSkelRedeemerMReferenceInputL (Maybe TxOutRef
-> (TxOutRef -> Maybe TxOutRef) -> Maybe TxOutRef -> Maybe TxOutRef
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (TxOutRef -> Maybe TxOutRef
forall a. a -> Maybe a
Just TxOutRef
refInput) TxOutRef -> Maybe TxOutRef
forall a. a -> Maybe a
Just)