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