import Std.Data.List.Basic

section catamorphism
  def cata {α β : Type} (b : β) (f : α → β → β) (x : List α) :=
    match x with
    | [] => b
    | a :: as => f a (cata b f as)

  notation:max "⦅" l:10 "," r:11 "⦆" => cata l r

  -- theorem cata_fusion (h : β → β) (b : β) (f : α → β → β) : h ∘ ⦅b, f⦆ = ⦅b, h ∘ f⦆ := by
    -- sorry

  section length
    def length := ⦅0, Function.const Nat Nat.succ⦆
    theorem length_empty : length [] = Nat.zero := by
      unfold length
      unfold cata
      rfl

    theorem length_cons : length (a :: as) = Nat.succ (length as) := by
      match as with
      | [] =>
        rw [length_empty]
        unfold length
        simp [cata]
      | b :: bs =>
        unfold length
        simp [cata]

    #eval length [1,2,3,4,5,6,7,8,9]
    end length

  section filter
    def filter {α : Type} (p : α → Bool) := ⦅[],
    λ (a as) => if (p a) then (a :: as) else (as)⦆

    theorem filter_nil {α : Type} (p : α → Bool) : filter p [] = [] := by
      unfold filter
      simp [cata]

    theorem filter_cons_pos {α : Type} (p : α → Bool) {a : α} (l : List α) : p a → filter p (a :: l) = a :: (filter p l) := by
      intro hp
      unfold filter
      simp [cata]
      simp [hp]

    theorem filter_cons_neg {α : Type} (p : α → Bool) {a : α} (l : List α) : ¬p a → filter p (a :: l) = filter p l := by
      intro hp
      unfold filter
      simp [cata]
      simp [hp]

    #eval filter (·>5) [1,2,3,4,5,6,7,8,9]
    end filter
end catamorphism

section anamorphism
  partial def ana {α β : Type} (p : β → Bool) (b : β) (g : ∀ (b : β) (_ : ¬p b), α × β) := by
    cases h : (p b)
    have hp := ne_true_of_eq_false h
    let t := g b hp
    exact t.fst :: ana p t.snd g
    exact []

  notation:max "〖" l:10 "," r:11 "〗" => λ b => ana r b l

  section zip
    def p (l : List α × List β) : Bool := (List.length l.1) = 0 || (List.length l.2 = 0)

    -- this si fucking ugly!!!!!!
    def g (l : List α × List β) (hp : ¬p l) := by
      unfold p at hp
      simp at hp
      rw [not_or] at hp

      have left := And.left hp
      rw [List.length_eq_zero] at left
      have right := And.right hp
      rw [List.length_eq_zero] at right

      have lh := List.head l.fst left
      have rh := List.head l.snd right

      exact ((lh, rh), (List.tail l.fst, List.tail l.snd))

    def curry : (α × β → φ) → α → β → φ := fun f a b => f (a, b)
    def zip : List α → List β → List (α × β) := curry 〖g, p〗

    #eval zip [1] [2]
  end zip
end anamorphism

section hylomorphism
  -- partial def hylo (c : φ) (f : β → φ → φ) (p : α → Bool) (a : α) := match p a with
    -- | true => c
    -- | false => let t := g a
              -- f t.fst (hylo c f g p t.snd)

  def hylo (c : φ) (f : β → φ → φ) (p : α → Bool) (g : (b : α) → ¬p b = true → β × α) := ⦅c,f⦆ ∘ 〖g, p〗

  notation:max "⟦" "(" c:10 "," f:11 ")" "," "(" g:12 "," p:13 ")" "⟧" => hylo c f p g

  section factorial
      def g_1 (b : Nat) (_ : ¬decide (b = 0)) : Nat × Nat := (b, b-1)

    def fac : Nat → Nat := ⟦(1, Nat.mul), (g_1, (·=0))⟧

    #eval fac 8

  end factorial

end hylomorphism
