module Reasoning

-- From the "Lists" chapter of Programming Language Foundations in Agda
-- https://plfa.github.io/Lists/

-- We define a few types and functions on lists and prove a couple of properties
-- about them. This example demonstrates mixfix operators.

infix 4 _≡_
data _≡_ : ∀ A. A → A → U where
  Refl : ∀ A. {0 x : A} → x ≡ x

sym : ∀ A. {0 x y : A} → x ≡ y → y ≡ x
sym Refl = Refl

trans : ∀ A. {0 x y z : A} → x ≡ y → y ≡ z → x ≡ z
trans Refl eq = eq

cong : ∀ A B. (0 f : A → B) {0 x y : A}
  → x ≡ y
  → f x ≡ f y
cong f Refl = Refl

cong-app : ∀ A B. {0 f g : A → B}
  → f ≡ g
  → (0 x : A) →  f x ≡ g x
cong-app Refl = λ y => Refl

infixl 1 begin_
infixr 2 _≡⟨⟩_ _≡⟨_⟩_
infixl 3 _∎

begin_ : ∀ A. {0 x y : A} → x ≡ y → x ≡ y
begin x≡y = x≡y

_≡⟨⟩_  : ∀ A. (0 x : A) {0 y : A} → x ≡ y → x ≡ y
x ≡⟨⟩ x≡y = x≡y

_≡⟨_⟩_ : ∀ A. (0 x : A) {0 y z : A} → x ≡ y → y ≡ z → x ≡ z
x ≡⟨ x≡y ⟩ y≡z = trans x≡y y≡z

_∎ : ∀ A. (0 x : A) → x ≡ x
x ∎ = Refl

-- Natural numbers are zero (Z) or the successor (S) of a natural number
-- We'll use these to represent the length of lists
data Nat : U where
    Z : Nat
    S : Nat -> Nat

-- declare a plus operator and define the corresponding function
infixl 7 _+_
_+_ : Nat -> Nat -> Nat
Z + m = m
S n + m = S (n + m)

-- A list is empty (Nil) or a value followed by a list (separated by the :: operator)
infixr 7  _::_
data List : U -> U where
    Nil : ∀ A. List A
    _::_ : ∀ A. A -> List A -> List A

-- length of a list is defined inductively
length : ∀ A . List A -> Nat
length Nil = Z
length (x :: xs) = S (length xs)

-- List concatenation
infixl 5 _++_
_++_ : ∀ A. List A -> List A -> List A
Nil ++ ys = ys
(x :: xs) ++ ys = x :: (xs ++ ys)

-- This lets us replace a with b inside an expression if a ≡ b
replace : ∀ A a b. (P : A -> U) -> a ≡ b -> P a -> P b
replace p Refl x = x

-- if concatenate two lists, the length is the sum of the lengths
-- of the original lists
length-++ : ∀ A. (xs ys : List A) -> length (xs ++ ys) ≡ length xs + length ys
length-++ Nil ys = Refl
length-++ (x :: xs) ys = cong S (length-++ xs ys)

-- a function to reverse a list
reverse : ∀ A. List A -> List A
reverse Nil = Nil
reverse (x :: xs) = reverse xs ++ (x :: Nil)

-- if we add an empty list to a list, we get the original back
++-identity : ∀ A. (xs : List A) -> xs ++ Nil ≡ xs
++-identity Nil = Refl
++-identity (x :: xs) = cong (_::_ x) (++-identity xs)

-- concatenation is associative
++-associative : ∀ A. (xs ys zs : List A) -> xs ++ (ys ++ zs) ≡ (xs ++ ys) ++ zs
++-associative Nil ys zs = Refl
++-associative (x :: xs) ys zs =
  begin
    (x :: xs) ++ (ys ++ zs)
  ≡⟨⟩
    x :: (xs ++ (ys ++ zs))
  ≡⟨ cong (_::_ x) (++-associative xs ys zs) ⟩
    x :: ((xs ++ ys) ++ zs)
  ≡⟨⟩
    (x :: xs ++ ys) ++ zs
  ∎

-- reverse distributes over ++, but switches order
reverse-++-distrib : ∀ A. (xs ys : List A) -> reverse (xs ++ ys) ≡ reverse ys ++ reverse xs
reverse-++-distrib Nil ys = -- sym (++-identity (reverse ys))
  begin
    reverse ( Nil ++ ys )
  ≡⟨⟩
    reverse ys
  ≡⟨ sym (++-identity (reverse ys)) ⟩
    reverse ys ++ Nil
  ∎

reverse-++-distrib (x :: xs) ys =
  begin
    reverse ((x :: xs ) ++ ys)
  ≡⟨⟩
    reverse (xs ++ ys) ++ (x :: Nil)
  ≡⟨ cong (\ z => z  ++ (x :: Nil)) (reverse-++-distrib xs ys) ⟩
    (reverse ys ++ reverse xs) ++ (x :: Nil)
  ≡⟨ sym (++-associative (reverse ys) (reverse xs) (x :: Nil)) ⟩
    reverse ys ++ reverse (x :: xs)
  ∎

-- reverse of reverse gives you the original list
reverse-involutive : ∀ A. (xs : List A) -> reverse (reverse xs) ≡ xs
reverse-involutive Nil = Refl
reverse-involutive (x :: xs) =
  begin
    reverse (reverse (x :: xs))
  ≡⟨ reverse-++-distrib (reverse xs) (x :: Nil) ⟩
    (x :: Nil) ++ reverse (reverse xs)
  ≡⟨ cong (_::_ x) (reverse-involutive xs) ⟩
    x :: xs
  ∎

-- helper for a different version of reverse
shunt : ∀ A. List A -> List A -> List A
shunt Nil ys = ys
shunt (x :: xs) ys = shunt xs (x :: ys)

-- lemma
shunt-reverse : ∀ A. (xs ys : List A) -> shunt xs ys ≡ reverse xs ++ ys
shunt-reverse Nil ys = Refl
shunt-reverse (x :: xs) ys =
  begin
    shunt xs (x :: ys)
  ≡⟨ shunt-reverse xs (x :: ys) ⟩
    reverse xs ++ (x :: ys)
  ≡⟨⟩
    reverse xs ++ ((x :: Nil) ++ ys)
  ≡⟨ (++-associative (reverse xs) (x :: Nil) ys) ⟩
    reverse (x :: xs) ++ ys
  ∎

-- an alternative definition of reverse
reverse' : ∀ A. List A -> List A
reverse' xs = shunt xs Nil

-- proof that the reverse and reverse' give the same results
reverses : ∀ A. (xs : List A) → reverse' xs ≡ reverse xs
reverses xs = trans (shunt-reverse xs Nil) (++-identity _)
