module My.Nats
 
import My.Structures

%default total
%hide Z
%hide S


public export
data ℕ : Type where
  Z : ℕ 
  S : ℕ -> ℕ 

public export
fromIntegerNat : Integer -> ℕ 
fromIntegerNat 0 = Z
fromIntegerNat n =
  if (n > 0) then
    S (fromIntegerNat (assert_smaller n (n - 1)))
  else
    Z

one : ℕ
one = S Z

public export
add : ℕ -> ℕ -> ℕ 
add Z a = a
add (S a) b = S (add a b)

public export
multiply : ℕ -> ℕ -> ℕ 
multiply Z a = Z
multiply (S a) b = add b (multiply a b)

public export
raiseToPower : ℕ -> ℕ -> ℕ 
raiseToPower a Z = one
raiseToPower a (S b) = multiply a (raiseToPower a b)

public export
monus : ℕ -> ℕ -> ℕ 
monus (S a) (S b) = monus a b
monus a Z = a
monus _ (S _) = Z

public export
naturalInduction : (P: ℕ -> Type) -> P Z -> ({x: ℕ} -> P x -> P (S x)) -> (x: ℕ) -> P x
naturalInduction p base recurse Z = base
naturalInduction p base recurse (S a) = recurse (naturalInduction p base recurse a) 

public export
Num ℕ where
  fromInteger = fromIntegerNat 
  (+) = add
  (*) = multiply

public export
%hint
setoidNats : My.Structures.Setoid ℕ
setoidNats = trivialSetoid ℕ

---------- Proofs
public export
succCommutesAddition : (a, b: ℕ) -> add (S a) b = add a (S b)
succCommutesAddition Z a = Refl
succCommutesAddition (S c) b = let 
  rec = succCommutesAddition c b
  in rewrite rec in Refl

public export
additionIsAssociative : AssociativityProof My.Nats.add
additionIsAssociative Z b c = Refl
additionIsAssociative (S a) b c = let
  rec = additionIsAssociative a b c
  in rewrite rec in Refl

public export
additionRightIdentity : RightIdentityProof My.Nats.add 0
additionRightIdentity Z = Refl
additionRightIdentity (S x) = rewrite additionRightIdentity x in Refl

public export
additionIsCommutative : CommutativityProof My.Nats.add
additionIsCommutative Z b = sym (additionRightIdentity b)
additionIsCommutative (S x) Z = rewrite additionIsCommutative x Z in Refl
additionIsCommutative (S x) (S y) = 
    rewrite sym (succCommutesAddition x y) in
    rewrite additionIsCommutative y (S x) in 
      Refl

----- Multiplication proofs
public export
multiplicationRightNullification : (a: ℕ) -> multiply a 0 = 0
multiplicationRightNullification Z = Refl
multiplicationRightNullification (S x) = rewrite multiplicationRightNullification x in Refl

public export
multiplicationRightIdentity : (a: ℕ) -> a * 1 = a
multiplicationRightIdentity Z = Refl
multiplicationRightIdentity (S x) = rewrite multiplicationRightIdentity x in Refl

public export
multiplicationLeftIdentity : (a: ℕ) -> a = 1 * a
multiplicationLeftIdentity a = rewrite additionRightIdentity a in Refl

public export
multiplicationDistributesAddition : (a, b, c: ℕ) -> a * (b + c) = a * b + a * c
multiplicationDistributesAddition Z b c = Refl
multiplicationDistributesAddition (S x) b c
  = let rec = multiplicationDistributesAddition x b c 
  in rewrite rec 
  in rewrite additionIsAssociative b c ((x * b) + (x * c))
  in rewrite additionIsAssociative b (x * b) (c + (x * c))
  in rewrite additionIsCommutative (x * b) (c + (x * c))
  in rewrite additionIsAssociative c (x * c) (x * b)
  in rewrite additionIsCommutative (x * b) (x * c)
  in Refl

public export
succIsPlusOne : (a: ℕ) -> S a = a + 1
succIsPlusOne Z = Refl
succIsPlusOne (S x) = rewrite additionIsCommutative x 1 in Refl

public export
multiplicationisCommutative : CommutativityProof My.Nats.multiply
multiplicationisCommutative Z b = sym (multiplicationRightNullification b)
multiplicationisCommutative (S x) b = 
  rewrite succIsPlusOne x in
  rewrite multiplicationDistributesAddition b x 1 in
  rewrite multiplicationRightIdentity b in
  rewrite additionIsCommutative b (x * b) in
  rewrite multiplicationisCommutative x b in
  Refl


public export
multiplicationIsAssociative : AssociativityProof My.Nats.multiply
multiplicationIsAssociative Z b c = Refl
multiplicationIsAssociative (S x) y c =  
  rewrite multiplicationisCommutative (y + (x * y)) c in
  rewrite multiplicationDistributesAddition c y (x * y) in
  rewrite multiplicationisCommutative y c in
  rewrite sym (multiplicationIsAssociative c x y) in
  rewrite sym (multiplicationIsAssociative x c y) in
  rewrite sym (multiplicationisCommutative x c) in
  Refl

---------- Monus proofs
public export
xMinusXIsZero : (a: ℕ) -> (monus a a) = 0
xMinusXIsZero Z = Refl
xMinusXIsZero (S x) = xMinusXIsZero x

public export
additionNullifiesMonus : (a, b: ℕ) -> (monus (a + b) b) = a
additionNullifiesMonus Z b = xMinusXIsZero b
additionNullifiesMonus (S x) Z = rewrite additionRightIdentity x in Refl
additionNullifiesMonus x (S y) = rewrite sym $ succCommutesAddition x y in additionNullifiesMonus x y

---------- Equality proofs
public export
additionPreservesEquality : {a, b: ℕ} -> (c: ℕ) -> (a = b) -> (a + c = b + c)
additionPreservesEquality c prf = cong (+c) prf

public export
substractionPreservesEquality : {a, b: ℕ} -> (c: ℕ) -> (a + c = b + c) -> (a = b)
substractionPreservesEquality c prf = let
    middle : (monus (a + c) c = monus (b + c) c)
    middle = cong (\e => monus e c) prf

    left : (a = monus (a + c) c)
    left = sym $ additionNullifiesMonus a c

    right : (monus (b + c) c = b)
    right = additionNullifiesMonus b c
    in (left `trans` middle) `trans` right

public export
multiplicationPreservesEquality : {a, b: ℕ} -> (c: ℕ) -> (a = b) -> (a * c = b * c)
multiplicationPreservesEquality c prf = cong (*c) prf

---------- Interace implementations
public export
[additionSemigroup] My.Structures.Semigroup ℕ where
  ∘ = add
  associativityProof = additionIsAssociative

public export
[additionMonoid] My.Structures.Monoid ℕ using additionSemigroup where
  empty = 0
  rightIdentityProof a = additionRightIdentity a
  leftIdentityProof a = Refl

public export
[multiplicationSemigroup] My.Structures.Semigroup ℕ where
  ∘ = multiply
  associativityProof = multiplicationIsAssociative
 
public export
[multiplicationMonoid] My.Structures.Monoid ℕ using multiplicationSemigroup where
   empty = 1
   rightIdentityProof = multiplicationRightIdentity
   leftIdentityProof  = multiplicationLeftIdentity