-- SPDX-FileCopyrightText: 2022 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

module Morley.Client.Action.Delegation
  ( setDelegateOp
  , registerDelegateOp
  ) where

import Colog.Core.Class (HasLog)
import Colog.Message (Message)

import Morley.Client.Action.Common
import Morley.Client.Action.Operation
import Morley.Client.RPC.Class
import Morley.Client.RPC.Types
import Morley.Client.TezosClient.Class
import Morley.Client.Types
import Morley.Tezos.Address
import Morley.Tezos.Crypto

-- | Set or revoke a delegate for the sender.
--   To set a supply @Just delegate@ as the second argument.
--   To withdraw a delegate supply @Nothing@ as the second argument.
--
--   Some notes on delegations:
--
--   Implicit Accounts can either be /registered delegates/, have no delegate,
--   or have a delegate set without being /registered delegates/ themselves.
--
--   For example imagine two implicit addresses Alice and Bob:
--   * Alice registers as delegate using `registerDelegateOp`. This means that Alice delegates to Alice.
--   * Bob sets Alice as his delegate. This delegates Bob's staking rights to Alice.
--
--   Now, Alice can't change their delegate, because that would revoke Alice as delegate and make
--   Bob's delegation void. So @setDelegateOp alice Nothing@ would throw a @FailedUnDelegation alice@ exception.
--
--   However, Bob, not beeing a /registered delegate/ can:
--   * Revoke the delegation to alice @setDelegateOp bob Nothing@
--   * Delegate to another "registered delegate": @registerDelegateOp charly >> setDelegateOp bob charly@
--   * Become a registered delegate himself: @registerDelegateOp bob@
--
--   Smart Contracts can also delegate to /registered delegates/, but can't be /registered delegates/ themselves.
--
setDelegateOp
  :: ( HasTezosRpc m
     , HasTezosClient m
     , MonadReader env m
     , HasLog env Message m)
  => ImplicitAddressWithAlias
  -> Maybe KeyHash
  -> m OperationHash
setDelegateOp :: forall (m :: * -> *) env.
(HasTezosRpc m, HasTezosClient m, MonadReader env m,
 HasLog env Message m) =>
ImplicitAddressWithAlias -> Maybe KeyHash -> m OperationHash
setDelegateOp ImplicitAddressWithAlias
sender Maybe KeyHash
delegate =
  ((OperationHash, NonEmpty (OperationInfo Result)) -> OperationHash)
-> m (OperationHash, NonEmpty (OperationInfo Result))
-> m OperationHash
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (OperationHash, NonEmpty (OperationInfo Result)) -> OperationHash
forall a b. (a, b) -> a
fst (m (OperationHash, NonEmpty (OperationInfo Result))
 -> m OperationHash)
-> (DelegationData
    -> m (OperationHash, NonEmpty (OperationInfo Result)))
-> DelegationData
-> m OperationHash
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ImplicitAddressWithAlias
-> NonEmpty (OperationInfo ClientInput)
-> m (OperationHash, NonEmpty (OperationInfo Result))
forall (m :: * -> *) env.
(HasTezosRpc m, HasTezosClient m, WithClientLog env m) =>
ImplicitAddressWithAlias
-> NonEmpty (OperationInfo ClientInput)
-> m (OperationHash, NonEmpty (OperationInfo Result))
runOperationsNonEmpty ImplicitAddressWithAlias
sender (NonEmpty (OperationInfo ClientInput)
 -> m (OperationHash, NonEmpty (OperationInfo Result)))
-> (DelegationData -> NonEmpty (OperationInfo ClientInput))
-> DelegationData
-> m (OperationHash, NonEmpty (OperationInfo Result))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OneItem (NonEmpty (OperationInfo ClientInput))
-> NonEmpty (OperationInfo ClientInput)
OperationInfo ClientInput -> NonEmpty (OperationInfo ClientInput)
forall x. One x => OneItem x -> x
one (OperationInfo ClientInput -> NonEmpty (OperationInfo ClientInput))
-> (DelegationData -> OperationInfo ClientInput)
-> DelegationData
-> NonEmpty (OperationInfo ClientInput)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DelegationInfo ClientInput -> OperationInfo ClientInput
DelegationData -> OperationInfo ClientInput
forall i. DelegationInfo i -> OperationInfo i
OpDelegation (DelegationData -> m OperationHash)
-> DelegationData -> m OperationHash
forall a b. (a -> b) -> a -> b
$ Maybe KeyHash -> Maybe Mutez -> DelegationData
DelegationData Maybe KeyHash
delegate Maybe Mutez
forall a. Maybe a
Nothing

-- | Register the sender as its delegate and become a "registered delegate"
--   Alias for @setDelegateOp sender (Just sender)@
registerDelegateOp
  :: ( HasTezosRpc m
     , HasTezosClient m
     , MonadReader env m
     , HasLog env Message m)
  => ImplicitAddressWithAlias
  -> m OperationHash
registerDelegateOp :: forall (m :: * -> *) env.
(HasTezosRpc m, HasTezosClient m, MonadReader env m,
 HasLog env Message m) =>
ImplicitAddressWithAlias -> m OperationHash
registerDelegateOp ImplicitAddressWithAlias
sender = do
  let senderKh :: KeyHash
senderKh = ImplicitAddress -> KeyHash
unImplicitAddress (ImplicitAddress -> KeyHash) -> ImplicitAddress -> KeyHash
forall a b. (a -> b) -> a -> b
$ ImplicitAddressWithAlias -> ImplicitAddress
forall (kind :: AddressKind).
AddressWithAlias kind -> KindedAddress kind
awaAddress ImplicitAddressWithAlias
sender
  ImplicitAddressWithAlias -> Maybe KeyHash -> m OperationHash
forall (m :: * -> *) env.
(HasTezosRpc m, HasTezosClient m, MonadReader env m,
 HasLog env Message m) =>
ImplicitAddressWithAlias -> Maybe KeyHash -> m OperationHash
setDelegateOp ImplicitAddressWithAlias
sender (KeyHash -> Maybe KeyHash
forall a. a -> Maybe a
Just KeyHash
senderKh)