{-# OPTIONS --allow-unsolved-metas #-}

{-
  Proposal for today:

  -1. Random questions and comments
  0. Upcoming events and conferences
  1. Review of some of the equational reasoning exercises
  2. Logical connectives
     - Review of ¬
     - ∨
     - ∃
     - ∧
  3. Program verification
-}

data  : Set where
-- empty data type

¬_ : Set  Set
¬ X = (X  )

-- Logical reading: If A, and if ¬ A, then ⊥. (The "law of non-contradiction".)
-- Computational reading: `foo` is a function which inputs
-- 0. an arbitrary type A (implicitly)
-- 1. a witness of A
-- 2. a witness of ¬ A
-- and which outputs a witness of ⊥.
foo : {A : Set}  A  ¬ A  
foo x f = f x
-- In English: "Assume A (let's call this assumption "x"). Assume ¬ A
-- (which means that A → ⊥) (let's call this assumption "f"). We want to show ⊥.
-- This follows by using assumption f, applied to the instance x."

-- bar : {A : Set} → "A or ¬ A"  -- the "law of excluded middle"
-- bar = ?

data Weird : Set where
  succ : Weird  Weird
-- This type is empty, but not manifestly empty.

-- Logical reading: "For every inhabitant of type Weird, we have a contradiction."
-- Computational reading: `theorem` is a function which inputs a value of type Weird and which outputs a value of type ⊥.
theorem : Weird  
theorem (succ x) = theorem x

open import Padova2025.ProgrammingBasics.Naturals.Base
open import Padova2025.ProgrammingBasics.Naturals.Arithmetic
open import Padova2025.ProvingBasics.Equality.Base

-- For every type A and every type B, there will be a new type `A ⊎ B`,
-- the "tagged union A and B" / "disjoint union of A and B".
data _⊎_ (A B : Set) : Set where
  left  : A  A  B
  right : B  A  B
-- The inhabitants of `A ⊎ B` look as follows:
-- Firstly,  an inhabitant can be of the form `left x`,  where `x : A`.
-- Secondly, an inhabitant can be of the form `right y`, where `y : B`.
-- There are no more inhabitants.

data 𝟙 : Set where
  * : 𝟙
-- This type contains exactly one inhabitant, namely *.
-- "The unit type"
-- In set-theoretical notation: 𝟙 = { * }.

-- On a blackboard, we could form 𝟙 ∪ 𝟙 = { *, * } = { * } = 𝟙.
-- In contrast, 𝟙 ⊎ 𝟙 contains two distinct elements, namely `left *` and `right *`.

-- Intuitively, a witness of "A ∨ B" should either be a witness of A or a witness of B.
-- An inhabitant of (two + two ≡ four) ⊎ (two + two ≡ five) is either of the form left p,
-- where p is a witness that two + two equals four, or of the form right q,
-- where q is a witness that two + two equals five.

-- 2 + 2 equals 4 or 2 + 2 equals 5.
observation : (two + two  four)  (two + two  five)  --- \u+
observation = left refl

data Even :   Set where
  base-even : Even zero
  step-even : {n : }  Even n  Even (succ (succ n))

data Odd :   Set where
  base-odd : Odd one
  step-odd : {n : }  Odd n  Odd (succ (succ n))

five-is-odd : Odd five
five-is-odd = step-odd (step-odd base-odd)

-- Logical reading: For every number x, x is even or x is odd.
-- Computational reading: `even-or-odd` is a function which inputs a number `x`
-- and which outputs either `left p`, where `p` is a witness that `x` is even,
-- or `right q`, where `q` is a witness that `x` is odd.
even-or-odd : (x : )  Even x  Odd x
even-or-odd zero            = left base-even
even-or-odd (succ zero)     = right base-odd
even-or-odd (succ (succ x)) with even-or-odd x
... | left  p = left  (step-even p)
... | right q = right (step-odd q)

-- In mainstream programming languages which don't have dependent types, we could only
-- write a function of type "even-or-odd : ℕ → Bool" which would output false or true
-- depending on whether the input number is even or odd. In Agda, with dependent types,
-- we can do better and output an informative witness of the correct alternative.


-- "For every number x, either x is zero or there is a number y such that x ≡ succ y."
-- "∀x. (x = 0 ∨ (∃y. x = succ(y)))"

-- "Beyond any natural number, there is an even number."
-- "∀x. ∃y. Even(y) ∧ y ≥ x."

-- Idea: A witness of a claim of the form "∃x : A. P x" is a pair (x , p)
-- consisting of an element x : A and a witness p : P x. The type of witnesses
-- of "∃x : A. P x" will be written "Σ A P".
data Σ (A : Set) (P : A  Set) : Set where
  _,_ : (x : A)  P x  Σ A P
-- The inhabitants of Σ A P look as follows:
-- (x , p)
-- where x : A and p : P x. So the inhabitants are "dependent pairs".

-- "There is an even number."
-- "∃x. Even(x)"
there-is-an-even-number : Σ  Even
there-is-an-even-number = (zero , base-even)

there-is-an-even-number' : Σ  Even
there-is-an-even-number' = (two , step-even base-even)

-- `Σ ℕ Even` is an example for a type of witnesses which contains more than one witness.
-- In fact, it contains infinitely many witnesses.

-- "∀x. (x = 0 ∨ (∃y. x = succ(y)))"
-- The expression "λ y → (x ≡ succ y)" denotes a function of type ℕ → Set.
-- This function does not have a name (it is an anonymous function).
-- This function maps a number `y` to the type `x ≡ succ y`.
-- In blackboard mathematics, this expression is written "y ↦ (...)".
-- In lambda calculus, this expression is written "λy. (...)".
-- If we setup custom syntax, then "Σ ℕ (λ y → (x ≡ succ y))" can also be written as
-- "Σ[ y ∈ ℕ ] x ≡ succ y" or even as "∃[ y ] x ≡ succ y".
zero-or-succ : (x : )  (x  zero)  Σ   y  (x  succ y))
zero-or-succ zero     = left refl
zero-or-succ (succ a) = right (a , refl)

-- "It is not the case there exists a value of type Weird."
-- "It is not the case that there exists a value x : Weird such that nothing in particular holds".
theorem' : ¬ Σ Weird  x  𝟙)
theorem' (x , _) = theorem x

-- Idea: A witness of A ∧ B should consist of a witness of A and a witness of B.
-- So the type of witnesses of A ∧ B should be the cartesian product of the type of witnesses of A
-- and the type of witnesses of B.
data _×_ (A B : Set) : Set where  -- \times
  _,_ : A  B  A × B

zero-is-even-and-one-is-odd : Even zero × Odd one
zero-is-even-and-one-is-odd = (base-even , base-odd)

-- Logical reading: If A ∧ B, then A.
-- Computational reading: `conj-elimination₁` is a function which inputs
-- 1. a type A (implicitly)
-- 2. a type B (implicitly)
-- 3. a witness that A and B holds
-- and which outputs a witness of A.
conj-elimination₁ : {A B : Set}  A × B  A
conj-elimination₁ (x , y) = x

data Bool : Set where
  false : Bool
  true  : Bool

id : Bool  Bool
id =  x  x)
-- same as: id x = x

neg : Bool  Bool
-- neg false = true
-- neg true  = false
neg = λ { false  true ; true  false }

neg' : Bool  Bool
neg' = λ
  { false  true
  ; true   false
  }

{-# TERMINATING #-}
f :   
f zero     = zero
f (succ x) = f (f x)
-- f one = f (succ zero) = f (f zero) = f zero = zero
-- f two = f (succ one) = f (f one) = f zero = zero
-- ...
-- This definition gives rise to a total well-defined function, but it's not obvious
-- to Agda that this is the case.

trivial-observation : (x : )  f (succ x)  f (f x)
trivial-observation x = refl

{-# NON_TERMINATING #-}
loop : 
loop = loop

{-
  Six options how to deal with termination issues:
  1. Give up and implement a different function
  2. Promise (without a proof) to Agda that the function is terminating
  3. Mark the function as potentially non-terminating
  4. Use a brittle kind of gas
  5. Use a more sophisticated version of gas
  6. Employ the Bove–Capretta method for proving that the function terminates
-}

f-brittle-gas :     
f-brittle-gas zero       x        = five  -- some fantasy value, hopefully never reached
f-brittle-gas (succ gas) zero     = zero
f-brittle-gas (succ gas) (succ x) = f-brittle-gas gas (f-brittle-gas gas x)

f-brittle-gas' :   
f-brittle-gas' x = f-brittle-gas (succ x) x  -- enough gas so that the fantasy value is not reached

we-should-prove : (x : )  f-brittle-gas' (succ x)  f-brittle-gas' (f-brittle-gas' x)
we-should-prove x = {!!}

-- Bove–Capretta approach to our function f

-- Idea: First formalize f as a potentially partial function (a
-- function which need not be defined on all inputs), and then prove
-- that f is total (i.e. defined on all inputs).

-- F : ℕ → ℕ
-- F zero     = zero
-- F (succ x) = F (F x)

-- "Def x" is the type of witnesses that F is defined on the input x.
data Def :   Set
F : (x : )  Def x  

data Def where
  base : Def zero
  rec  : {x : }  (p : Def x)  Def (F x p)  Def (succ x)
  -- If F is defined on the input x (with witness p) and if F is defined on the input (F x p),
  -- then F is defined on the input succ x.

F zero     p         = zero
F (succ x) (rec p q) = F (F x p) q

F-is-total : (x : )  Def x
F-is-total x = {!!}

F' :   
F' x = F x (F-is-total x)


-- Program verification

-- Approach A to establishing correctness: Implement, then verify.

-- The following function has the task of determining whether the two input numbers are equal.
eq? :     Bool
eq? zero     zero     = true
eq? zero     (succ y) = false
eq? (succ x) zero     = false
eq? (succ x) (succ y) = eq? x y

open import Padova2025.ProvingBasics.Equality.General

-- "soundness"
eq?-correct₁ : (x y : )  eq? x y  true  x  y
eq?-correct₁ zero     zero     p = refl
eq?-correct₁ (succ x) (succ y) p = cong succ (eq?-correct₁ x y p)

-- "completeness"
eq?-correct₂ : (x y : )  x  y  eq? x y  true
eq?-correct₂ x y p = {!!}


-- Approach B: Correct by construction
eq?' : (x y : )  (x  y)  (¬ (x  y))
eq?' zero     zero     = left refl
eq?' zero     (succ y) = right  { () })
eq?' (succ x) zero     = right {!!}
eq?' (succ x) (succ y) with eq?' x y
... | left  p = left (cong succ p)
... | right p = {!!}

eq?'' : (x y : )  Bool
eq?'' x y with eq?' x y
... | left  p = true
... | right q = false