/- Benchmark for new code generator -/
inductive Expr
| Val : Int → Expr
| Var : String → Expr
| Add : Expr → Expr → Expr
| Mul : Expr → Expr → Expr
| Pow : Expr → Expr → Expr
| Ln  : Expr → Expr

open Expr

def Expr.toString : Expr → String
| (Val n) := toString n
| (Var x) := x
| (Add f g) := "(" ++ Expr.toString f ++ " + " ++ Expr.toString g ++ ")"
| (Mul f g) := "(" ++ Expr.toString f ++ " * " ++ Expr.toString g ++ ")"
| (Pow f g) := "(" ++ Expr.toString f ++ " ^ " ++ Expr.toString g ++ ")"
| (Ln f)    := "ln(" ++ Expr.toString f ++ ")"

instance : HasToString Expr :=
⟨Expr.toString⟩

partial def pown : Int → Int → Int
| a 0 := 1
| a 1 := a
| a n :=
  let b := pown a (n / 2) in
  b * b * (if n % 2 = 0 then 1 else a)

partial def addAux : Expr → Expr → Expr
| (Val n)   (Val m)         := Val (n + m)
| (Val 0)   f               := f
| f         (Val 0)         := f
| f         (Val n)         := addAux (Val n) f
| (Val n)   (Add (Val m) f) := addAux (Val (n+m)) f
| f         (Add (Val n) g) := addAux (Val n) (addAux f g)
| (Add f g) h               := addAux f (addAux g h)
| f         g               := Add f g

def add (a b : Expr) : Expr :=
-- dbgTrace (">> add (" ++ toString a ++ ", " ++ toString b ++ ")") $ λ _,
addAux a b

-- set_option trace.compiler.borrowed_inference true

partial def mulAux : Expr → Expr → Expr
| (Val n)   (Val m)         := Val (n*m)
| (Val 0)   _               := Val 0
| _         (Val 0)         := Val 0
| (Val 1)   f               := f
| f         (Val 1)         := f
| f         (Val n)         := mulAux (Val n) f
| (Val n)   (Mul (Val m) f) := mulAux (Val (n*m)) f
| f         (Mul (Val n) g) := mulAux (Val n) (mulAux f g)
| (Mul f g) h               := mulAux f (mulAux g h)
| f         g               := Mul f g

def mul (a b : Expr) : Expr :=
-- dbgTrace (">> mul (" ++ toString a ++ ", " ++ toString b ++ ")") $ λ _,
mulAux a b

def pow : Expr → Expr → Expr
| (Val m) (Val n) := Val (pown m n)
| _       (Val 0) := Val 1
| f       (Val 1) := f
| (Val 0) _       := Val 0
| f       g       := Pow f g

def ln : Expr → Expr
| (Val 1) := Val 0
| f       := Ln f

def d (x : String) : Expr → Expr
| (Val _)   := Val 0
| (Var y)   := if x = y then Val 1 else Val 0
| (Add f g) := add (d f) (d g)
| (Mul f g) :=
  -- dbgTrace (">> d (" ++ toString f ++ ", " ++ toString g ++ ")") $ λ _,
  add (mul f (d g)) (mul g (d f))
| (Pow f g) := mul (pow f g) (add (mul (mul g (d f)) (pow f (Val (-1)))) (mul (ln f) (d g)))
| (Ln f)    := mul (d f) (pow f (Val (-1)))

def count : Expr → Nat
| (Val _) := 1
| (Var _) := 1
| (Add f g) := count f + count g
| (Mul f g) := count f + count g
| (Pow f g) := count f + count g
| (Ln f)    := count f


def nestAux (s : Nat) (f : Nat → Expr → IO Expr) : Nat → Expr → IO Expr
| 0       x := pure x
| m@(n+1) x := f (s - m) x >>= nestAux n

def nest (f : Nat → Expr → IO Expr) (n : Nat) (e : Expr) : IO Expr :=
nestAux n f n e

def deriv (i : Nat) (f : Expr) : IO Expr :=
do
  let d := d "x" f,
  IO.println (toString (i+1) ++ " count: " ++ (toString $ count d)),
  IO.println (toString d),
  pure d

def main (xs : List String) : IO UInt32 :=
do let x := Var "x",
   let f := add x (mul x (mul x (add x x))),
   IO.println f,
   nest deriv 3 f,
   pure 0

-- setOption profiler True
-- #eval main []
