{-# OPTIONS --allow-unsolved-metas #-}
module session05 where

{-
  Possible topics for today:

  0. Random questions and comments
  1. More on program verification
  2. Numbers beyond infinity
-}

module Random where
  data  : Set where
    zero : 
    succ :   
  
  Nat : Set
  Nat = 
  
  -- parametric style (the parameter X will be fixed throughout the declaration)
  data _≡_ {X : Set} : X  X  Set where
    refl : {x : X}  x  x
  
  -- indexed style (the value of X can vary)
  data _≡'_ : {X : Set}  X  X  Set₁ where
    refl : {X : Set}  {x : X}  x ≡' x   -- this is a new freshly-minted inhabitant of _≡'_ {X} x x
    foo : zero ≡' succ zero               -- this is a new freshly-minted inhabitant of _≡'_ {ℕ} zero (succ zero)

-- More on verified algorithms. Case study: insertion sort
-- 1. Implement insertion sort
-- 2. Verify that the implementation is correct
-- 3. Reimplement insertion sort in a correct-by-construction manner

data List (A : Set) : Set where
  []  : List A
  _∷_ : A  List A  List A

open import Padova2025.ProvingBasics.Connectives.Disjunction

-- import: loading a module defined in a different file
-- open: open a module (whether it's from the same file or from an already imported file), i.e.
--       bringing the module's definition into the current scope

-- Let's fix a type A; for any elements x, y : A, a type x ≤ y of witnesses that x is at most y;
-- a decision function which inputs two elements x, y : A and outputs either a witness that x ≤ y
-- or a witness that y ≤ x.
module Implementation (A : Set) (_≤_ : A  A  Set) (cmp? : (x y : A)  x  y  y  x) where
  -- The task of the `insert` function is to behave as `_∷_`, but ensuring that the output list
  -- will be sorted if the input list was.
  --
  -- Input: a new element x and a sorted list ys
  -- Output: a sorted containing all the elements of ys, and the new element x, and none others
  insert : A  List A  List A
  insert x []       = x  []
  insert x (y  ys) with cmp? x y
  ... | left  x≤y = x  (y  ys)
  ... | right y≤x = y  insert x ys

  sort : List A  List A
  sort []       = []
  sort (x  xs) = insert x (sort xs)

  cheatsort : List A  List A
  cheatsort xs = []

module Verification (A : Set) (_≤_ : A  A  Set) (cmp? : (x y : A)  x  y  y  x) where
  -- For every list `xs`, let's introduce the type `IsOrdered xs` of witnesses that the list `xs` is (ascendingly) ordered.
  data IsOrdered : List A  Set where
    empty     : IsOrdered []
    singleton : {x : A}  IsOrdered (x  [])
    cons      : {x y : A} {ys : List A}  x  y  IsOrdered (y  ys)  IsOrdered (x  (y  ys))

  -- Include the definitions of the Implementation module, instantiated with the same three parameters as this module
  -- is instantiated with
  open Implementation A _≤_ cmp? using (sort; insert; cheatsort)

  lemma₀ : (x y : A) (ys : List A)  y  x  IsOrdered (y  ys)  IsOrdered (y  insert x ys)
  lemma₀ = {!!}

  lemma : (x : A) (ys : List A)  IsOrdered ys  IsOrdered (insert x ys)
  lemma x []       p = singleton
  lemma x (y  ys) p with cmp? x y
  ... | left  x≤y = cons x≤y p
  ... | right y≤x = lemma₀ x y ys y≤x p

  -- Logical reading: For every list xs, it is the case that sort xs is ordered.
  -- Computational reading: `theorem` is a function which inputs a list `xs` and outputs a witness that `sort xs` is ordered.
  theorem : (xs : List A)  IsOrdered (sort xs)
  theorem []       = empty
  theorem (x  xs) = lemma x (sort xs) (theorem xs)

  cheattheorem : (xs : List A)  IsOrdered (cheatsort xs)
  cheattheorem xs = empty

module CorrectByConstruction (A : Set) (_≤_ : A  A  Set) (-∞ : A) (min! : {x : A}  -∞  x) (cmp? : (x y : A)  x  y  y  x) where
  -- `OList l` is the type of ordered lists of elements of A bounded from below by `l`
  -- (i.e. all entries should be ≥ l).
  data OList (l : A) : Set where
    nil  : OList l  -- the empty ordered list
    cons : (x : A)  l  x  OList x  OList l
    -- `cons` is a function which inputs an element x and an ordered list all of whose elements are ≥ x
    -- and which outputs a freshly-minted ordered list all of whose elements are ≥ l
    -- for instance, `cons 6 (cons 2 (cons 4 nil))` should be forbidden

  insert : {l : A} (x : A)  l  x  OList l  OList l
  insert x l≤x nil             = cons x l≤x nil
  insert x l≤x (cons y l≤y ys) with cmp? x y
  ... | left  x≤y = cons x l≤x (cons y x≤y ys)
  ... | right y≤x = cons y l≤y (insert x y≤x ys)

  sort : List A  OList -∞
  sort []       = nil
  sort (x  xs) = insert x min! (sort xs)

-- Ordinal numbers beyond infinity \o/
-- 0, 1, 2, ..., ω, ω + 1, ω + 2, ..., ω·2, ω·2 + 1, ..., ω·ω = ω², ..., ω³, ...
--
-- ω = the smallest infinite number
module Ordinal where
  open import Padova2025.ProgrammingBasics.Naturals.Base

  data O : Set
  Monotonic : (  O)  Set
  _<_ : O  O  Set
  data _≤_ : O  O  Set

  Monotonic f = ((n : )  f n < f (succ n))
  -- So for a monotonic function, we will have f 0 < f 1 < f 2 < ...

  data O where
    -- There should be a first ordinal number, called "zero".
    zer : O

    -- Every ordinal number should have a successor.
    -- For instance, ω + 1 is the successor of ω.
    -- For instance, ω + 2 is the successor of ω + 1.
    suc : O  O

    -- Every monotonic infinite sequence of ordinals should have a limit.
    -- For instance, ω     is the limit of the sequence 0, 1, 2, ...
    -- For instance, ω · 2 is the limit of the sequence ω, ω+1, ω+2, ...
    -- By "monotonic", we mean strictly increasing.
    lim : (f :   O)  Monotonic f  O
    -- Not *every* sequence should have a limit, for instance we don't expect a limit for 0, 1, 0, 1, ...

  x < y = suc x  y

  -- For any ordinal numbers x and y, `x ≤ y` will be the type of witnesses that `x` is at most `y`.
  data _≤_ where
    ≤-zer      : {x : O}  zer  x
    ≤-suc-mon  : {x y : O}  x  y  suc x  suc y

    -- Together, the following two constructors expresses that `lim f fmon` is the smallest number which is ≥ than all terms of f.
    ≤-cocone   : {x : O} {n : } {f :   O} {fmon : Monotonic f}  x  f n  x  lim f fmon
    ≤-limiting : {f :   O} {fmon : Monotonic f} {x : O}  ((n : )  f n  x)  lim f fmon  x

  uno : O
  uno = suc zer

  fromℕ :   O
  fromℕ zero     = zer
  fromℕ (succ x) = suc (fromℕ x)
  -- For instance, fromℕ (succ zero) is uno.

  ω : O
  ω = lim fromℕ {!!}

  ω+1 : O
  ω+1 = suc ω

  -- ω + 1 is strictly larger than ω, whereas 1 + ω is just ω again.