{-# OPTIONS --safe #-}

open import Ledger.Types.GovStructure
open import Ledger.Transaction using (TransactionStructure)

module Ledger.Gov (txs : _) (open TransactionStructure txs hiding (epoch)) where

open import Ledger.Prelude hiding (any?; Any; all?; All; Rel; lookup; ∈-filter)

open import Axiom.Set.Properties th using (∃-sublist-⇔)

open import Ledger.GovernanceActions govStructure using (Vote)
open import Ledger.Enact govStructure
open import Ledger.Ratify txs hiding (vote)
open import Ledger.Certs govStructure

open import Data.List.Ext using (subpermutations; sublists)
open import Data.List.Ext.Properties2
open import Data.List.Membership.Propositional.Properties using (Any↔; ∈-filter⁻; ∈-filter⁺)
open import Data.List.Relation.Binary.Subset.Propositional using () renaming (_⊆_ to _⊆ˡ_)
open import Data.List.Relation.Unary.All using (all?; All)
open import Data.List.Relation.Unary.Any using (any?; Any)
open import Data.List.Relation.Unary.Unique.DecPropositional using (unique?)
open import Data.List.Relation.Unary.Unique.Propositional using (Unique)
open import Data.Relation.Nullary.Decidable.Ext using (map′⇔)
open import Function.Related.Propositional using (↔⇒)

open GovActionState


GovState : Type


GovState = List (GovActionID × GovActionState)

record GovEnv : Type where
  field
    txid        : TxId
    epoch       : Epoch
    pparams     : PParams
    ppolicy     : Maybe ScriptHash
    enactState  : EnactState
    certState   : CertState
    rewardCreds :  Credential


instance
  unquoteDecl To-GovEnv = derive-To
    [ (quote GovEnv , To-GovEnv) ]

private variable
  Γ : GovEnv
  s s' : GovState
  aid : GovActionID
  voter : Voter
  vote : GovVote
  v : Vote
  d : Coin
  addr : RwdAddr
  a : GovAction
  prev : NeedsHash (gaType a)
  k : 
  p : Maybe ScriptHash

open GState
open PState


govActionPriority : GovActionType  
govActionPriority NoConfidence     = 0
govActionPriority UpdateCommittee  = 1
govActionPriority NewConstitution  = 2
govActionPriority TriggerHF        = 3
govActionPriority ChangePParams    = 4
govActionPriority TreasuryWdrl     = 5
govActionPriority Info             = 6

Overlap : GovActionType  GovActionType  Type
Overlap NoConfidence    UpdateCommittee  = 
Overlap UpdateCommittee NoConfidence     = 
Overlap a               a'               = a  a'


-- TODO: cleanup this
Overlap? : (a a' : GovActionType)  Dec (Overlap a a')
Overlap? NoConfidence    UpdateCommittee  = Dec-⊤ .dec
Overlap? UpdateCommittee NoConfidence     = Dec-⊤ .dec
Overlap? NoConfidence NoConfidence = yes refl
Overlap? NoConfidence NewConstitution = no  ())
Overlap? NoConfidence TriggerHF = no  ())
Overlap? NoConfidence ChangePParams = no  ())
Overlap? NoConfidence TreasuryWdrl = no  ())
Overlap? NoConfidence Info = no  ())
Overlap? UpdateCommittee UpdateCommittee = yes refl
Overlap? UpdateCommittee NewConstitution = no  ())
Overlap? UpdateCommittee TriggerHF = no  ())
Overlap? UpdateCommittee ChangePParams = no  ())
Overlap? UpdateCommittee TreasuryWdrl = no  ())
Overlap? UpdateCommittee Info = no  ())
Overlap? NewConstitution NoConfidence = no  ())
Overlap? NewConstitution UpdateCommittee = no  ())
Overlap? NewConstitution NewConstitution = yes refl
Overlap? NewConstitution TriggerHF = no  ())
Overlap? NewConstitution ChangePParams = no  ())
Overlap? NewConstitution TreasuryWdrl = no  ())
Overlap? NewConstitution Info = no  ())
Overlap? TriggerHF NoConfidence = no  ())
Overlap? TriggerHF UpdateCommittee = no  ())
Overlap? TriggerHF NewConstitution = no  ())
Overlap? TriggerHF TriggerHF = yes refl
Overlap? TriggerHF ChangePParams = no  ())
Overlap? TriggerHF TreasuryWdrl = no  ())
Overlap? TriggerHF Info = no  ())
Overlap? ChangePParams NoConfidence = no  ())
Overlap? ChangePParams UpdateCommittee = no  ())
Overlap? ChangePParams NewConstitution = no  ())
Overlap? ChangePParams TriggerHF = no  ())
Overlap? ChangePParams ChangePParams = yes refl
Overlap? ChangePParams TreasuryWdrl = no  ())
Overlap? ChangePParams Info = no  ())
Overlap? TreasuryWdrl NoConfidence = no  ())
Overlap? TreasuryWdrl UpdateCommittee = no  ())
Overlap? TreasuryWdrl NewConstitution = no  ())
Overlap? TreasuryWdrl TriggerHF = no  ())
Overlap? TreasuryWdrl ChangePParams = no  ())
Overlap? TreasuryWdrl TreasuryWdrl = yes refl
Overlap? TreasuryWdrl Info = no  ())
Overlap? Info NoConfidence = no  ())
Overlap? Info UpdateCommittee = no  ())
Overlap? Info NewConstitution = no  ())
Overlap? Info TriggerHF = no  ())
Overlap? Info ChangePParams = no  ())
Overlap? Info TreasuryWdrl = no  ())
Overlap? Info Info = yes refl



insertGovAction : GovState  GovActionID × GovActionState  GovState
insertGovAction [] gaPr = [ gaPr ]
insertGovAction ((gaID₀ , gaSt₀)  gaPrs) (gaID₁ , gaSt₁)
  =  if (govActionPriority (action gaSt₀ .gaType)) ≤? (govActionPriority (action gaSt₁ .gaType))
     then (gaID₀ , gaSt₀)  insertGovAction gaPrs (gaID₁ , gaSt₁)
     else (gaID₁ , gaSt₁)  (gaID₀ , gaSt₀)  gaPrs

mkGovStatePair : Epoch  GovActionID  RwdAddr  (a : GovAction)  NeedsHash (a .gaType)
                  GovActionID × GovActionState
mkGovStatePair e aid addr a prev = (aid , record
  { votes =  ; returnAddr = addr ; expiresIn = e ; action = a ; prevAction = prev })

addAction : GovState
           Epoch  GovActionID  RwdAddr  (a : GovAction)  NeedsHash (a .gaType)
           GovState
addAction s e aid addr a prev = insertGovAction s (mkGovStatePair e aid addr a prev)


opaque


  addVote : GovState  GovActionID  Voter  Vote  GovState
  addVote s aid voter v = map modifyVotes s
    where modifyVotes : GovActionID × GovActionState  GovActionID × GovActionState
          modifyVotes = λ (gid , s')  gid , record s'
            { votes = if gid  aid then insert (votes s') voter v else votes s'}

  isRegistered : GovEnv  Voter  Type
  isRegistered Γ (r , c) = case r of λ where
    CC     just c  range (gState .ccHotKeys)
    DRep   c  dom (gState .dreps)
    SPO    c  mapˢ KeyHashObj (dom (pState .pools))
      where
        open CertState (GovEnv.certState Γ) using (gState; pState)

  validHFAction : GovProposal  GovState  EnactState  Type
  validHFAction (record { action =  TriggerHF , v ⟧ᵍᵃ ; prevAction = prev }) s e =
    (let (v' , aid) = EnactState.pv e in aid  prev × pvCanFollow v' v)
     ∃₂[ x , v' ] (prev , x)  fromList s × x .action   TriggerHF , v' ⟧ᵍᵃ × pvCanFollow v' v
  validHFAction _ _ _ = 


data


  _⊢_⇀⦇_,GOV⦈_  : GovEnv ×   GovState  GovVote  GovProposal  GovState  Type


_⊢_⇀⦇_,GOVS⦈_   : GovEnv  GovState  List (GovVote  GovProposal)  GovState  Type


-- Convert list of (GovActionID,GovActionState)-pairs to list of GovActionID pairs.
getAidPairsList : GovState  List (GovActionID × GovActionID)
getAidPairsList aid×states =
  mapMaybe  (aid , aState)  (aid ,_) <$> getHash (prevAction aState)) $ aid×states

-- A list of GovActionID pairs connects the first GovActionID to the second.
_connects_to_ : List (GovActionID × GovActionID)  GovActionID  GovActionID  Type
[] connects aidNew to aidOld = aidNew  aidOld
((aid , aidPrev)  s) connects aidNew to aidOld  =
  aid  aidNew × s connects aidPrev to aidOld  s connects aidNew to aidOld


enactable  : EnactState  List (GovActionID × GovActionID)
            GovActionID × GovActionState  Type
enactable e aidPairs = λ (aidNew , as)  case getHashES e (action as .gaType) of


  λ where


   nothing         
   (just aidOld)   ∃[ t ]  fromList t  fromList aidPairs
                            × Unique t × t connects aidNew to aidOld

allEnactable : EnactState  GovState  Type
allEnactable e aid×states = All (enactable e (getAidPairsList aid×states)) aid×states

hasParentE : EnactState  GovActionID  GovActionType  Type
hasParentE e aid gaTy = case getHashES e gaTy of


  λ where


   nothing     
   (just id)   id  aid

hasParent : EnactState  GovState  (gaTy : GovActionType)  NeedsHash gaTy  Type
hasParent e s gaTy aid = case getHash aid of


  λ where


    nothing       
    (just aid')   hasParentE e aid' gaTy
                    Any  (gid , gas)  gid  aid' × Overlap (gas .action .gaType) gaTy) s


open Equivalence

hasParentE? :  e aid a  Dec (hasParentE e aid a)
hasParentE? e aid gaTy with getHashES e gaTy
... | nothing   = yes _
... | (just id) = id  aid

hasParent? :  e s a aid  Dec (hasParent e s a aid)
hasParent? e s gaTy aid with getHash aid
... | just aid' = hasParentE? e aid' gaTy
                  ⊎-dec any?  (gid , gas)  gid  aid' ×-dec Overlap? (gas .action .gaType) gaTy) s
... | nothing = yes _

-- newtype to make the instance resolution work
data hasParent' : EnactState  GovState  (gaTy : GovActionType)  NeedsHash gaTy  Type where
  HasParent' :  {x y z w}  hasParent x y z w  hasParent' x y z w

instance
  hasParent?' :  {x y z w}  hasParent' x y z w 
  hasParent?' =  map′ HasParent'  where (HasParent' x)  x) (hasParent? _ _ _ _)

[_connects_to_?] :  l aidNew aidOld  Dec (l connects aidNew to aidOld)
[ [] connects aidNew to aidOld ?] = aidNew  aidOld

[ (aid , aidPrev)  s connects aidNew to aidOld ?] =
  ((aid  aidNew) ×-dec [ s connects aidPrev to aidOld ?]) ⊎-dec [ s connects aidNew to aidOld ?]

any?-connecting-subperm :  {u} {v}   L  Dec (Any l  Unique l × l connects u to v) (subpermutations L))
any?-connecting-subperm {u} {v} L = any?  l  unique? _≟_ l ×-dec [ l connects u to v ?]) (subpermutations L)

∃?-connecting-subperm :  {u} {v}   L  Dec (∃[ l ] l ∈ˡ subpermutations L × Unique l × l connects u to v)
∃?-connecting-subperm L = from (map′⇔ (↔⇒ Any↔)) (any?-connecting-subperm L)

∃?-connecting-subset :  {u} {v}   L  Dec (∃[ l ] l ⊆ˡ L × Unique l × l connects u to v)
∃?-connecting-subset L = from (map′⇔ ∃uniqueSubset⇔∃uniqueSubperm) (∃?-connecting-subperm L)

enactable? :  eState aidPairs aidNew×st  Dec (enactable eState aidPairs aidNew×st)
enactable? eState aidPairs (aidNew , as) with getHashES eState (GovActionState.action as .gaType)
... | nothing = yes tt
... | just aidOld = from (map′⇔ ∃-sublist-⇔) (∃?-connecting-subset aidPairs)

allEnactable? :  eState aid×states  Dec (allEnactable eState aid×states)
allEnactable? eState aid×states =
  all?  aid×st  enactable? eState (getAidPairsList aid×states) aid×st) aid×states

-- newtype to make the instance resolution work
data allEnactable' : EnactState  GovState  Type where
  AllEnactable' :  {x y}  allEnactable x y  allEnactable' x y

instance
  allEnactable?' :  {x y}  allEnactable' x y 
  allEnactable?' =  map′ AllEnactable'  where (AllEnactable' x)  x) (allEnactable? _ _)

-- `maxAllEnactable` returns a list `ls` of sublists of the given
-- list (`aid×states : List (GovActionID × GovActionState)`) such that
--    (i) each sublist `l ∈ ls` satisfies `allEnactable e l` and
--   (ii) each sublist `l ∈ ls` is of maximal length among sublists of `aid×states` satisfying `allEnactable`.
maxAllEnactable : EnactState  List (GovActionID × GovActionState)  List (List (GovActionID × GovActionState))
maxAllEnactable e = maxsublists⊧P (allEnactable? e)

-- Every sublist returned by `maxAllEnactable` satisfies (i).
∈-maxAllEnactable→allEnactable :  {e} {aid×states} l
   l ∈ˡ maxAllEnactable e aid×states  allEnactable e l
∈-maxAllEnactable→allEnactable {e} {aid×states} l l∈ =
  proj₂ (∈-filter⁻ (allEnactable? e) {l} {sublists aid×states}
          (proj₁ (∈-filter⁻  l  length l  maxlen (sublists⊧P (allEnactable? e) aid×states)) l∈)))

-- Every sublist returned by `maxAllEnactable` satisfies (ii).
∈-maxAllEnactable→maxLength :  {e aid×states l l'}
                               l ∈ˡ sublists aid×states  allEnactable e l
                               l' ∈ˡ maxAllEnactable e aid×states
                               length l  length l'
∈-maxAllEnactable→maxLength {e} {aid×states} {l} {l'} l∈ el l'∈ =
  let ls = sublists⊧P (allEnactable? e) aid×states in
    subst (length l ≤_)
          (sym (proj₂ (∈-filter⁻  l  length l  maxlen ls) {xs = ls} l'∈)))
          (∈-maxlen-≤ l (∈-filter⁺ (allEnactable? e) l∈ el))


actionValid :  Credential  Maybe ScriptHash  Maybe ScriptHash  Epoch  GovAction  Type
actionValid rewardCreds p ppolicy epoch  ChangePParams , _ ⟧ᵍᵃ =
  p  ppolicy
actionValid rewardCreds p ppolicy epoch  TreasuryWdrl  , x ⟧ᵍᵃ =
  p  ppolicy × mapˢ RwdAddr.stake (dom x)  rewardCreds
actionValid rewardCreds p ppolicy epoch  UpdateCommittee , (new , rem , q) ⟧ᵍᵃ =
  p  nothing × (∀[ e  range new ]  epoch < e) × (dom new  rem ≡ᵉ )
actionValid rewardCreds p ppolicy epoch _ =
  p  nothing

actionWellFormed : GovAction  Type
actionWellFormed  ChangePParams , x ⟧ᵍᵃ = ppdWellFormed x
actionWellFormed  TreasuryWdrl  , x ⟧ᵍᵃ =
  (∀[ a  dom x ] RwdAddr.net a  NetworkId) × (∃[ v  range x ] ¬ (v  0))
actionWellFormed _                 = 


actionValid? :  {rewardCreds p ppolicy epoch a}  actionValid rewardCreds p ppolicy epoch a 
actionValid? {a =  NoConfidence    , _ ⟧ᵍᵃ} = it
actionValid? {a =  UpdateCommittee , _ ⟧ᵍᵃ} = it
actionValid? {a =  NewConstitution , _ ⟧ᵍᵃ} = it
actionValid? {a =  TriggerHF       , _ ⟧ᵍᵃ} = it
actionValid? {a =  ChangePParams   , _ ⟧ᵍᵃ} = it
actionValid? {a =  TreasuryWdrl    , _ ⟧ᵍᵃ} = it
actionValid? {a =  Info            , _ ⟧ᵍᵃ} = it

actionWellFormed? :  {a}  actionWellFormed a 
actionWellFormed? { NoConfidence    , _ ⟧ᵍᵃ} = it
actionWellFormed? { UpdateCommittee , _ ⟧ᵍᵃ} = it
actionWellFormed? { NewConstitution , _ ⟧ᵍᵃ} = it
actionWellFormed? { TriggerHF       , _ ⟧ᵍᵃ} = it
actionWellFormed? { ChangePParams   , _ ⟧ᵍᵃ} = it
actionWellFormed? { TreasuryWdrl    , _ ⟧ᵍᵃ} = it
actionWellFormed? { Info            , _ ⟧ᵍᵃ} = it


open GovEnv
open PParams hiding (a)

variable
  machr : Maybe Anchor
  achr : Anchor
  ast  : GovActionState


data _⊢_⇀⦇_,GOV⦈_ where


  GOV-Vote :
     (aid , ast)  fromList s
     canVote (Γ .pparams) (action ast) (proj₁ voter)
     isRegistered Γ voter
     ¬ expired (Γ .epoch) ast
      ───────────────────────────────────────
      (Γ , k)  s ⇀⦇ inj₁  aid , voter , v , machr  ,GOV⦈ addVote s aid voter v

  GOV-Propose :
    let pp           = Γ .pparams
        e            = Γ .epoch
        enactState   = Γ .enactState
        rewardCreds  = Γ .rewardCreds
        prop         = record { returnAddr = addr ; action = a ; anchor = achr
                              ; policy = p ; deposit = d ; prevAction = prev }
    in
     actionWellFormed a
     actionValid rewardCreds p (Γ .ppolicy) e a
     d  pp .govActionDeposit
     validHFAction prop s enactState
     hasParent enactState s (a .gaType) prev
     addr .RwdAddr.net  NetworkId
     addr .RwdAddr.stake  rewardCreds
      ───────────────────────────────────────
      (Γ , k)  s ⇀⦇ inj₂ prop ,GOV⦈ addAction s (pp .govActionLifetime +ᵉ e)
                                                 (Γ .txid , k) addr a prev

_⊢_⇀⦇_,GOVS⦈_ = ReflexiveTransitiveClosureᵢ {sts = _⊢_⇀⦇_,GOV⦈_}