/-
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.Assertion
import Bppl.Lilac.ProofRules.MeasureProduct
import Bppl.Lilac.ProofRules.WPMeas

/-! # WP proof rules

Appendic B.20 in Lilac paper

These are the main tools for reasoning about programs in Appl on top of the Iris instantiation.

## Main Statements
- `wp_cons`: The rule of rule of consequence standard in program logics.
- `wp_frame`: The frame rule standard in separation logics.
- `wp_ret`: Eliminating `wp` in an assertion
- `wp_bind`: The monadic `bind` is like the let construct, and this rule unrolls the program line by line
- `wp_unif`: Converting a syntactic `X ← unif01` program to an LProp `X ~ Unif[0,1]`
-/

namespace WP
open Appl PMF NNReal List ProbabilityTheory ProbabilityTheory.Kernel MeasureTheory.Measure
open Iris.BI.BIBase LProp Iris.BI
-- importing all of MeasureTheory imports an operator ∗ conflicting with the separating conjunct
open MeasureTheory (Measure ProbabilityMeasure)
variable {ds : List Ty} {rs : List Ty} {r r' : Ty} {A B ty₁ ty₂ : Ty}
noncomputable section

abbrev ret [MeasurableSpace α] (a : α) := dirac a

-- Appendix B.20
theorem wp_cons {P Q : RV ⟪A⟫ → LProp} {M : RV (ProbabilityMeasure ⟪A⟫)}
    (P_entails_Q : ∀ X : RV ⟪A⟫, iprop(P X ⊢ Q X)) : iprop(wp M P ⊢ wp M Q) := by
  intro Ω wp_l Ω_fr Ω_pre μ Ω_pre_le_μ  _ msα D_ext
  have ⟨X, Ω', Ω_post, μ', Ω_post_le_μ, calc_block, postcond⟩ := wp_l Ω_fr Ω_pre μ Ω_pre_le_μ (msα := msα) D_ext
  use X, Ω', Ω_post, μ', Ω_post_le_μ, calc_block, P_entails_Q X Ω' postcond

open Krm in
theorem wp_frame {F : LProp} {Q : RV ⟪A⟫ → LProp} {M : RV (ProbabilityMeasure ⟪A⟫)} :
    iprop(F ∗ (wp M Q) ⊢ wp M (fun X ↦ iprop(F ∗ Q X))) := by
  rintro Ω ⟨Ω_F, Ω_M, Ω_F_M, hΩ_F_M,  hF, wp_left⟩ Ω_fr Ω_fr_Ω μ hΩ_fr_Ω _ _ D_ext
  -- all the shuffling paranthesis needed to feed arguments into `wp_left`
  obtain ⟨Ω_fr_F_M, hΩ_fr_F_M⟩ := le_mul_mono_right' hΩ_F_M Ω_fr_Ω
  have Ω_fr_F_M_le_μ : (↓(assoc_left Ω_fr_F_M)).toPSpace ≤ PSpace.mk' μ := by
    trans (↓Ω_fr_Ω).toPSpace
    · rw [assoc_left_eq Ω_fr_F_M]
      exact hΩ_fr_F_M
    · exact hΩ_fr_Ω
  obtain ⟨X, Ω_M', Ω_fr_F_M', μ', Ω_fr_F_M'_le_μ', calc_block, post_left_wp⟩ :=
    wp_left ↓(left Ω_fr_F_M) (assoc_left Ω_fr_F_M) μ Ω_fr_F_M_le_μ D_ext
  -- and now feed these "outputs" from `wp_left` into wp_right (the goal).
  refine ⟨X, ↓(right Ω_fr_F_M'), (assoc_right Ω_fr_F_M'), μ', ?le_μ', calc_block, ?post_right_wp⟩
  case le_μ' =>
    exact (assoc_right_eq Ω_fr_F_M').symm ▸ Ω_fr_F_M'_le_μ'
  case post_right_wp =>
    use Ω_F, Ω_M', (right Ω_fr_F_M')

theorem wp_disj {P Q : RV ⟪A⟫ → LProp} {M : RV (ProbabilityMeasure ⟪A⟫)} :
    iprop((wp M P) ∨ (wp M Q) ⊢ wp M (fun X ↦ iprop(P X ∨ Q X))) := sorry

-- Appendix B.22 from Lilac paper
-- TODO: It is unclear how to express the substitution of `wp_ret`. Just a function?

theorem wp_ret (Q : RV ⟪A⟫ → LProp) (D : RV (TProd (⟪·⟫) rs)) (M : Term rs A) :
    iprop((Q (M.den ∘ᵣ D)) ⊢ wp ((Term.ret M).den ∘ᵣ D) Q) := by
  rintro Ω wp Ω_fr Ω_pre μ Ω_pre_le_μ _ _ D_ext
  use (M.den ∘ᵣ D), Ω, Ω_pre, μ, Ω_pre_le_μ
  exact ⟨rfl, wp⟩

variable [MeasurableSpace α] [MeasurableSpace β]
/-- helper needed since we use `RV` instead of as a wrapper around measurable functions. -/
lemma h_det_prod (f : RV α) (g : RV β) : det f ×ₖ det g = det (f ;; g) :=
          Kernel.deterministic_prod_deterministic f.meas g.meas

theorem wp_bind (Q : RV ⟪B⟫ → LProp) (D : RV (TProd (⟪·⟫) rs))
    (M : Term rs A.G) (N : Term (A::rs) B.G)
    : wp (M.den ∘ᵣ D) (fun X ↦ (wp (N.den ∘ᵣ (X ;; D)) Q)) ⊢ wp ((M.bind N).den ∘ᵣ D) Q := by
  rintro Ω wp_outer Ω_fr Ω_pre μ Ω_pre_le_μ _ _ D_ext
  obtain ⟨X, Ω_X, Ω_fr_X, μX, Ω_fr_X_le_μX, calc_outer, wp_inner⟩ :=
    wp_outer Ω_fr Ω_pre μ Ω_pre_le_μ (D ;; D_ext)
  obtain ⟨Y, Ω_Y, Ω_fr_Y, μY, Ω_fr_Y_le_μY, calc_inner, post_cond⟩ :=
    wp_inner Ω_fr Ω_fr_X μX Ω_fr_X_le_μX D_ext
  refine ⟨Y, Ω_Y, Ω_fr_Y, μY, Ω_fr_Y_le_μY, ?calc_left, post_cond⟩
  calc
    -- unfold denotation of syntactic bind into semantic bind
    _ = ((Kernel.comap (toMK (M.bind N).den) D D.meas) ×ₖ det D_ext) ∘ₘ μ := by rfl
    _ = (((toMK N.den) ∘ₖ (toMK M.den ×ₖ Kernel.id)).comap D.toFun D.meas ×ₖ det D_ext) ∘ₘ μ := by
      rfl
    _ = (((toMK N.den) ∘ₖ (toMK M.den ×ₖ Kernel.id).comap D.toFun D.meas) ×ₖ det D_ext) ∘ₘ μ := by
      rfl
    _ = ((toMK N.den ∥ₖ Kernel.id) ∘ₖ ((toMK M.den ×ₖ Kernel.id).comap D.toFun D.meas ×ₖ det D_ext))
            ∘ₘ μ := by
        -- We know that it `IsSFinite`, since `M.den`, `IsMarkovKernel`
        haveI : IsSFiniteKernel ((toMK M.den ×ₖ Kernel.id).comap D.toFun D.meas) :=
          Kernel.IsSFiniteKernel.comap _ D.meas
        rw [Kernel.parallelComp_comp_prod, Kernel.id_comp]
    _ = (toMK N.den ∥ₖ Kernel.id) ∘ₘ (((toMK M.den ×ₖ Kernel.id).comap D.toFun D.meas ×ₖ det D_ext)
            ∘ₘ μ) := Measure.comp_assoc.symm
    _ = (toMK N.den ∥ₖ Kernel.id) ∘ₘ ((((toMK M.den).comap D D.meas ×ₖ det D) ×ₖ det D_ext) ∘ₘ μ) :=
      by rw [Kernel.comap_prod, Kernel.id_comap]; rfl
    _ = (toMK N.den ∥ₖ Kernel.id) ∘ₘ (((det X ×ₖ det D) ×ₖ det D_ext) ∘ₘ μX) := by
        suffices h_inner :
            ((((toMK M.den).comap D D.meas ×ₖ det D) ×ₖ det D_ext) ∘ₘ μ) =
              (((det X ×ₖ det D) ×ₖ det D_ext) ∘ₘ μX) by
          rw [h_inner]
        have h_det_prod : det D ×ₖ det D_ext = det (D ;; D_ext) :=
          Kernel.deterministic_prod_deterministic D.meas D_ext.meas
        -- assoc yields an additional `.map prodAssoc.symm` like
        -- `(A ×ₖ B) ×ₖ C = (A ×ₖ (B ×ₖ C)).map prodAssoc.symm` on both sides
        -- push the `.map` through `∘ₘ μ`, then reduce to `calc_outer`.
        rw [← Kernel.prodAssoc_symm_prod ((toMK M.den).comap D D.meas) (det D_ext) (det D),
            ← Kernel.prodAssoc_symm_prod (det X) (det D_ext) (det D),
            ← Measure.map_comp _ _ MeasurableEquiv.prodAssoc.symm.measurable,
            ← Measure.map_comp _ _ MeasurableEquiv.prodAssoc.symm.measurable]
        congr 1
        rw [h_det_prod]
        exact calc_outer
    _ = ((toMK N.den ∥ₖ Kernel.id) ∘ₖ ((det X ×ₖ det D) ×ₖ det D_ext)) ∘ₘ μX := Measure.comp_assoc
    _ = (((toMK N.den) ∘ₖ (det X ×ₖ det D)) ×ₖ det D_ext) ∘ₘ μX  := by
        haveI : IsSFiniteKernel (det X ×ₖ det D) := inferInstance
        rw [Kernel.parallelComp_comp_prod, Kernel.id_comp]
    _ = (det Y ×ₖ det D_ext) ∘ₘ μY := by
        -- Compose `det X ×ₖ det D = det (X ;; D)` with `K ∘ₖ deterministic g = comap K g`
        -- to identify the LHS kernel with `toMK (N.den ∘ᵣ (X ;; D))`, then `calc_inner`.
        convert calc_inner using 4
        change toMK N.den ∘ₖ (deterministic X.toFun X.meas ×ₖ deterministic D.toFun D.meas)
          = toMK (N.den ∘ᵣ X ;; D).toMeasurableFun
        rw [Kernel.deterministic_prod_deterministic X.meas D.meas]
        exact Kernel.comp_deterministic_eq_comap (toMK N.den) (X ;; D).meas

theorem wp_unif (Q : RV ⟪Ty.real⟫ → LProp) (D : RV (List.TProd (⟪·⟫) rs)) :
    iprop(∀ (X : RV ⟪Ty.real⟫), iprop(X ∼ lebI' -∗ Q X))
    ⊢ wp (Term.unif01.den ∘ᵣ D) Q :=
  wp_meas Ty.real ⟨λ i ↦ (i : ℝ), by fun_prop⟩ Q D

end
end WP
