/-
Copyright (c) 2026 Edwin Fernando. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Edwin Fernando
-/

import Bppl.Lilac.Appl
import Bppl.Lilac.BI

/-! # Probability-specific connectives of Lilac

On top of the generic separation logic connectives provided by the `BI` instance, Lilac introduces
probability-specific assertions: ownership of random variables, distribution assertions, almost-sure
equality, expectations, and the weakest precondition for APPL programs. These are defined as
predicates over `PSp` (probability spaces with finite footprint) and bundled with their
monotonicity proofs into `LProp`.

The notations `∼`, `≗`, and `𝔼[ ]= ` are registered for use inside the `iprop(...)` proof mode.

## Main Definitions
- `LProp`: Lilac propositions, i.e. `IProp PSp`
- `LProp.own`: Ownership of a random variable, asserting `E` is measurable w.r.t. the resource's
σ-algebra
- `LProp.dist`: Distribution assertion `E ∼ μ`, asserting the pushforward of the resource's measure
along `E` equals `μ`
- `LProp.expectation`: Expectation assertion `𝔼[E]= e`, asserting the integral of `E` equals `e`
- `LProp.eq`: Almost-sure equality `E₁ ≗ E₂`, with the additional closure condition required by
Lilac's semantics
- `LProp.wp`: Weakest precondition for APPL programs, quantifying over frame resources and
extension variables in the style of Lilac's frame-preserving semantics
-/

open Appl MeasureTheory MeasureTheory.Measure ProbabilityTheory ProbabilityTheory.Kernel
open List (TProd)

open Iris.Instances Iris.Instances.instBIBase

/-- Lilac propositions -/
abbrev LProp := IProp PSp

-- Using `MeasurableSpace.pi`
instance : MeasurableSpace HC := inferInstance

namespace LProp

-- ds: deterministic context, rs: random variable context
variable {ds : List Ty} {rs : List Ty} {A : Ty}

def own (E : RV ⟪A⟫) : LProp :=
  ⟨fun Ω ↦ Measurable[Ω.ms] E, by
    intro σ₁ σ₂ hle hm
    exact hm.mono hle.1 le_rfl ⟩

/-- We depart from the convention of using `RV` for for the variable `E` here.
The measurability of `E : RV ⟪A⟫` would be given as `HC.Inf_borel` on the domain
and this is unnessecary info. So we instead take a simple function E. Measurability is
asserted as part of the resource in the original defintion. -/
def dist (E : HC → ⟪A⟫) (μ : Measure ⟪A⟫) : LProp :=
  ⟨fun Ω ↦ Measurable[Ω.ms] E ∧ μ = @Measure.map _ _ Ω.ms _ E Ω.μ,
  -- monotonicity proof
  by sorry
    -- intro σ₁ σ₂ hle ⟨hm, hbind⟩
    -- change σ₁.toPSpace ≤ σ₂.toPSpace at hle
    -- obtain ⟨hms, hmu⟩ := hle
    -- refine ⟨hm.mono hms le_rfl, ?_⟩
    -- rw [hbind, hmu,
    --     Measure.bind_dirac_eq_map _ (hm.mono hms le_rfl),
    --     Measure.bind_dirac_eq_map _ hm,
    --     Measure.cast_eq_self,
    --     Measure.map_map hm (measurable_id.mono hms le_rfl)]
    -- rfl
  ⟩

def expectation (E : RV ⟪Ty.real⟫) (e : ℝ) : LProp :=
  ⟨fun Ω ↦ Measurable[Ω.ms] E ∧ ∫ ω, E ω ∂(Ω.μ) = e, sorry⟩

-- We do not use `ae` filter and general mathlib infrastructure, because these don't give the
-- very particular measurability of spaces that we require
def eq (E₁ E₂ : RV ⟪A⟫) : LProp :=
  ⟨fun ⟨⟨⟨ℱ, μ⟩, hμ⟩, _⟩ ↦
    let F := {ω | E₁ ω = E₂ ω}
    MeasurableSet[ℱ] F ∧ μ F = 1 ∧
    -- (F ∪ ⟨X₁, X₂⟩ᶠ⁻¹' ·) '' (⟪A⟫ᵐ.prod ⟪A⟫ᵐ).MeasurableSet' ⊆ ℱ.MeasurableSet'
    ∀ x :Set (⟪A⟫ × ⟪A⟫), MeasurableSet x →
      MeasurableSet[ℱ] (F ∪ (fun ω ↦ (E₁ ω, E₂ ω))⁻¹' x),
    -- monotonicity proof
    by
    intro σ₁ σ₂ hle ⟨hF_meas, hF_prob, hF_union⟩
    change σ₁.toPSpace ≤ σ₂.toPSpace at hle
    obtain ⟨hms, hmu⟩ := hle
    refine ⟨hms _ hF_meas, ?_, fun x hx => hms _ (hF_union x hx)⟩
    rw [hmu] at hF_prob
    rwa [Measure.map_apply (measurable_id.mono hms le_rfl) hF_meas, Set.preimage_id] at hF_prob
  ⟩

def PSpace.mk' {Ω : Type*} {ms : MeasurableSpace Ω} (μ : ProbabilityMeasure Ω) : PSpace Ω :=
  ⟨⟨_, μ.1⟩, μ.2⟩

noncomputable abbrev det [MeasurableSpace α] (X : RV α) : Kernel HC α  :=
  deterministic X X.meas

open HC in
def wp (M : RV (⟪Ty.G A⟫)) (Q : RV ⟪A⟫ → LProp) : LProp :=
  ⟨fun Ω ↦
  ∀ Ω_fr : PSp, ∀ Ω_pre : ✓'(Ω_fr ⋆ Ω), ∀ μ : @ProbabilityMeasure HC Inf_borel,
    (↓Ω_pre).1 ≤ PSpace.mk' μ → ∀ {α: Type} {msα : MeasurableSpace α} (D_ext : RV α),
  ∃ X : RV ⟪A⟫, ∃ Ω' : PSp, ∃ Ω_post: ✓'(Ω_fr ⋆ Ω'), ∃ μ' : @ProbabilityMeasure HC Inf_borel,
    (↓Ω_post).1 ≤ PSpace.mk' μ' ∧
  -- (μ.1.bind (fun ω ↦ (M ω).bind (fun v ↦ Measure.dirac (D ω, v)))) =
  --   (Measure.bind μ'.1 (fun ω ↦ Measure.dirac (D ω, X ω))) ∧
  (toMK M ×ₖ det D_ext) ∘ₘ μ = (det X ×ₖ det D_ext) ∘ₘ μ' ∧
  (Q X).1 Ω'
  ,
  -- monotonicity proof
  by
  intro σ₁ σ₂ hle hσ₁ Ω_fr Ω_pre μ hpre_le _ _ D
  -- From σ₁ ≤ σ₂ and ✓'(Ω_fr ⋆ σ₂), get ✓'(Ω_fr ⋆ σ₁) with ↓ ≤ ↓
  obtain ⟨Ω_pre₁, hle_pre⟩ := Krm.le_mul_mono_right' hle Ω_pre
  have hpre_le' : (↓Ω_pre₁).toPSpace ≤ PSpace.mk' μ :=
    (show (↓Ω_pre₁).toPSpace ≤ (↓Ω_pre).toPSpace from hle_pre).trans hpre_le
  exact hσ₁ Ω_fr Ω_pre₁ μ hpre_le' D
  ⟩

open Iris.BI

syntax:52 term:53 " ∼ " term:53 : term

macro_rules
  | `(iprop($rv ∼ $dist)) => `(dist $rv $dist)

delab_rule dist
  | `($_ $rv $dist) => do ``(iprop($(← unpackIprop rv) ∼ iprop($(← unpackIprop dist))))

syntax:54 term:53 " ≗ " term:53 : term

macro_rules
  | `(iprop($E₁ ≗ $E₂)) => `(eq $E₁ $E₂)

delab_rule eq
  | `($_ $E₁ $E₂) => do ``(iprop($(← unpackIprop E₁) ≗ $(← unpackIprop E₂)))

syntax:53 " 𝔼[ " term:52 " ]= " term:54 : term

macro_rules
  | `(iprop(𝔼[$E]=$e)) => `(expectation $E $e)

delab_rule eq
  | `($_ $E₁ $E₂) => do ``(iprop($(← unpackIprop E₁) ≗ $(← unpackIprop E₂)))

-- Why am I needing to use nested iprop when using this notation??
-- My delab rules are naive, they need to use unpack?
-- syntax:52 " ∀ᵣᵥ: " term:53 " , " term:53 : term

-- macro_rules
--   | `(iprop(∀ᵣᵥ: $A , $P)) => `(forall_rv $A $P)

-- delab_rule eq
--   | `($_ $A $P) => `(iprop(∀ᵣᵥ:$A , $P))

end LProp

/- Satisfaction relation: `(γ, D, Ω)⊨ P` means `P` holds under deterministic env `γ`,
    random env `D`, and resource `Ω` -/
-- notation:50 "(" γ ", " D ", " Ω ")⊨ " P => Assertion.denote P γ D Ω
