namespace Ex1

inductive T: Type :=
  | mk: String → Option T → T

def runT: T → Nat
  | .mk _ none => 0
  | .mk _ (some t) => runT t

class Run (α: Type) where
  run: α → Nat
instance: Run T := ⟨runT⟩

def x := T.mk "PrettyLong" (some <| .mk "PrettyLong" none)

theorem equivalent: Run.run x = Run.run x := by
  -- simp (config := { dsimp := false, decide := false, etaStruct := .none }) [Run.run]
  apply Eq.refl (runT x)

example : Run.run x = Run.run x := by
  simp [Run.run]

end Ex1

namespace Ex2

inductive Wrapper where
  | wrap: Wrapper

def Wrapper.extend: Wrapper → (Unit × Unit)
  | .wrap => ((), ())

mutual
inductive Op where
  | mk: String → Block → Op

inductive Assign where
  | mk : String → Op → Assign

inductive Block where
  | mk: Assign → Block
  | empty: Block
end

mutual
def runOp: Op → Wrapper
  | .mk _ r => let r' := runBlock r; .wrap

def runAssign: Assign → Wrapper
  | .mk _ op => runOp op

def runBlock: Block → Wrapper
  | .mk a => runAssign a
  | .empty => .wrap
end

private def b: Assign := .mk "r" (.mk "APrettyLongString" .empty)

theorem bug: (runAssign b).extend.snd = (runAssign b).extend.snd := by
  --unfold b -- extremely slow
  sorry

end Ex2

namespace Ex3

inductive ProgramType := | Op | Assign | Block

section
set_option hygiene false
notation "Op"     => Program ProgramType.Op
notation "Assign" => Program ProgramType.Assign
notation "Block"  => Program ProgramType.Block
end

inductive Program: (type: ProgramType) → Type :=
  | mkOp: String → Block → Op
  | mkAssign: String → Op → Assign
  | mkBlock: Assign → Block
  | emptyBlock: Block

def runBase: Program type → Nat
  | .mkOp _ v => let _ := runBase v; 0
  | .mkAssign _ t => runBase t
  | .mkBlock u => runBase u
  | .emptyBlock => 0

class Run (α: Type) where
  run: α → Nat
instance: Run Assign := ⟨runBase⟩

def x: Assign := .mkAssign "PrettyLong" <| .mkOp "PrettyLong" .emptyBlock
-- Now runs fine
theorem equivalent: Run.run x = Run.run x := by simp [Run.run]

end Ex3
