-- ExprEval/Basic.lean

import ExprEval.Steps

import ExprEval.Lemmas

variable (V: Type)

-- Repository definitions used:
content: inductive ArStep: (val: V -> Int) -> (ArExpr V) -> (ArExpr V) -> Prop where
    | getVarValue (var: V) :
        ArStep
            val
            (ArExpr.Var var)
            (ArExpr.Const (val var))
    | addConstConst(n₁ n₂: Int) :
        ArStep
            val
            (ArExpr.Add (ArExpr.Const n₁) (ArExpr.Const n₂))
            (ArExpr.Const (n₁ + n₂))
    | subConstConst(n₁ n₂: Int) :
        ArStep
            val
            (ArExpr.Sub (ArExpr.Const n₁) (ArExpr.Const n₂))
            (ArExpr.Const (n₁ - n₂))
    | mulConstConst(n₁ n₂: Int) :
        ArStep
            val
            (ArExpr.Mul (ArExpr.Const n₁) (ArExpr.Const n₂))
            (ArExpr.Const (n₁ * n₂))
    | addLeft (e₁ e₁' e₂) : ArStep val e₁ e₁' -> ArStep val (ArExpr.Add e₁ e₂) (ArExpr.Add e₁' e₂)

    | subLeft (e₁ e₁' e₂) : ArStep val e₁ e₁' -> ArStep val (ArExpr.Sub e₁ e₂) (ArExpr.Sub e₁' e₂)

    | mulLeft (e₁ e₁' e₂) : ArStep val e₁ e₁' -> ArStep val (ArExpr.Mul e₁ e₂) (ArExpr.Mul e₁' e₂)

    | addRight (n: Int) (e₂ e₂': ArExpr V) : ArStep val e₂ e₂' -> ArStep val (ArExpr.Add (ArExpr.Const n) e₂)  (ArExpr.Add (ArExpr.Const n) e₂')

    | subRight (n: Int) (e₂ e₂': ArExpr V) : ArStep val e₂ e₂' -> ArStep val (ArExpr.Sub (ArExpr.Const n) e₂)  (ArExpr.Sub (ArExpr.Const n) e₂')
    | mulRight (n: Int) (e₂ e₂': ArExpr V) : ArStep val e₂ e₂' -> ArStep val (ArExpr.Mul (ArExpr.Const n) e₂)  (ArExpr.Mul (ArExpr.Const n) e₂')

    | ifStep (e e': BoolExpr V) (a b : ArExpr V): BoolStep val e e' ->
        ArStep val (ArExpr.If e a b) (ArExpr.If e' a b)
    | ifCondTrue (a b: ArExpr V) : ArStep val (ArExpr.If (BoolExpr.Const true) a b) a
    | ifCondFalse (a b: ArExpr V) : ArStep val (ArExpr.If (BoolExpr.Const false) a b) b

content: def ArStepStar.trans {V: Type} {val: V -> Int} e₁ e₂ e₃ := StepStar.trans e₁ e₂ e₃ (Step := ArStep V) (val := val)

content: inductive BoolStep: (val: V -> Int) -> (BoolExpr V) -> (BoolExpr V) -> Prop where
    | notIsBoolNot (b: Bool): BoolStep val
        (BoolExpr.Not (BoolExpr.Const b))
        (BoolExpr.Const !b)
    | andLeftTrue (e: BoolExpr V): BoolStep val
        (BoolExpr.And (BoolExpr.Const true) e)
        e
    | orLeftFalse (e: BoolExpr V): BoolStep val
        (BoolExpr.Or (BoolExpr.Const false) e)
        e
    | andLeftShortCircuit e : BoolStep val
        (BoolExpr.And (BoolExpr.Const false) e)
        (BoolExpr.Const false)
    | orLeftShortCircuit e : BoolStep val
        (BoolExpr.Or (BoolExpr.Const true) e)
        (BoolExpr.Const true)
    | lessConstConstTrue n₁ n₂ : n₁ < n₂ -> BoolStep val
        (BoolExpr.Less (ArExpr.Const n₁) (ArExpr.Const n₂))
        (BoolExpr.Const true)
    | lessConstConstFalse n₁ n₂ : n₁ >= n₂ -> BoolStep val
        (BoolExpr.Less (ArExpr.Const n₁) (ArExpr.Const n₂))
        (BoolExpr.Const false)
    | eqConstConstTrue n₁ n₂ : n₁ = n₂ -> BoolStep val
        (BoolExpr.Eq (ArExpr.Const n₁) (ArExpr.Const n₂))
        (BoolExpr.Const true)
    | eqConstConstFalse n₁ n₂ : n₁ != n₂ -> BoolStep val
        (BoolExpr.Eq (ArExpr.Const n₁) (ArExpr.Const n₂))
        (BoolExpr.Const false)
    | lessArStepLeft (e₁ e₁' e₂: ArExpr V):
        ArStep val e₁ e₁' ->
            BoolStep val
                (BoolExpr.Less e₁ e₂)
                (BoolExpr.Less e₁' e₂)
    | eqArStepLeft (e₁ e₁' e₂: ArExpr V):
        ArStep val e₁ e₁' ->
            BoolStep val
                (BoolExpr.Eq e₁ e₂)
                (BoolExpr.Eq e₁' e₂)
     | lessArStepRight (e₁ e₂ e₂': ArExpr V):
        ArStep val e₂ e₂' ->
            BoolStep val
                (BoolExpr.Less e₁ e₂)
                (BoolExpr.Less e₁ e₂')
    | eqArStepRight (e₁ e₂ e₂': ArExpr V):
        ArStep val e₂ e₂' ->
            BoolStep val
                (BoolExpr.Eq e₁ e₂)
                (BoolExpr.Eq e₁ e₂')
    | orStepLeft (e₁ e₁' e₂: BoolExpr V):
        BoolStep val e₁ e₁' ->
            BoolStep val
                (BoolExpr.Or e₁ e₂)
                (BoolExpr.Or e₁' e₂)
    | andStepLeft (e₁ e₁' e₂: BoolExpr V):
        BoolStep val e₁ e₁' ->
            BoolStep val
                (BoolExpr.And e₁ e₂)
                (BoolExpr.And e₁' e₂)
    | notStep (e e' : BoolExpr V):
        BoolStep val e e' -> BoolStep val
            (BoolExpr.Not e)
            (BoolExpr.Not e')

    inductive BoolExpr : Type
        | Const: Bool -> BoolExpr
        | Less: ArExpr -> ArExpr -> BoolExpr
        | Eq: ArExpr -> ArExpr -> BoolExpr
        | Not : BoolExpr -> BoolExpr
        | And : BoolExpr -> BoolExpr -> BoolExpr
        | Or : BoolExpr -> BoolExpr -> BoolExpr

    inductive ArExpr: Type
        | Const: Int -> ArExpr
        | Add: ArExpr -> ArExpr -> ArExpr
        | Sub: ArExpr -> ArExpr -> ArExpr
        | Mul: ArExpr -> ArExpr -> ArExpr
        | Var: V -> ArExpr
        | If : BoolExpr -> ArExpr -> ArExpr -> ArExpr

content: inductive StepStar {ExprType: Type} {Step: StepKind V ExprType} : (val: V -> Int) -> ExprType -> ExprType -> Prop where
        | refl val e : StepStar val e e
        | trans e₁ e₂ e₃ : Step val e₁ e₂ -> StepStar val e₂ e₃ -> StepStar val e₁ e₃

content: def ArStepStar (V: Type) := StepStar (Step := ArStep V) V

    def eval (val: V -> Int) (e: ArExpr V) : Int :=
        match e with
            | ArExpr.Const x => x
            | ArExpr.Add lhs rhs => (eval val lhs) + (eval val rhs)
            | ArExpr.Sub lhs rhs => (eval val lhs) - (eval val rhs)
            | ArExpr.Mul lhs rhs => (eval val lhs) * (eval val rhs)
            | ArExpr.Var v => val v
            | ArExpr.If cond then_e else_e => if eval_bool val cond then eval val then_e else eval val else_e

    def eval_bool (val: V -> Int) (e: BoolExpr V): Bool :=
        match e with
        | BoolExpr.Const b => b
        | BoolExpr.Less l r =>  (eval val l) < (eval val r)
        | BoolExpr.Eq l r => (eval val l) == (eval val r)
        | BoolExpr.Not e => not (eval_bool val e)
        | BoolExpr.And l r => if eval_bool val l then eval_bool val r else false
        | BoolExpr.Or l r => if eval_bool val l then true else eval_bool val r

content: def ArStepStar.refl {V: Type} (val: V -> Int) := StepStar.refl (Step := ArStep V) val

content: def BoolStepStar (V: Type) := StepStar (Step := BoolStep V) V

content: def BoolStepStar.refl {V: Type} (val: V -> Int) := StepStar.refl (Step := BoolStep V) val

content: def StepKind (V: Type) (ExprType: Type):= (V -> Int) -> ExprType -> ExprType -> Prop

content: def BoolStepStar.trans {V: Type} {val: V -> Int} e₁ e₂ e₃ := StepStar.trans e₁ e₂ e₃ (Step := BoolStep V) (val := val)

-- Repository lemmas used:
content: theorem arstepstar_add_right (n: Int) (e₂ e₂': ArExpr V) :
            ArStepStar V val e₂ e₂' ->
                    ArStepStar V val
                            (ArExpr.Add (ArExpr.Const n) e₂)
                            (ArExpr.Add (ArExpr.Const n) e₂')

content: theorem arstepstar_sub_left (e₁ e₁' e₂: ArExpr V) :
            ArStepStar V val e₁ e₁' ->
                    ArStepStar V val
                            (ArExpr.Sub e₁ e₂)
                            (ArExpr.Sub e₁' e₂)

content: theorem chasles_step_star {ExprKind: Type} {Step: StepKind V ExprKind} {e₁ e₂ e₃: ExprKind}:
    StepStar (Step := Step) V val e₁ e₂  ->
        StepStar (Step := Step) V val e₂ e₃ ->
            StepStar (Step := Step) V val e₁ e₃

content: theorem arstepstar_add_left (e₁ e₁' e₂: ArExpr V) :
            ArStepStar V val e₁ e₁' -> ArStepStar V val (ArExpr.Add e₁ e₂) (ArExpr.Add e₁' e₂)

content: theorem arstepstar_sub_right (n: Int) (e₂ e₂': ArExpr V) :
            ArStepStar V val e₂ e₂' ->
                    ArStepStar V val
                            (ArExpr.Sub (ArExpr.Const n) e₂)
                            (ArExpr.Sub (ArExpr.Const n) e₂')

content: theorem ArStepStar.ifStep (e e': BoolExpr V) (a b: ArExpr V): BoolStepStar V val e e' ->
        ArStepStar V val (ArExpr.If e a b) (ArExpr.If e' a b)

content: theorem arstepstar_mul_right (n: Int) (e₂ e₂': ArExpr V) :
            ArStepStar V val e₂ e₂' ->
                    ArStepStar V val
                            (ArExpr.Mul (ArExpr.Const n) e₂)
                            (ArExpr.Mul (ArExpr.Const n) e₂')

content: theorem arstepstar_mul_left (e₁ e₁' e₂: ArExpr V) :
            ArStepStar V val e₁ e₁' ->
                    ArStepStar V val
                            (ArExpr.Mul e₁ e₂)
                            (ArExpr.Mul e₁' e₂)

content: theorem BoolStepStar.andStepLeft (e₁ e₁' e₂: BoolExpr V):
        BoolStepStar V val e₁ e₁' -> BoolStepStar V val
            (BoolExpr.And e₁ e₂)
            (BoolExpr.And e₁' e₂)

content: theorem BoolStepStar.lessArStepStarLeft (e₁ e₁' e₂: ArExpr V):
        ArStepStar V val e₁ e₁' ->
            BoolStepStar V val
                (BoolExpr.Less e₁ e₂)
                (BoolExpr.Less e₁' e₂)

content: theorem BoolStepStar.notStep {V: Type} {val: V -> Int}
        (e e': BoolExpr V):
            BoolStepStar V val e e' -> BoolStepStar V val (BoolExpr.Not e) (BoolExpr.Not e')

content: theorem BoolStepStar.lessArStepStarRight (e₁ e₂ e₂': ArExpr V):
        ArStepStar V val e₂ e₂' ->
            BoolStepStar V val
                (BoolExpr.Less e₁ e₂)
                (BoolExpr.Less e₁ e₂')

content: theorem step_to_stepstar {ExprKind: Type} {Step: StepKind V ExprKind} {e e': ExprKind}:
    Step val e e' -> StepStar (Step := Step) V val e e'

content: theorem BoolStepStar.eqArStepStarRight (e₁ e₂ e₂': ArExpr V):
        ArStepStar V val e₂ e₂' ->
            BoolStepStar V val
                (BoolExpr.Eq e₁ e₂)
                (BoolExpr.Eq e₁ e₂')

content: theorem BoolStepStar.eqArStepStarLeft (e₁ e₁' e₂: ArExpr V):
        ArStepStar V val e₁ e₁' ->
            BoolStepStar V val
                (BoolExpr.Eq e₁ e₂)
                (BoolExpr.Eq e₁' e₂)

content: theorem BoolStepStar.orStepLeft (e₁ e₁' e₂: BoolExpr V):
        BoolStepStar V val e₁ e₁' -> BoolStepStar V val
            (BoolExpr.Or e₁ e₂)
            (BoolExpr.Or e₁' e₂)

-- Local definitions used:

-- Local lemmas used:

-- Target theorem and proof:
theorem eval_completeness (e: ArExpr V) : ArStepStar V val e (ArExpr.Const (eval V val e))
:= by
        cases e with
        | Const x =>
            simp [eval]
            exact ArStepStar.refl _ _
        | Add e₁ e₂ =>
            have ih1 := eval_completeness e₁ (val := val)
            have ih2 := eval_completeness e₂ (val := val)
            simp [eval]
            have a := arstepstar_add_left _ _ e₂ ih1
            have b := arstepstar_add_right (eval V val e₁) e₂ _ ih2
            have c := ArStep.addConstConst (val := val) (eval V val e₁) (eval V val e₂)
            have d := ArStepStar.trans _ _ _ c (ArStepStar.refl _ (ArExpr.Const (eval V val e₁ + eval V val e₂)))
            have e := chasles_step_star _ a (chasles_step_star _ b d)
            exact e
        | Sub e₁ e₂ =>
            have ih1 := eval_completeness e₁ (val := val)
            have ih2 := eval_completeness e₂ (val := val)
            simp [eval]
            have a := arstepstar_sub_left _ _ e₂ ih1
            have b := arstepstar_sub_right (eval V val e₁) e₂ _ ih2
            have c := ArStep.subConstConst (val := val) (eval V val e₁) (eval V val e₂)
            have d := ArStepStar.trans _ _ _ c (ArStepStar.refl _ (ArExpr.Const  (eval V val e₁ - eval V val e₂)))
            have e := chasles_step_star _ a (chasles_step_star _ b d)
            exact e
        | Mul e₁ e₂ =>
            have ih1 := eval_completeness e₁ (val := val)
            have ih2 := eval_completeness e₂ (val := val)
            simp [eval]
            have a := arstepstar_mul_left _ _ e₂ ih1
            have b := arstepstar_mul_right (eval V val e₁) e₂ _ ih2
            have c := ArStep.mulConstConst (val := val) (eval V val e₁) (eval V val e₂)
            have d := ArStepStar.trans _ _ _ c (ArStepStar.refl _ (ArExpr.Const (eval  V val e₁ * eval V val e₂)))
            have e := chasles_step_star _ a (chasles_step_star _ b d)
            exact e
        | Var var =>
            simp [eval]
            have a := ArStep.getVarValue var (val := val)
            have b := ArStepStar.refl val (ArExpr.Const (val var))
            exact ArStepStar.trans (val := val) _ _ _ a b
        | If cond a b =>
            simp [eval]
            cases h: (eval_bool V val cond)
            .   simp
                have h1 := ArStep.ifCondFalse a b (val := val)
                have h2 := eval_completeness b (val := val)
                have h3 := StepStar.trans _ _ _ h1 h2
                have h4 := eval_bool_completeness cond (val := val)
                rw [h] at h4
                have h5 := ArStepStar.ifStep _ _ a b h4
                exact chasles_step_star V h5 h3
            .   simp
                have h1 := ArStep.ifCondTrue a b (val := val)
                have h2 := eval_completeness a (val := val)
                have h3 := StepStar.trans _ _ _ h1 h2
                have h4 := eval_bool_completeness cond (val := val)
                rw [h] at h4
                have h5 := ArStepStar.ifStep _ _ a b h4
                exact chasles_step_star V h5 h3