module Scripts.Transfer where

import DA.Map (empty, fromList)
import DA.Set (singleton)
import Daml.Script

-- INTERFACE DEPENDENCIES --
import Daml.Finance.Interface.Account.Factory qualified as Account (F)
import Daml.Finance.Interface.Holding.Base qualified as Holding (I)
import Daml.Finance.Interface.Holding.Factory qualified as Holding (F)
import Daml.Finance.Interface.Instrument.Base.Instrument qualified as Instrument (I)
import Daml.Finance.Interface.Types.Common.Types (AccountKey, Id(..), InstrumentKey(..))

-- IMPLEMENTATION DEPENDENCIES --
import Daml.Finance.Account.Account qualified as Account (Factory(..))
import Daml.Finance.Holding.Fungible qualified as Fungible (Factory(..))
import Daml.Finance.Instrument.Token.Instrument (Instrument(..))

import Workflow.CreateAccount qualified as CreateAccount
import Workflow.CreditAccount qualified as CreditAccount
import Workflow.Transfer qualified as Transfer

-- | Helper container used to transfer state from one script to another.
data TransferState = TransferState
  with
    alice : Party
    bank : Party
    bob : Party
    public : Party
    aliceAccount : AccountKey
    bobAccount : AccountKey
    cashInstrument : InstrumentKey
    holdingFactoryCid : ContractId Holding.F
    newHoldingCid : ContractId Holding.I
  deriving (Eq, Show)

-- | Test script that
-- 1. creates an account for Alice and Bob at the Bank
-- 2. issues a cash instrument
-- 3. credits a cash holding to Alice in her bank account
-- 4. transfers the holding from Alice to Bob
runTransfer : Script TransferState
runTransfer = do

  -- Allocate parties
  [alice, bank, bob, public] <- mapA createParty $ ["Alice", "Bank", "Bob", "Public"]

  -- Account Factory (it is used by the bank to create accounts)
  -- CREATE_ACCOUNT_FACTORY_BEGIN
  accountFactoryCid <- toInterfaceContractId @Account.F <$> submit bank do
    createCmd Account.Factory with provider = bank; observers = empty
  -- CREATE_ACCOUNT_FACTORY_END

  -- Holding Factory (it is used by the bank to create holdings with the desired implementation)
  -- CREATE_HOLDING_FACTORY_BEGIN
  holdingFactoryCid <- toInterfaceContractId @Holding.F <$> submit bank do
    createCmd Fungible.Factory with
      provider = bank
      observers = fromList [("PublicObserver", singleton public )]
  -- CREATE_HOLDING_FACTORY_END

  -- Alice and Bob setup account @Bank
  -- SETUP_ALICE_ACCOUNT_BEGIN
  aliceRequestCid <- submit alice do
    createCmd CreateAccount.Request with owner = alice; custodian = bank
  aliceAccount <- submit bank do
    exerciseCmd aliceRequestCid CreateAccount.Accept with
      label = "Alice@Bank"
      description = "Account of Alice at Bank"
      accountFactoryCid = accountFactoryCid
      holdingFactoryCid = holdingFactoryCid
      observers = []
  -- SETUP_ALICE_ACCOUNT_END

  bobRequestCid <- submit bob do createCmd CreateAccount.Request with owner = bob; custodian = bank
  bobAccount <- submit bank do
    exerciseCmd bobRequestCid CreateAccount.Accept with
      label = "Bob@Bank"
      description = "Account of Bob at Bank"
      accountFactoryCid = accountFactoryCid
      holdingFactoryCid = holdingFactoryCid
      observers = [alice]

  -- Bank creates the cash instrument
  -- ISSUE_CASH_INSTRUMENT_BEGIN
  let
    instrumentId = Id "USD"
    instrumentVersion = "0"
  now <- getTime

  cashInstrumentCid <- toInterfaceContractId @Instrument.I <$> submit bank do
    createCmd Instrument with
      depository = bank
      issuer = bank
      id = instrumentId
      version = instrumentVersion
      description = "Instrument representing units of USD"
      validAsOf = now
      observers = empty
  -- ISSUE_CASH_INSTRUMENT_END

  -- Alice deposits cash at the bank
  -- CREATE_ALICE_HOLDING_BEGIN
  aliceRequestCid <- submit alice do
    createCmd CreditAccount.Request with
      account = aliceAccount
      instrument = InstrumentKey with
        issuer = bank
        depository = bank
        id = instrumentId
        version = instrumentVersion
      amount = 1000.0

  aliceCashHoldingCid <- submit bank do exerciseCmd aliceRequestCid CreditAccount.Accept
  -- CREATE_ALICE_HOLDING_END

  -- Bob requests a cash transfer from Alice
  -- TRANSFER_BEGIN
  let
    cashInstrument = InstrumentKey with
      issuer = bank
      depository = bank
      id = instrumentId
      version = instrumentVersion

  transferRequestCid <- submit bob do
    createCmd Transfer.Request with
      receiverAccount = bobAccount
      instrument = cashInstrument
      amount = 1000.0
      currentOwner = alice

  newHoldingCid <- submitMulti [alice] [public] do
    exerciseCmd transferRequestCid Transfer.Accept with holdingCid = aliceCashHoldingCid
  -- TRANSFER_END

  pure $ TransferState with
    alice
    bank
    bob
    public
    aliceAccount
    bobAccount
    cashInstrument
    holdingFactoryCid
    newHoldingCid

-- | Creates a user + party given a hint
createParty : Text -> Script Party
createParty name = do
  party <- allocatePartyWithHint name $ PartyIdHint name
  userId <- validateUserId name
  createUser (User userId (Some party)) [CanActAs party]
  pure party