-- SPLean/Common/Heap.lean

import Lean

import Mathlib.Data.Finmap

import Mathlib.Algebra.Group.Basic

import Mathlib.Algebra.BigOperators.Group.Finset

import Mathlib.Algebra.BigOperators.Intervals

import Mathlib.Data.Int.Interval

import Mathlib.Order.Interval.Finset.Basic

import Batteries.Data.List.Perm

import Ssreflect.Lang

open Classical

abbrev loc := Nat

section Option.merge

variable {α : Type u} (f : α → α → α)

end Option.merge

open PartialCommMonoid (valid)

section

variable {val : Type} [PartialCommMonoid val] -- [Inhabited val]

local notation "heap" => Heap.heap val

private def kmerge1 (l : loc) (v : val) (l₂ : List (Sigma (fun _ : loc => val))) : val :=
  match l₂.dlookup l with
  | .some v' => v + v'
  | _ => v

@[simp]
def kmerge : List (Sigma (fun _ : loc => val)) → List (Sigma (fun _ : loc => val)) → List (Sigma (fun _ : loc => val))
  | [], l₂ => l₂
  | s :: l₁, l₂ =>
    (if s.1 ∈ l₂.keys then
      ⟨s.1, kmerge1 s.1 s.2 l₂⟩ :: kmerge l₁ (l₂.kerase s.1)
    else s :: kmerge l₁ l₂)

-- Repository definitions used:
content: ...

-- Repository lemmas used:
content: theorem List.getElem_append_left{α : Type u_1} {l₁ l₂ : List α} {i : Nat} (hn : i < l₁.length) :\n(l₁ ++ l₂)[i] = l₁[i]

-- Local definitions used:
content: abbrev loc := Nat

content: private def kmerge1 (l : loc) (v : val) (l₂ : List (Sigma (fun _ : loc => val))) : val :=
  match l₂.dlookup l with
  | .some v' => v + v'
  | _ => v

content: @[simp]
def kmerge : List (Sigma (fun _ : loc => val)) → List (Sigma (fun _ : loc => val)) → List (Sigma (fun _ : loc => val))
  | [], l₂ => l₂
  | s :: l₁, l₂ =>
    (if s.1 ∈ l₂.keys then
      ⟨s.1, kmerge1 s.1 s.2 l₂⟩ :: kmerge l₁ (l₂.kerase s.1)
    else s :: kmerge l₁ l₂)

-- Local lemmas used:
content: lemma List.kerase_noterased {α : Type u} {β : α → Type v} [DecidableEq α] (l : List (Sigma β))
  (a a' : α) (hneq : a ≠ a') (b : β a) : ⟨a, b⟩ ∈ l ↔ ⟨a, b⟩ ∈ List.kerase a' l

content: lemma kmerge_mem2 (l₁ l₂ : List (Sigma (fun _ : loc => val))) (nd₁ : l₁.NodupKeys) (nd₂ : l₂.NodupKeys) -- necessary
    (a : Sigma (fun _ : loc => val)) : a ∈ (kmerge l₁ l₂) ↔
      if a.1 ∈ l₁.keys
      then (if a.1 ∈ l₂.keys then Option.merge (· + ·) (l₁.dlookup a.1) (l₂.dlookup a.1) = .some a.2 else a ∈ l₁)
      else a ∈ l₂

content: lemma kmerge_NodupKeys (l₁ l₂ : List (Sigma (fun _ : loc => val))) (nd₁ : l₁.NodupKeys) (nd₂ : l₂.NodupKeys) : (kmerge l₁ l₂).NodupKeys

-- Target theorem and proof:
theorem Perm.kmerge {l₁ l₂ l₃ l₄ : List (Sigma (fun _ : loc => val))} (nd₁ : l₁.NodupKeys) /- nd₁ is necessary -/ (nd₃ : l₃.NodupKeys)
    (p₁₂ : l₁.Perm l₂) (p₃₄ : l₃.Perm l₄) : (kmerge l₁ l₃).Perm $ kmerge l₂ l₄
:= by
  have nd₂ := nd₁
  rw [List.perm_nodupKeys p₁₂] at nd₂
  have nd₄ := nd₃
  rw [List.perm_nodupKeys p₃₄] at nd₄
  rw [List.perm_ext_iff_of_nodup] <;> try (apply List.NodupKeys.nodup ; apply kmerge_NodupKeys=> //)
  move=> [] l v
  srw !kmerge_mem2 // (List.perm_dlookup _ nd₁ nd₂ p₁₂) // (List.perm_dlookup _ nd₃ nd₄ p₃₄)
  dsimp [List.keys]
  srw (List.Perm.mem_iff (List.Perm.map Sigma.fst p₁₂)) (List.Perm.mem_iff (List.Perm.map Sigma.fst p₃₄)) (List.Perm.mem_iff p₁₂) (List.Perm.mem_iff p₃₄)