Theory Common_Proof

section "Common Proofs"

theory Common_Proof
imports Common_Aux Idle_Proof Current_Proof
begin

lemma take_rev_drop: "take_rev n xs @ acc = drop (length xs - n) (rev xs) @ acc"
  unfolding take_rev_def using rev_take by blast

lemma take_rev_step: "xs  []  take_rev n (tl xs) @ (hd xs # acc) = take_rev (Suc n) xs @ acc"
  by (simp add: take_Suc)

lemma take_rev_empty [simp]: "take_rev n [] = []"
  by simp

lemma take_rev_tl_hd: 
    "0 < n  xs  []  take_rev  n xs @ ys = take_rev (n - (Suc 0)) (tl xs) @ (hd xs #ys)"
  by (simp add: take_rev_step del: take_rev_def)

lemma take_rev_nth: 
    "n < length xs  x = xs ! n  x # take_rev n xs @ ys = take_rev (Suc n) xs @ ys"
  by (simp add: take_Suc_conv_app_nth)

lemma step_list [simp]: "invar common  list (step common) = list common"
proof(induction common rule: step_common_state.induct)
  case (1 idle)
  then show ?case by auto
next
  case (2 current aux new moved)

  then show ?case 
  proof(cases current)
    case (Current extra added old remained)
    
    with 2 have aux_not_empty: "aux  []"
        by auto

    from 2 Current show ?thesis 
    proof(cases "remained  Suc moved")
      case True
     
      with 2 Current have "remained - length new = 1"
        by auto

      with True Current 2 aux_not_empty show ?thesis 
        by auto
    next
      case False
      with Current show ?thesis 
        by(auto simp: aux_not_empty take_rev_step Suc_diff_Suc simp del: take_rev_def)
    qed
  qed
qed

lemma step_list_current [simp]: "invar common  list_current (step common) = list_current common"
  by(cases common)(auto split: current.splits)

lemma push_list [simp]: "list (push x common) = x # list common"
proof(induction x common rule: push.induct)
  case (1 x stack stackSize)
  then show ?case 
    by auto
next
  case (2 x current aux new moved)
  then show ?case 
    by(induction x current rule: Current.push.induct) auto
qed

lemma invar_step: "invar (common :: 'a common_state)  invar (step common)" 
proof(induction "common" rule: invar_common_state.induct)
  case (1 idle)
  then show ?case
    by auto
next
  case (2 current aux new moved)
  then show ?case
  proof(cases current)
    case (Current extra added old remained)
    then show ?thesis
    proof(cases "aux = []")
      case True
      with 2 Current show ?thesis by auto 
    next
      case False
      note AUX_NOT_EMPTY = False

      then show ?thesis
      proof(cases "remained  Suc (length new)")
        case True
        with 2 Current False 
          have "take (Suc (length new)) (Stack_Aux.list old) = take (size old) (hd aux # new)"
            by(auto simp: le_Suc_eq take_Cons')
         
        with 2 Current True show ?thesis 
          by auto
      next
        case False
        with 2 Current AUX_NOT_EMPTY show ?thesis 
          by(auto simp: take_rev_step Suc_diff_Suc simp del: take_rev_def)
      qed
    qed
  qed
qed

lemma invar_push: "invar common  invar (push x common)"
proof(induction x common rule: push.induct)
  case (1 x current stack stackSize)
  then show ?case
  proof(induction x current rule: Current.push.induct)
    case (1 x extra added old remained)
    then show ?case
    proof(induction x stack rule: Stack.push.induct)
      case (1 x left right)
      then show ?case by auto
    qed
  qed
next
  case (2 x current aux new moved)
  then show ?case
  proof(induction x current rule: Current.push.induct)
    case (1 x extra added old remained)
    then show ?case by auto
  qed
qed

lemma invar_pop: "
  0 < size common; 
  invar common;
  pop common = (x, common')
  invar common'"
proof(induction common arbitrary: x rule: pop.induct)
  case (1 current idle)
  then obtain idle' where idle: "Idle.pop idle = (x, idle')"
    by(auto split: prod.splits)

  obtain current' where current: "drop_first current = current'"
    by auto

  from 1 current idle show ?case 
    using Idle_Proof.size_pop[of idle x idle', symmetric] 
        size_new_pop[of current] 
        size_pop_sub[of current _ current']
    by(auto simp: Idle_Proof.invar_pop invar_pop eq_snd_iff take_tl size_not_empty)
next
  case (2 current aux new moved)
  then show ?case 
  proof(induction current rule: Current.pop.induct)
    case (1 added old remained)
    then show ?case
    proof(cases "remained - Suc 0  length new")
      case True

      with 1 have [simp]: 
          "0 < size old" 
          "Stack_Aux.list old  []" 
          "aux  []"
          "length new = remained - Suc 0"
        by(auto simp: Stack_Proof.size_not_empty Stack_Proof.list_not_empty)

      then have [simp]: "Suc 0  size old" 
        by linarith

      from 1 have "0 < remained"
        by auto

      then have "take remained (Stack_Aux.list old)
          = hd (Stack_Aux.list old) # take (remained - Suc 0) (tl (Stack_Aux.list old))"
        by (metis Suc_pred Stack_Aux.list old  [] list.collapse take_Suc_Cons)

      with 1 True show ?thesis 
        using Stack_Proof.pop_list[of old] 
        by(auto simp: Stack_Proof.size_not_empty)
    next
      case False
      with 1 have "remained - Suc 0  length aux + length new" by auto

      with 1 False show ?thesis 
        using Stack_Proof.pop_list[of old]
        apply(auto simp: Suc_diff_Suc take_tl Stack_Proof.size_not_empty tl_append_if)
        by (simp add: Suc_diff_le rev_take tl_drop_2 tl_take)
    qed
   next
    case (2 x xs added old remained)
    then show ?case by auto
  qed
qed

lemma push_list_current [simp]: "list_current (push x left) = x # list_current left"
  by(induction x left rule: push.induct) auto

lemma pop_list [simp]: "invar common  0 < size common  pop common = (x, common') 
   x # list common' = list common"
proof(induction common arbitrary: x rule: pop.induct)
  case 1
  then show ?case
    by(auto simp: size_not_empty split: prod.splits)
next
  case (2 current aux new moved)
  then show ?case
  proof(induction current rule: Current.pop.induct)
    case (1 added old remained)
    then show ?case
    proof(cases "remained - Suc 0  length new")
      case True

      from 1 True have [simp]: 
          "aux  []" "0 < remained" 
          "Stack_Aux.list old  []" "remained - length new = 1"
        by(auto simp: Stack_Proof.size_not_empty Stack_Proof.list_not_empty)

      then have "take remained (Stack_Aux.list old) = hd aux # take (size old - Suc 0) new
              Stack.first old = hd aux"
        by (metis first_hd hd_take list.sel(1))
     
      with 1 True take_hd[of aux] show ?thesis 
        by(auto simp: Suc_leI)
    next
      case False
      then show ?thesis 
      proof(cases "remained - length new = length aux")
        case True

        then have length_minus_1: "remained - Suc (length new) = length aux - 1"
          by simp

        from 1 have not_empty: "0 < remained" "0 < size old"  "aux  []" "¬ is_empty old"
          by(auto simp: Stack_Proof.size_not_empty)

        from 1 True not_empty have "take 1 (Stack_Aux.list old) = take 1 (rev aux)"
          using take_1[of 
                remained 
                "size old" 
                "Stack_Aux.list old"  
                "(rev aux) @ take (size old + length new - remained) new"
                ] 
          by(simp)
         
        then have "[last aux] = [Stack.first old]"
          using take_last first_take not_empty
          by fastforce

        then have "last aux = Stack.first old"
          by auto

        with 1 True False show ?thesis 
          using not_empty last_drop_rev[of aux]
          by(auto simp: take_rev_drop length_minus_1 simp del: take_rev_def)
       next
        case False

        with 1 have a: "take (remained - length new) aux  []"
          by auto

        from 1 False have b: "¬ is_empty old"
          by(auto simp: Stack_Proof.size_not_empty)

        from 1 have c: "remained - Suc (length new) < length aux"
          by auto

        from 1 have not_empty: 
            "0 < remained" 
            "0 < size old" 
            "0 < remained - length new" 
            "0 < length aux" 
          by auto

        with False have 
           "take remained (Stack_Aux.list old) =
            take (size old) (take_rev (remained - length new) aux @ new)
             take (Suc 0) (Stack_Aux.list old) =
                take (Suc 0) (rev (take (remained - length new) aux))"
          using take_1[of
                remained 
                "size old" 
                "Stack_Aux.list old" 
                " (take_rev (remained - length new) aux @ new)"
              ]
          by(auto simp: not_empty Suc_le_eq)

        with 1 False have 
            "take 1 (Stack_Aux.list old) = take 1 (rev (take (remained - length new) aux))"
          by auto
          
        then have d: "[Stack.first old] = [last (take (remained - length new) aux)]"
          using take_last first_take a b
          by metis

        have "last (take (remained - length new) aux) # rev (take (remained - Suc (length new)) aux) 
            = rev (take (remained - length new) aux)"
          using Suc_diff_Suc c not_empty
          by (metis a drop_drop last_drop_rev plus_1_eq_Suc rev_take zero_less_diff)
          
        with 1(1) 1(3) False not_empty d show ?thesis 
          by(cases "remained - length new = 1") (auto)
      qed
    qed
  next
    case 2
    then show ?case by auto
  qed
qed

lemma pop_list_current: "invar common  0 < size common  pop common = (x, common')
    x # list_current common' = list_current common"
proof(induction common arbitrary: x rule: pop.induct)
  case (1 current idle)
  then show ?case 
  proof(induction idle rule: Idle.pop.induct)
    case (1 stack stackSize)
    then show ?case
    proof(induction current rule: Current.pop.induct)
      case (1 added old remained)
      then have "Stack.first old = Stack.first stack"
        using take_first[of old stack]
        by auto

      with 1 show ?case 
        by(auto simp: Stack_Proof.size_not_empty Stack_Proof.list_not_empty)
    next
      case (2 x xs added old remained)
      then have "0 < size stack" 
        by auto

      with Stack_Proof.size_not_empty Stack_Proof.list_not_empty
      have not_empty: "¬ is_empty stack" "Stack_Aux.list stack  []"
        by auto

      with 2 have "hd (Stack_Aux.list stack) = x"
        using take_hd'[of "Stack_Aux.list stack" x "xs @ Stack_Aux.list old"]
        by auto
       
      with 2 show ?case 
        using first_list[of stack] not_empty
        by auto
    qed
  qed
next
  case (2 current)
  then show ?case
  proof(induction current rule: Current.pop.induct)
    case (1 added old remained)
    then have "¬ is_empty old"
      by(auto simp: Stack_Proof.size_not_empty)

    with 1 show ?case
      using first_pop
      by(auto simp: Stack_Proof.list_not_empty)
  next
    case 2
    then show ?case by auto
  qed
qed

lemma list_current_size [simp]: 
  "0 < size common; list_current common = []; invar common  False"
proof(induction common rule: invar_common_state.induct)
  case 1
  then show ?case
    using list_size by auto
next
  case (2 current)
  then have "invar current" 
            "Current_Aux.list current = []"  
            "0 < size current" 
    by(auto split: current.splits)
 
  then show ?case using list_size by auto
qed

lemma list_size [simp]: "0 < size common; list common = []; invar common  False"
proof(induction common rule: invar_common_state.induct)
  case 1
  then show ?case
    using list_size Idle_Proof.size_empty
    by auto
next
  case (2 current aux new moved)
  then have "invar current" 
            "Current_Aux.list current = []"  
            "0 < size current" 
    by(auto split: current.splits)
 
  then show ?case using list_size by auto
qed
  
lemma step_size [simp]: "invar (common :: 'a common_state)  size (step common) = size common"
proof(induction common rule: step_common_state.induct)
  case 1
  then show ?case by auto
next
  case 2
  then show ?case 
    by(auto simp: min_def split: current.splits)
qed

lemma step_size_new [simp]: "invar (common :: 'a common_state)
    size_new (step common) = size_new common"
proof(induction common rule: step_common_state.induct)
  case (1 current idle)
  then show ?case by auto
next
  case (2 current aux new moved)
  then show ?case by(auto split: current.splits)
qed

lemma remaining_steps_step [simp]: "invar (common :: 'a common_state); remaining_steps common > 0
    Suc (remaining_steps (step common)) = remaining_steps common"
  by(induction common)(auto split: current.splits)

lemma remaining_steps_step_sub [simp]: "invar (common :: 'a common_state)
  remaining_steps (step common) = remaining_steps common - 1"
  by(induction common)(auto split: current.splits)

lemma remaining_steps_step_0 [simp]: "invar (common :: 'a common_state); remaining_steps common = 0
    remaining_steps (step common) = 0"
  by(induction common)(auto split: current.splits)

lemma remaining_steps_push [simp]: "invar common
    remaining_steps (push x common) = remaining_steps common"
  by(induction x common rule: Common.push.induct)(auto split: current.splits)

lemma remaining_steps_pop: "invar common; pop common = (x, common') 
   remaining_steps common'  remaining_steps common"
proof(induction common rule: pop.induct)
  case (1 current idle)
  then show ?case 
  proof(induction idle rule: Idle.pop.induct)
    case 1
    then show ?case  
      by(induction current rule: Current.pop.induct) auto
  qed
next
  case (2 current aux new moved)
  then show ?case 
    by(induction current rule: Current.pop.induct) auto
qed

lemma size_push [simp]: "invar common  size (push x common) = Suc (size common)"
  by(induction x common rule: push.induct) (auto split: current.splits)
 
lemma size_new_push [simp]: "invar common  size_new (push x common) = Suc (size_new common)"
  by(induction x common rule: Common.push.induct) (auto split: current.splits)

lemma size_pop [simp]: "invar common; 0 < size common; pop common = (x, common')
    Suc (size common') = size common"
proof(induction common rule: Common.pop.induct)
  case (1 current idle)
  then show ?case 
    using size_drop_first_sub[of current] Idle_Proof.size_pop_sub[of idle]
    by(auto simp: size_not_empty split: prod.splits)
next
  case (2 current aux new moved)
  then show ?case 
    by(induction current rule: Current.pop.induct) auto
qed

lemma size_new_pop [simp]: "invar common; 0 < size_new common; pop common = (x, common')
     Suc (size_new common') = size_new common"
proof(induction common rule: Common.pop.induct)
  case (1 current idle)
  then show ?case
    using size_new_pop[of current]
    by(auto split: prod.splits)
next
  case (2 current aux new moved)
  then show ?case 
  proof(induction current rule: Current.pop.induct)
    case (1 added old remained)
    then show ?case by auto
  next
    case (2 x xs added old remained)
    then show ?case by auto
  qed
qed

lemma size_size_new: "invar (common :: 'a common_state); 0 < size common  0 < size_new common"
  by(cases common) auto

end