import Mathlib.Data.Set.Basic
import Mathlib.Data.Set.Insert
import InductiveVerification.Message

-- Define the `Event` type
inductive Event
| Says : Agent → Agent → Msg → Event
| Gets : Agent → Msg → Event
| Notes : Agent → Msg → Event

-- Define the `initState` function
class HasInitState (α : Type) where
  initState : α → Set Msg

variable [ hasInitStateAgent : HasInitState Agent ]
  
open HasInitState
  
-- Define the `bad` set
abbrev DecidableMem ( A : Set Agent ) := (a : Agent) →  Decidable (a ∈ A)
class Bad where
  bad : Set Agent 
  decidableBad : DecidableMem bad
  Spy_in_bad : Agent.Spy ∈ bad
  Server_not_bad : Agent.Server ∉ bad

instance [Bad] : DecidableMem Bad.bad := Bad.decidableBad
open Bad

-- attribute [simp] Spy_in_bad
-- attribute [simp] Server_not_bad

instance decidableAgentEq : DecidableEq Agent :=
  λ a b =>
    match a, b with
    | Agent.Spy, Agent.Spy => isTrue rfl
    | Agent.Spy, Agent.Server => isFalse (λ h => by contradiction)
    | Agent.Spy, Agent.Friend m => isFalse (λ h => by contradiction)
    | Agent.Server, Agent.Spy => isFalse (λ h => by contradiction)
    | Agent.Server, Agent.Server => isTrue rfl
    | Agent.Server, Agent.Friend m => isFalse (λ h => by contradiction)
    | Agent.Friend n, Agent.Spy => isFalse (λ h => by contradiction)
    | Agent.Friend n, Agent.Server => isFalse (λ h => by contradiction)
    | Agent.Friend n, Agent.Friend m =>
      if h : n = m then isTrue (congrArg Agent.Friend h) else isFalse (λ h' => h (Agent.Friend.inj h'))

-- instance decidableBad (a: Agent) : Decidable (a ∈ bad) :=
-- by rw[bad]; apply Set.decidableSingleton

-- Define the `knows` function
def knows [Bad] : Agent → List Event → Set Msg
| A, [] => initState A
| Agent.Spy, Event.Says _ _ X :: evs => insert X (knows Agent.Spy evs)
| Agent.Spy, Event.Gets _ _ :: evs => knows Agent.Spy evs
| Agent.Spy, Event.Notes A X :: evs =>
  if A ∈ bad then insert X (knows Agent.Spy evs) else knows Agent.Spy evs
| A, Event.Says A' _ X :: evs =>
  if A = A' then insert X (knows A evs) else knows A evs
| A, Event.Gets A' X :: evs =>
  if A = A' then insert X (knows A evs) else knows A evs
| A, Event.Notes A' X :: evs =>
  if A = A' then insert X (knows A evs) else knows A evs

-- Define the `spies` abbreviation
abbrev spies (evs : List Event) [Bad] : Set Msg := knows Agent.Spy evs

-- Define the `used` function
def used : List Event → Set Msg
| [] => ⋃ (B : Agent), parts (initState B)
| Event.Says _ _ X :: evs => parts {X} ∪ used evs
| Event.Gets _ _ :: evs => used evs
| Event.Notes _ X :: evs => parts {X} ∪ used evs

-- Lemmas for `used`

 lemma used_mono : used (evs) ⊆ used (ev :: evs) := by
  cases ev <;> simp[used]
  
 lemma Notes_imp_used {A : Agent} {X : Msg} {evs : List Event} :
  Event.Notes A X ∈ evs → X ∈ used evs := by
  induction evs with
  | nil => intro h; cases h
  | cons ev evs ih =>
    intro h
    cases h with
    | tail => cases ev
              · simp [used]; right; aapply ih
              · simp [used]; aapply ih
              · simp [used]; right; aapply ih
    | head => simp [used]; left; apply parts.inj; simp

 lemma Says_imp_used {A B : Agent} {X : Msg} {evs : List Event} :
  Event.Says A B X ∈ evs → X ∈ used evs := by
  induction evs with
  | nil => intro h; cases h
  | cons ev evs ih =>
    intro h
    cases h with
    | tail => cases ev
              · simp [used]; right; aapply ih
              · simp [used]; aapply ih
              · simp [used]; right; aapply ih
    | head => simp [used]; left; apply parts.inj; simp

-- Knowledge subset rules
lemma knows_subset_knows_Cons [Bad] {A : Agent} {ev : Event} {evs : List Event} :
  knows A (evs) ⊆ knows A (ev :: evs) := by
  by_cases h :A = Agent.Spy
  · rw[h]
    intro _ _
    cases ev
    · simp [knows]; right; assumption
    · simp [knows]; assumption
    · simp [knows]; split_ifs
      · right; assumption
      · assumption
  · intro _ _
    cases ev <;> (simp [knows]; split_ifs; right; assumption; assumption)

lemma initState_subset_knows [Bad] :
  ∀ {A : Agent} {evs : List Event},
    initState A ⊆ knows A evs := by
  intro A evs
  induction evs with
  | nil => simp [knows]
  | cons e evs ih =>
    apply subset_trans
    · exact ih
    · apply knows_subset_knows_Cons

-- Lemmas for `knows`
@[simp]
lemma knows_Spy_Says [Bad] {A B : Agent} {X : Msg} {evs : List Event} :
  knows Agent.Spy (Event.Says A B X :: evs) = insert X (knows Agent.Spy evs) := by
  simp [knows]

@[simp]
lemma knows_Spy_Notes [Bad] {A : Agent} {X : Msg} {evs : List Event} :
  knows Agent.Spy (Event.Notes A X :: evs) =
    if A ∈ bad then insert X (knows Agent.Spy evs) else knows Agent.Spy evs := by
  simp [knows]

@[simp]
lemma knows_Spy_Gets [Bad] {A : Agent} {X : Msg} {evs : List Event} :
  knows Agent.Spy (Event.Gets A X :: evs) = knows Agent.Spy evs := by
  simp [knows]

lemma Says_imp_knows_Spy [Bad] {A B : Agent} {X : Msg} {evs : List Event} :
  Event.Says A B X ∈ evs → X ∈ knows Agent.Spy evs := by
  induction evs with
  | nil => intro h; cases h
  | cons ev evs ih =>
    intro h
    cases h with
    | tail h => cases ev
                · simp [knows]; right; aapply ih
                · simp [knows]; aapply ih
                · simp [knows]; split_ifs
                  · right; aapply ih
                  · aapply ih
    | head h => simp [knows];

lemma Notes_imp_knows_Spy [Bad] {A : Agent} {X : Msg} {evs : List Event} :
  Event.Notes A X ∈ evs → A ∈ bad → X ∈ knows Agent.Spy evs := by
  induction evs with
  | nil => intro h; cases h
  | cons ev evs ih =>
    intro h h_bad
    cases h with
    | head => simp [knows]; split_ifs; left; trivial
    | tail _ h => apply knows_subset_knows_Cons; apply ih; apply h; apply h_bad

-- Elimination rules: derive contradictions from old Says events containing
-- items known to be fresh
lemma Says_imp_parts_knows_Spy [Bad] :
  ∀ {A B : Agent} {X : Msg} {evs : List Event},
    Event.Says A B X ∈ evs → X ∈ parts (knows Agent.Spy evs) := by
  intro A B X evs h
  apply parts.inj
  apply Says_imp_knows_Spy
  exact h

lemma knows_Spy_partsEs [Bad] :
  ∀ {A B : Agent} {X : Msg} {evs : List Event},
    Event.Says A B X ∈ evs → X ∈ parts (knows Agent.Spy evs) := by
  exact Says_imp_parts_knows_Spy

lemma Says_imp_analz_Spy [InvKey] [Bad] :
  ∀ {A B : Agent} {X : Msg} {evs : List Event},
    Event.Says A B X ∈ evs → X ∈ analz (knows Agent.Spy evs) := by
  intro A B X evs h
  apply analz.inj
  apply Says_imp_knows_Spy
  exact h

-- Compatibility for the old "spies" function
lemma spies_partsEs [Bad] :
  ∀ {A B : Agent} {X : Msg} {evs : List Event},
    Event.Says A B X ∈ evs → X ∈ parts (knows Agent.Spy evs) := by
  exact knows_Spy_partsEs

lemma Says_imp_spies [Bad] :
  ∀ {A B : Agent} {X : Msg} {evs : List Event},
    Event.Says A B X ∈ evs → X ∈ knows Agent.Spy evs := by
  exact Says_imp_knows_Spy

lemma parts_insert_spies [Bad] :
    parts (insert X (knows Agent.Spy evs)) = parts {X} ∪ parts (knows Agent.Spy evs) :=
by
  apply parts_insert

-- Knowledge of Agents
lemma knows_subset_knows_Says [Bad] :
  ∀ {A A' B : Agent} {X : Msg} {evs : List Event},
    knows A evs ⊆ knows A (Event.Says A' B X :: evs) := by
  intro A A' B X evs
  apply knows_subset_knows_Cons

lemma knows_subset_knows_Notes [Bad] :
  ∀ {A A' : Agent} {X : Msg} {evs : List Event},
    knows A evs ⊆ knows A (Event.Notes A' X :: evs) := by
  intro A A' X evs
  apply knows_subset_knows_Cons

lemma knows_subset_knows_Gets [Bad] :
  ∀ {A A' : Agent} {X : Msg} {evs : List Event},
    knows A evs ⊆ knows A (Event.Gets A' X :: evs) := by
  intro A A' X evs
  apply knows_subset_knows_Cons

-- Agents know what they say
lemma Says_imp_knows [Bad] :
  ∀ {A B : Agent} {X : Msg} {evs : List Event},
    Event.Says A B X ∈ evs → X ∈ knows A evs := by
  intro A B X evs h
  induction evs with
  | nil => cases h
  | cons ev evs ih =>
    cases h with
    | head => by_cases h: A = Agent.Spy
              · rw [h]; simp [knows]
              · simp [knows]
    | tail => by_cases h: A = Agent.Spy
              · rw[h]; cases ev
                · simp [knows]; right; rw[←h]; aapply ih
                · simp [knows]; rw[←h]; aapply ih
                · simp [knows]; split_ifs
                  · right; rw[←h]; aapply ih
                  · rw[←h]; aapply ih
              · cases ev <;> (
                simp [knows]; split_ifs; right; aapply ih; aapply ih)
    
-- Agents know what they note
lemma Notes_imp_knows [Bad] :
  ∀ {A : Agent} {X : Msg} {evs : List Event},
    Event.Notes A X ∈ evs → X ∈ knows A evs := by
  intro A X evs h
  induction evs with
  | nil => cases h
  | cons ev evs ih =>
    cases h with
    | head => by_cases h: A = Agent.Spy
              · rw [h]; simp [knows, Spy_in_bad]
              · simp [knows]
    | tail => by_cases h: A = Agent.Spy
              · rw[h]; cases ev
                · simp [knows]; right; rw[←h]; aapply ih
                · simp [knows]; rw[←h]; aapply ih
                · simp [knows]; split_ifs
                  · right; rw[←h]; aapply ih
                  · rw[←h]; aapply ih
              · cases ev <;> (
                simp [knows]; split_ifs; right; aapply ih; aapply ih)

-- Agents know what they receive
lemma Gets_imp_knows_agents [Bad] :
  ∀ {A : Agent} {X : Msg} {evs : List Event},
    A ≠ Agent.Spy → Event.Gets A X ∈ evs → X ∈ knows A evs := by
  intro A X evs h_ne h
  induction evs with
  | nil => cases h
  | cons ev evs ih =>
    cases h with
    | head => simp [knows]
    | tail => cases ev <;> (simp [knows]; split_ifs; right; aapply ih; aapply ih)

-- What agents DIFFERENT FROM Spy know
lemma knows_imp_Says_Gets_Notes_initState [Bad] :
  ∀ {A : Agent} {X : Msg} {evs : List Event},
    X ∈ knows A evs → A ≠ Agent.Spy →
    ∃ B, Event.Says A B X ∈ evs ∨ Event.Gets A X ∈ evs ∨ Event.Notes A X ∈ evs ∨ X ∈ initState A := by
  intro _ _ evs h _
  induction evs with
  | nil => simp [knows] at h; exists Agent.Server; simp_all
  | cons ev evs ih =>
    cases ev
    · simp [knows] at h; split_ifs at h with eq
      · cases h with
        | inl h₁ => grind
        | inr h' => apply ih at h'; cases h' with | intro A'; exists A'; grind
      · apply ih at h; cases h with | intro A'; exists A'; grind
    · simp [knows] at h; split_ifs at h with eq
      · cases h with
        | inl h₁ => simp[h₁, eq]; exact ⟨Agent.Server⟩
        | inr h' => apply ih at h'; cases h' with | intro A'; exists A'; grind
      · apply ih at h; cases h with | intro A'; exists A'; grind
    · simp [knows] at h; split_ifs at h with eq
      · cases h with
        | inl h₁ => simp[h₁, eq]; exact ⟨Agent.Server⟩
        | inr h' => apply ih at h'; cases h' with | intro A'; exists A'; grind
      · apply ih at h; cases h with | intro A'; exists A'; grind

-- auxiliary lemma

lemma knows_Spy_imp_Says_Notes_initState_aux
  [Bad] {X : Msg} {ev : Event} {evs : List Event} :
  (∃ A B, Event.Says A B X ∈ evs ∨ Event.Notes A X ∈ evs ∨ X ∈ initState Agent.Spy)
  → (∃ A B,
      Event.Says A B X ∈ ev :: evs ∨ Event.Notes A X ∈ ev :: evs ∨ X ∈ initState Agent.Spy
  ) := by
    intro h
    cases h with | intro A h =>
    cases h with | intro B h => 
    exists A; exists B
    cases h with
    | inl => left; right; assumption
    | inr h => right; cases h
               · left; right; assumption
               · right; assumption

-- What the Spy knows
lemma knows_Spy_imp_Says_Notes_initState [Bad] {X : Msg} {evs : List Event} :
    X ∈ knows Agent.Spy evs →
    ∃ A B, Event.Says A B X ∈ evs ∨ Event.Notes A X ∈ evs ∨ X ∈ initState Agent.Spy := by
  intro h
  induction evs with
  | nil => simp [knows] at h; exists Agent.Server; exists Agent.Server; simp_all
  | cons ev evs ih =>
    cases ev with
    | Says A' B' =>
      simp [knows] at h; cases h with
      | inl => exists A'; exists B'; simp_all;
      | inr h => 
        apply ih at h; aapply knows_Spy_imp_Says_Notes_initState_aux
    | Gets => 
      simp [knows] at h; apply ih at h; aapply knows_Spy_imp_Says_Notes_initState_aux
    | Notes A' X' => 
      simp [knows] at h; split_ifs at h with A'_bad
      · cases h with
        | inl h => rw[h]; exists A'; exists A'; simp
        | inr h => apply ih at h; aapply knows_Spy_imp_Says_Notes_initState_aux
      · apply ih at h; aapply knows_Spy_imp_Says_Notes_initState_aux

-- Parts of what the Spy knows are a subset of what is used
lemma parts_knows_Spy_subset_used [Bad] :
    parts (knows Agent.Spy evs) ⊆ used evs := by
  induction evs with
  | nil => simp [used, knows]; intro _ _; simp; exists Agent.Spy;
  | cons ev evs ih =>
    cases ev
    · simp[used, knows]; apply subset_trans; apply ih; simp
    · simp[used, knows]; assumption
    · simp[used, knows]; split_ifs with ABad
      · simp; apply subset_trans; apply ih; simp
      · apply subset_trans; apply ih; simp

-- Parts of what the Spy knows are a subset of what is used
lemma usedI [Bad] :
    X ∈ parts (knows Agent.Spy evs) → X ∈ used evs := by
  intro h
  apply parts_knows_Spy_subset_used
  exact h

-- Initial state messages are part of the used set

lemma initState_into_used {B : Agent} {evs : List Event} :
    parts (initState B) ⊆ used evs := by
  induction evs with
  | nil => simp [used]; apply Set.subset_iUnion (parts ∘ initState) B
  | cons ev =>
    cases ev <;> (simp[used]; try apply Set.subset_union_of_subset_right)
    all_goals assumption

-- Simplification rules for `used`

@[simp]
lemma used_Says {A B : Agent} {X : Msg} {evs : List Event} :
    used (Event.Says A B X :: evs) = parts {X} ∪ used evs := by
  simp [used]


@[simp]
lemma used_Notes {A : Agent} {X : Msg} {evs : List Event} :
    used (Event.Notes A X :: evs) = parts {X} ∪ used evs := by
  simp [used]


@[simp]
lemma used_Gets {A : Agent} {X : Msg} {evs : List Event} :
    used (Event.Gets A X :: evs) = used evs := by
  simp [used]


lemma used_nil_subset {evs : List Event} :
    used [] ⊆ used evs := by
  have B := Agent.Server
  induction evs with
  | nil => simp
  | cons head tail ih =>
  apply subset_trans (b := used tail)
  · assumption
  · exact used_mono
  
-- Keys for parts insert
open InvKey

omit hasInitStateAgent in

lemma keysFor_parts_insert {K : Key} {X : Msg} {G H : Set Msg} [InvKey]:
    K ∈ keysFor (parts (insert X G)) →
    X ∈ synth (analz H) →
    K ∈ keysFor (parts (G ∪ H)) ∪ invKey '' {K | Msg.Key K ∈ parts H} := by
  intro h h₂
  rw[parts_union, keysFor_union]
  rcases h with ⟨K', ⟨⟨X', h⟩, r⟩⟩
  by_cases KkfpG: K∈ keysFor (parts G)
  · left; left; assumption
  · rw[Set.union_assoc]; right; rw[←keysFor_synth]; rw[parts_insert] at h
    cases h with
    | inl h => 
      apply Crypt_imp_invKey_keysFor at h; rw[r] at h
      apply keysFor_mono (a := parts {X})
      · apply synth_subset_iff.mpr; rw[←synth_subset_iff]
        apply subset_trans (b := parts (synth (analz H)))
        · apply parts_mono; simp; assumption
        · rw[parts_synth, parts_analz]; apply sup_le
          · exact synth_increasing
          · apply synth_mono; exact analz_subset_parts
      · assumption
    | inr h => apply Crypt_imp_invKey_keysFor at h; rw[r] at h; contradiction
