section \<open>Arithmetic Expressions\<close>
text\<open>
This theory defines a language of arithmetic expressions over variables and literal values. Here,
values are limited to integers and strings. Variables may be either inputs or registers. We also
limit ourselves to a simple arithmetic of addition, subtraction, and multiplication as a proof of
concept.
\<close>

theory AExp
  imports Value_Lexorder VName FinFun.FinFun "HOL-Library.Option_ord"
begin

declare One_nat_def [simp del]
unbundle finfun_syntax

type_synonym registers = "nat \<Rightarrow>f value option"
type_synonym 'a datastate = "'a \<Rightarrow> value option"

text_raw\<open>\snip{aexptype}{1}{2}{%\<close>
datatype 'a aexp = L "value" | V 'a | Plus "'a aexp" "'a aexp" | Minus "'a aexp" "'a aexp" | Times "'a aexp" "'a aexp"
text_raw\<open>}%endsnip\<close>

fun is_lit :: "'a aexp \<Rightarrow> bool" where
  "is_lit (L _) = True" |
  "is_lit _ = False"

lemma aexp_induct_separate_V_cases  [case_names L I R Plus Minus Times]:
  "(\<And>x. P (L x)) \<Longrightarrow>
   (\<And>x. P (V (I x))) \<Longrightarrow>
   (\<And>x. P (V (R x))) \<Longrightarrow>
   (\<And>x1a x2a. P x1a \<Longrightarrow> P x2a \<Longrightarrow> P (Plus x1a x2a)) \<Longrightarrow>
   (\<And>x1a x2a. P x1a \<Longrightarrow> P x2a \<Longrightarrow> P (Minus x1a x2a)) \<Longrightarrow>
   (\<And>x1a x2a. P x1a \<Longrightarrow> P x2a \<Longrightarrow> P (Times x1a x2a)) \<Longrightarrow>
   P a"
  by (metis aexp.induct vname.exhaust)

text_raw\<open>\snip{avaldef}{1}{2}{%\<close>
fun aval :: "'a aexp \<Rightarrow> 'a datastate \<Rightarrow> value option" where
  "aval (L x) s = Some x" |
  "aval (V x) s = s x" |
  "aval (Plus a1 a2) s = value_plus (aval a1 s)(aval a2 s)" |
  "aval (Minus a1 a2) s = value_minus (aval a1 s) (aval a2 s)" |
  "aval (Times a1 a2) s = value_times (aval a1 s) (aval a2 s)"
text_raw\<open>}%endsnip\<close>

lemma aval_plus_symmetry: "aval (Plus x y) s = aval (Plus y x) s"
  by (simp add: value_plus_symmetry)

text \<open>A little syntax magic to write larger states compactly:\<close>
definition null_state ("<>") where
  "null_state \<equiv> (K$ bot)"

no_notation finfun_update ("_'(_ $:= _')" [1000, 0, 0] 1000)
nonterminal fupdbinds and fupdbind

syntax
  "_fupdbind" :: "'a \<Rightarrow> 'a \<Rightarrow> fupdbind"             ("(2_ $:=/ _)")
  ""         :: "fupdbind \<Rightarrow> fupdbinds"             ("_")
  "_fupdbinds":: "fupdbind \<Rightarrow> fupdbinds \<Rightarrow> fupdbinds" ("_,/ _")
  "_fUpdate"  :: "'a \<Rightarrow> fupdbinds \<Rightarrow> 'a"            ("_/'((_)')" [1000, 0] 900)
  "_State" :: "fupdbinds => 'a" ("<_>")

translations
  "_fUpdate f (_fupdbinds b bs)" \<rightleftharpoons> "_fUpdate (_fUpdate f b) bs"
  "f(x$:=y)" \<rightleftharpoons> "CONST finfun_update f x y"
  "_State ms" == "_fUpdate <> ms"
  "_State (_updbinds b bs)" <= "_fUpdate (_State b) bs"

lemma empty_None: "<> = (K$ None)"
  by (simp add: null_state_def bot_option_def)

lemma apply_empty_None [simp]: "<> $ x2 = None"
  by (simp add: null_state_def bot_option_def)

definition input2state :: "value list \<Rightarrow> registers" where
  "input2state n = fold (\<lambda>(k, v) f. f(k $:= Some v)) (enumerate 0 n) (K$ None)"

primrec input2state_prim :: "value list \<Rightarrow> nat \<Rightarrow> registers" where
  "input2state_prim [] _ = (K$ None)" |
  "input2state_prim (v#t) k = (input2state_prim t (k+1))(k $:= Some v)"

lemma input2state_append:
  "input2state (i @ [a]) = (input2state i)(length i $:= Some a)"
  apply (simp add: eq_finfun_All_ext finfun_All_def finfun_All_except_def)
  apply clarify
  by (simp add: input2state_def enumerate_eq_zip)

lemma input2state_out_of_bounds:
  "i \<ge> length ia \<Longrightarrow> input2state ia $ i = None"
proof(induct ia rule: rev_induct)
  case Nil
  then show ?case
    by (simp add: input2state_def)
next
  case (snoc a as)
  then show ?case
    by (simp add: input2state_def enumerate_eq_zip)
qed

lemma input2state_within_bounds:
  "input2state i $ x = Some a \<Longrightarrow> x < length i"
  by (metis input2state_out_of_bounds not_le_imp_less option.distinct(1))

lemma input2state_empty: "input2state [] $ x1 = None"
  by (simp add: input2state_out_of_bounds)

lemma input2state_some:
  "i < length ia \<Longrightarrow>
   ia ! i = x \<Longrightarrow>
   input2state ia $ i = Some x"
  by (simp add: input2state_nth)

lemma input2state_take: "x1 < A \<Longrightarrow>
   A \<le> length i \<Longrightarrow>
   x = vname.I x1 \<Longrightarrow>
   input2state i $ x1 = input2state (take A i) $ x1"
proof(induct i)
  case Nil
  then show ?case
    by simp
next
  case (Cons a i)
  then show ?case
    by (simp add: input2state_nth)
qed

lemma input2state_not_None:
  "(input2state i $ x \<noteq> None) \<Longrightarrow> (x < length i)"
  using input2state_within_bounds by blast

lemma input2state_Some:
  "(\<exists>v. input2state i $ x = Some v) = (x < length i)"
  apply standard
  using input2state_within_bounds apply blast
  by (simp add: input2state_nth)

lemma input2state_cons: "x1 > 0 \<Longrightarrow>
   x1 < length ia \<Longrightarrow>
   input2state (a # ia) $ x1 = input2state ia $ (x1-1)"
  by (simp add: input2state_nth)

lemma input2state_cons_shift:
  "input2state i $ x1 = Some a \<Longrightarrow> input2state (b # i) $ (Suc x1) = Some a"
proof(induct i rule: rev_induct)
  case Nil
  then show ?case
    by (simp add: input2state_def)
next
  case (snoc x xs)
  then show ?case
    using input2state_within_bounds[of xs x1 a]
    using input2state_cons[of "Suc x1" "xs @ [x]" b]
    apply simp
    apply (case_tac "x1 < length xs")
     apply simp
    by (metis finfun_upd_apply input2state_append input2state_nth length_Cons length_append_singleton lessI list.sel(3) nth_tl)
qed

lemma input2state_exists: "\<exists>i. input2state i $ x1 = Some a"
proof(induct x1)
  case 0
  then show ?case
    apply (rule_tac x="[a]" in exI)
    by (simp add: input2state_def)
next
  case (Suc x1)
  then show ?case
    apply clarify
    apply (rule_tac x="a#i" in exI)
    by (simp add: input2state_cons_shift)
qed

primrec repeat :: "nat \<Rightarrow> 'a \<Rightarrow> 'a list" where
  "repeat 0 _ = []" |
  "repeat (Suc m) a = a#(repeat m a)"

lemma length_repeat: "length (repeat n a) = n"
proof(induct n)
  case 0
  then show ?case
    by simp
next
  case (Suc a)
  then show ?case
    by simp
qed

lemma length_append_repeat: "length (i@(repeat a y)) \<ge> length i"
  by simp

lemma length_input2state_repeat:
  "input2state i $ x = Some a \<Longrightarrow> y < length (i @ repeat y a)"
  by (metis append.simps(1) append_eq_append_conv input2state_within_bounds length_append length_repeat list.size(3) neqE not_add_less2 zero_order(3))

lemma input2state_double_exists:
  "\<exists>i. input2state i $ x = Some a \<and> input2state i $ y = Some a"
  apply (insert input2state_exists[of x a])
  apply clarify
  apply (case_tac "x \<ge> y")
  apply (rule_tac x="list_update i y a" in exI)
  apply (metis (no_types, lifting) input2state_within_bounds input2state_nth input2state_out_of_bounds le_trans length_list_update not_le_imp_less nth_list_update_eq nth_list_update_neq)
  apply (rule_tac x="list_update (i@(repeat y a)) y a" in exI)
  apply (simp add: not_le)
  by (metis length_input2state_repeat input2state_nth input2state_out_of_bounds le_trans length_append_repeat length_list_update not_le_imp_less nth_append nth_list_update_eq nth_list_update_neq option.distinct(1))

lemma input2state_double_exists_2:
  "x \<noteq> y \<Longrightarrow> \<exists>i. input2state i $ x = Some a \<and> input2state i $ y = Some a'"
  apply (insert input2state_exists[of x a])
  apply clarify
  apply (case_tac "x \<ge> y")
  apply (rule_tac x="list_update i y a'" in exI)
  apply (metis (no_types, lifting) input2state_within_bounds input2state_nth input2state_out_of_bounds le_trans length_list_update not_le_imp_less nth_list_update_eq nth_list_update_neq)
  apply (rule_tac x="list_update (i@(repeat y a')) y a'" in exI)
  apply (simp add: not_le)
  apply standard
  apply (metis input2state_nth input2state_within_bounds le_trans length_append_repeat length_list_update linorder_not_le nth_append nth_list_update_neq order_refl)
  by (metis input2state_nth length_append length_input2state_repeat length_list_update length_repeat nth_list_update_eq)

definition join_ir :: "value list \<Rightarrow> registers \<Rightarrow> vname datastate" where
  "join_ir i r \<equiv> (\<lambda>x. case x of
    R n \<Rightarrow> r $ n |
    I n \<Rightarrow> (input2state i) $ n
  )"

lemmas datastate = join_ir_def input2state_def

lemma join_ir_empty [simp]: "join_ir [] <> = (\<lambda>x. None)"
  apply (rule ext)
  apply (simp add: join_ir_def)
  apply (case_tac x)
   apply (simp add: input2state_def)
  by (simp add: empty_None)

lemma join_ir_R [simp]: "(join_ir i r) (R n) = r $ n"
  by (simp add: join_ir_def)

lemma join_ir_double_exists:
  "\<exists>i r. join_ir i r v = Some a \<and> join_ir i r v' = Some a"
proof(cases v)
  case (I x1)
  then show ?thesis
    apply (simp add: join_ir_def)
    apply (cases v')
     apply (simp add: input2state_double_exists input2state_exists)
    using input2state_exists by auto
next
  case (R x2)
  then show ?thesis
    apply (simp add: join_ir_def)
    apply (cases v')
    using input2state_exists apply force
    using input2state_double_exists by fastforce
qed

lemma join_ir_double_exists_2:
  "v \<noteq> v' \<Longrightarrow> \<exists>i r. join_ir i r v = Some a \<and> join_ir i r v' = Some a'"
proof(cases v)
  case (I x1)
  assume "v \<noteq> v'"
  then show ?thesis
    apply (simp add: join_ir_def)
    apply (cases v')
     apply (simp add: I input2state_double_exists_2)
    using I input2state_exists by auto
next
  case (R x2)
  assume "v \<noteq> v'"
  then show ?thesis
    apply (simp add: join_ir_def)
    apply (cases v')
     apply simp
    using R input2state_exists apply auto[1]
    apply (simp add: R)
    apply (rule_tac x="<x2 $:= Some a,x2a $:= Some a'>" in exI)
    by simp
qed

lemma exists_join_ir_ext: "\<exists>i r. join_ir i r v = s v"
  apply (simp add: join_ir_def)
  apply (case_tac "s v")
   apply (cases v)
    apply (rule_tac x="[]" in exI)
    apply (simp add: input2state_out_of_bounds)
   apply simp
   apply (rule_tac x="<>" in exI)
   apply simp
  apply simp
  apply (cases v)
   apply simp
   defer
   apply simp
   apply (rule_tac x="<x2 $:= Some a>" in exI)
   apply simp
  by (simp add: input2state_exists)

lemma join_ir_nth [simp]:
  "i < length is \<Longrightarrow> join_ir is r (I i) = Some (is ! i)"
  by (simp add: join_ir_def input2state_nth)

fun aexp_constrains :: "'a aexp \<Rightarrow> 'a aexp \<Rightarrow> bool" where
  "aexp_constrains (L l) a = (L l = a)" |
  "aexp_constrains (V v) v' = (V v = v')" |
  "aexp_constrains (Plus a1 a2) v = ((Plus a1 a2) = v \<or> (Plus a1 a2) = v \<or> (aexp_constrains a1 v \<or> aexp_constrains a2 v))" |
  "aexp_constrains (Minus a1 a2) v = ((Minus a1 a2) = v \<or> (aexp_constrains a1 v \<or> aexp_constrains a2 v))" |
  "aexp_constrains (Times a1 a2) v = ((Times a1 a2) = v \<or> (aexp_constrains a1 v \<or> aexp_constrains a2 v))"

fun aexp_same_structure :: "'a aexp \<Rightarrow> 'a aexp \<Rightarrow> bool" where
  "aexp_same_structure (L v) (L v') = True" |
  "aexp_same_structure (V v) (V v') = True" |
  "aexp_same_structure (Plus a1 a2) (Plus a1' a2') = (aexp_same_structure a1 a1' \<and> aexp_same_structure a2 a2')" |
  "aexp_same_structure (Minus a1 a2) (Minus a1' a2') = (aexp_same_structure a1 a1' \<and> aexp_same_structure a2 a2')" |
  "aexp_same_structure _ _ = False"

fun enumerate_aexp_inputs :: "vname aexp \<Rightarrow> nat set" where
  "enumerate_aexp_inputs (L _) = {}" |
  "enumerate_aexp_inputs (V (I n)) = {n}" |
  "enumerate_aexp_inputs (V (R n)) = {}" |
  "enumerate_aexp_inputs (Plus v va) = enumerate_aexp_inputs v \<union> enumerate_aexp_inputs va" |
  "enumerate_aexp_inputs (Minus v va) = enumerate_aexp_inputs v \<union> enumerate_aexp_inputs va" |
  "enumerate_aexp_inputs (Times v va) = enumerate_aexp_inputs v \<union> enumerate_aexp_inputs va"

lemma enumerate_aexp_inputs_list: "\<exists>l. enumerate_aexp_inputs a = set l"
proof(induct a)
  case (L x)
  then show ?case
    by simp
next
  case (V x)
  then show ?case
    apply (cases x)
     apply (metis empty_set enumerate_aexp_inputs.simps(2) list.simps(15))
    by simp
next
  case (Plus a1 a2)
  then show ?case
    by (metis enumerate_aexp_inputs.simps(4) set_append)
next
  case (Minus a1 a2)
  then show ?case
    by (metis enumerate_aexp_inputs.simps(5) set_append)
next
  case (Times a1 a2)
  then show ?case
    by (metis enumerate_aexp_inputs.simps(6) set_append)
qed

fun enumerate_regs :: "vname aexp \<Rightarrow> nat set" where
  "enumerate_regs (L _) = {}" |
  "enumerate_regs (V (R n)) = {n}" |
  "enumerate_regs (V (I _)) = {}" |
  "enumerate_regs (Plus v va) = enumerate_regs v \<union> enumerate_regs va" |
  "enumerate_regs (Minus v va) = enumerate_regs v \<union> enumerate_regs va" |
  "enumerate_regs (Times v va) = enumerate_regs v \<union> enumerate_regs va"

lemma finite_enumerate_regs: "finite (enumerate_regs a)"
  by(induct a rule: aexp_induct_separate_V_cases, auto)

lemma no_variables_aval: "enumerate_aexp_inputs a = {} \<Longrightarrow>
   enumerate_regs a = {} \<Longrightarrow>
   aval a s = aval a s'"
  by (induct a rule: aexp_induct_separate_V_cases, auto)

lemma enumerate_aexp_inputs_not_empty:
  "(enumerate_aexp_inputs a \<noteq> {}) = (\<exists>b c. enumerate_aexp_inputs a = set (b#c))"
  using enumerate_aexp_inputs_list by fastforce

lemma aval_ir_take: "A \<le> length i \<Longrightarrow>
  enumerate_regs a = {} \<Longrightarrow>
  enumerate_aexp_inputs a \<noteq> {} \<Longrightarrow>
  Max (enumerate_aexp_inputs a) < A \<Longrightarrow>
  aval a (join_ir (take A i) r) = aval a (join_ir i ra)"
proof(induct a)
  case (L x)
  then show ?case
    by simp
next
  case (V x)
  then show ?case
    apply (cases x)
     apply (simp add: join_ir_def input2state_nth)
    by simp
next
  case (Plus a1 a2)
  then show ?case
    apply (simp only: enumerate_aexp_inputs_not_empty[of "Plus a1 a2"])
    apply (erule exE)+
    apply (simp only: neq_Nil_conv List.linorder_class.Max.set_eq_fold)
    apply (case_tac "fold max c b \<le> length i")
     apply simp
     apply (metis List.finite_set Max.union Plus.prems(4) enumerate_aexp_inputs.simps(4) enumerate_aexp_inputs_not_empty max_less_iff_conj no_variables_aval sup_bot.left_neutral sup_bot.right_neutral)
    by simp
next
  case (Minus a1 a2)
  then show ?case
    apply (simp only: enumerate_aexp_inputs_not_empty[of "Minus a1 a2"])
    apply (erule exE)+
    apply (simp only: neq_Nil_conv List.linorder_class.Max.set_eq_fold)
    apply (case_tac "fold max c b \<le> length i")
     apply simp
    apply (metis List.finite_set Max.union Minus.prems(4) enumerate_aexp_inputs.simps(5) enumerate_aexp_inputs_not_empty max_less_iff_conj no_variables_aval sup_bot.left_neutral sup_bot.right_neutral)
    by simp
next
  case (Times a1 a2)
  then show ?case
    apply (simp only: enumerate_aexp_inputs_not_empty[of "Times a1 a2"])
    apply (erule exE)+
    apply (simp only: neq_Nil_conv List.linorder_class.Max.set_eq_fold)
    apply (case_tac "fold max c b \<le> length i")
     apply simp
    apply (metis List.finite_set Max.union Times.prems(4) enumerate_aexp_inputs.simps(6) enumerate_aexp_inputs_not_empty max_less_iff_conj no_variables_aval sup_bot.left_neutral sup_bot.right_neutral)
    by simp
qed

definition max_input :: "vname aexp \<Rightarrow> nat option" where
  "max_input g = (let inputs = (enumerate_aexp_inputs g) in if inputs = {} then None else Some (Max inputs))"

definition max_reg :: "vname aexp \<Rightarrow> nat option" where
  "max_reg g = (let regs = (enumerate_regs g) in if regs = {} then None else Some (Max regs))"

lemma max_reg_V_I: "max_reg (V (I n)) = None"
  by (simp add: max_reg_def)

lemma max_reg_V_R: "max_reg (V (R n)) = Some n"
  by (simp add: max_reg_def)

lemmas max_reg_V = max_reg_V_I max_reg_V_R

lemma max_reg_Plus: "max_reg (Plus a1 a2) = max (max_reg a1) (max_reg a2)"
  apply (simp add: max_reg_def Let_def max_absorb2)
  by (metis Max.union bot_option_def finite_enumerate_regs max_bot2 sup_Some sup_max)

lemma max_reg_Minus: "max_reg (Minus a1 a2) = max (max_reg a1) (max_reg a2)"
  apply (simp add: max_reg_def Let_def max_absorb2)
  by (metis Max.union bot_option_def finite_enumerate_regs max_bot2 sup_Some sup_max)

lemma max_reg_Times: "max_reg (Times a1 a2) = max (max_reg a1) (max_reg a2)"
  apply (simp add: max_reg_def Let_def max_absorb2)
  by (metis Max.union bot_option_def finite_enumerate_regs max_bot2 sup_Some sup_max)

lemma no_reg_aval_swap_regs:
  "max_reg a = None \<Longrightarrow> aval a (join_ir i r) = aval a (join_ir i r')"
proof(induct a)
  case (L x)
  then show ?case
    by simp
next
  case (V x)
  then show ?case
    apply (cases x)
     apply (simp add: join_ir_def)
    by (simp add: join_ir_def max_reg_def)
next
  case (Plus a1 a2)
  then show ?case
    by (metis (no_types, lifting) aval.simps(3) max.absorb2 max.cobounded2 max_reg_Plus sup_None_2 sup_max)
next
  case (Minus a1 a2)
  then show ?case
    by (metis (no_types, lifting) aval.simps(4) max.cobounded2 max_def_raw max_reg_Minus sup_None_2 sup_max)
next
  case (Times a1 a2)
  then show ?case
  proof -
    have "bot = max_reg a2"
      by (metis (no_types) Times.prems bot_option_def max.left_commute max_bot2 max_def_raw max_reg_Times)
    then show ?thesis
      by (metis Times.hyps(1) Times.hyps(2) Times.prems aval.simps(5) bot_option_def max_bot2 max_reg_Times)
  qed
qed

lemma aval_reg_some_superset:
"\<forall>a. (r $ a  \<noteq> None) \<longrightarrow> r $ a = r' $ a \<Longrightarrow>
 aval a (join_ir i r) = Some v \<Longrightarrow>
 aval a (join_ir i r') = Some v"
proof(induct a arbitrary: v rule: aexp_induct_separate_V_cases)
  case (L x)
  then show ?case by simp
next
  case (I x)
  then show ?case
    by (simp add: join_ir_def)
next
  case (R x)
  then show ?case
    by simp
next
  case (Plus x1a x2a)
  then show ?case
    apply simp
    by (metis maybe_arith_int_not_None option.simps(3) value_plus_def)
next
  case (Minus x1a x2a)
  then show ?case
    apply simp
    by (metis maybe_arith_int_not_None option.simps(3) value_minus_def)
next
  case (Times x1a x2a)
  then show ?case
    apply simp
    by (metis maybe_arith_int_not_None option.simps(3) value_times_def)
qed

lemma aval_reg_none_superset:
"\<forall>a. (r $ a  \<noteq> None) \<longrightarrow> r $ a = r' $ a \<Longrightarrow>
 aval a (join_ir i r') = None \<Longrightarrow>
 aval a (join_ir i r) = None"
proof(induct a)
  case (L x)
  then show ?case
    by simp
next
  case (V x)
  then show ?case
    apply (cases x)
     apply (simp add: join_ir_def)
    by auto
next
  case (Plus a1 a2)
  then show ?case
    apply simp
    by (metis (no_types, lifting) maybe_arith_int_None Plus.prems(1) aval_reg_some_superset value_plus_def)
next
  case (Minus a1 a2)
  then show ?case
    apply simp
    by (metis (no_types, lifting) maybe_arith_int_None Minus.prems(1) aval_reg_some_superset value_minus_def)
next
  case (Times a1 a2)
  then show ?case
    apply simp
    by (metis (no_types, lifting) maybe_arith_int_None Times.prems(1) aval_reg_some_superset value_times_def)
qed

lemma enumerate_regs_empty_reg_unconstrained:
  "enumerate_regs a = {} \<Longrightarrow> \<forall>r. \<not> aexp_constrains a (V (R r))"
  by (induct a rule: aexp_induct_separate_V_cases, auto)

lemma enumerate_aexp_inputs_empty_input_unconstrained:
  "enumerate_aexp_inputs a = {} \<Longrightarrow> \<forall>r. \<not> aexp_constrains a (V (I r))"
  by (induct a rule: aexp_induct_separate_V_cases, auto)

lemma input_unconstrained_aval_input_swap:
  "\<forall>i. \<not> aexp_constrains a (V (I i)) \<Longrightarrow>
   aval a (join_ir i r) = aval a (join_ir i' r)"
  using join_ir_def
  by (induct a rule: aexp_induct_separate_V_cases, auto)

lemma input_unconstrained_aval_register_swap:
  "\<forall>i. \<not> aexp_constrains a (V (R i)) \<Longrightarrow>
   aval a (join_ir i r) = aval a (join_ir i r')"
  using join_ir_def
  by (induct a rule: aexp_induct_separate_V_cases, auto)

lemma unconstrained_variable_swap_aval:
  "\<forall>i. \<not> aexp_constrains a (V (I i)) \<Longrightarrow>
   \<forall>r. \<not> aexp_constrains a (V (R r)) \<Longrightarrow>
   aval a s = aval a s'"
  by (induct a rule: aexp_induct_separate_V_cases, auto)

lemma max_input_I: "max_input (V (vname.I i)) = Some i"
  by (simp add: max_input_def)

lemma max_input_Plus:
  "max_input (Plus a1 a2) = max (max_input a1) (max_input a2)"
  apply (simp add: max_input_def Let_def max.commute max_absorb2)
  by (metis List.finite_set Max.union enumerate_aexp_inputs_list sup_Some sup_max)

lemma max_input_Minus:
  "max_input (Minus a1 a2) = max (max_input a1) (max_input a2)"
  apply (simp add: max_input_def Let_def max.commute max_absorb2)
  by (metis List.finite_set Max.union enumerate_aexp_inputs_list sup_Some sup_max)

lemma max_input_Times:
  "max_input (Times a1 a2) = max (max_input a1) (max_input a2)"
  apply (simp add: max_input_def Let_def max.commute max_absorb2)
  by (metis List.finite_set Max.union enumerate_aexp_inputs_list sup_Some sup_max)

lemma aval_take:
  "max_input x < Some a \<Longrightarrow>
   aval x (join_ir i r) = aval x (join_ir (take a i) r)"
proof(induct x rule: aexp_induct_separate_V_cases)
  case (L x)
  then show ?case
    by simp
next
  case (I x)
  then show ?case
    by (metis aval.simps(2) input2state_take join_ir_def le_cases less_option_Some max_input_I take_all vname.simps(5))
next
  case (R x)
  then show ?case
    by (simp add: join_ir_def)
next
  case (Plus x1a x2a)
  then show ?case
    by (simp add: max_input_Plus)
next
  case (Minus x1a x2a)
  then show ?case
    by (simp add: max_input_Minus)
next
  case (Times x1a x2a)
  then show ?case
    by (simp add: max_input_Times)
qed

lemma aval_no_reg_swap_regs: "max_input x < Some a \<Longrightarrow>
   max_reg x = None \<Longrightarrow>
   aval x (join_ir i ra) = aval x (join_ir (take a i) r)"
proof(induct x)
case (L x)
  then show ?case
    by simp
next
  case (V x)
  then show ?case
    apply (cases x)
     apply (metis aval_take enumerate_regs.simps(3) enumerate_regs_empty_reg_unconstrained input_unconstrained_aval_register_swap)
    by (simp add: max_reg_def)
next
  case (Plus x1 x2)
  then show ?case
    by (metis aval_take no_reg_aval_swap_regs)
next
  case (Minus x1 x2)
  then show ?case
    by (metis aval_take no_reg_aval_swap_regs)
next
  case (Times x1 x2)
  then show ?case
    by (metis aval_take no_reg_aval_swap_regs)
qed

fun enumerate_aexp_strings :: "'a aexp \<Rightarrow> String.literal set" where
  "enumerate_aexp_strings (L (Str s)) = {s}" |
  "enumerate_aexp_strings (L (Num s)) = {}" |
  "enumerate_aexp_strings (V _) = {}" |
  "enumerate_aexp_strings (Plus a1 a2) = enumerate_aexp_strings a1 \<union> enumerate_aexp_strings a2" |
  "enumerate_aexp_strings (Minus a1 a2) = enumerate_aexp_strings a1 \<union> enumerate_aexp_strings a2" |
  "enumerate_aexp_strings (Times a1 a2) = enumerate_aexp_strings a1 \<union> enumerate_aexp_strings a2"

fun enumerate_aexp_ints :: "'a aexp \<Rightarrow> int set" where
  "enumerate_aexp_ints (L (Str s)) = {}" |
  "enumerate_aexp_ints (L (Num s)) = {s}" |
  "enumerate_aexp_ints (V _) = {}" |
  "enumerate_aexp_ints (Plus a1 a2) = enumerate_aexp_ints a1 \<union> enumerate_aexp_ints a2" |
  "enumerate_aexp_ints (Minus a1 a2) = enumerate_aexp_ints a1 \<union> enumerate_aexp_ints a2" |
  "enumerate_aexp_ints (Times a1 a2) = enumerate_aexp_ints a1 \<union> enumerate_aexp_ints a2"

definition enumerate_vars :: "vname aexp \<Rightarrow> vname set" where
  "enumerate_vars a = (image I (enumerate_aexp_inputs a)) \<union> (image R (enumerate_regs a))"

fun rename_regs :: "(nat \<Rightarrow> nat) \<Rightarrow> vname aexp \<Rightarrow> vname aexp" where
  "rename_regs _ (L l) = (L l)" |
  "rename_regs f (V (R r)) = (V (R (f r)))" |
  "rename_regs _ (V v) = (V v)" |
  "rename_regs f (Plus a b) = Plus (rename_regs f a) (rename_regs f b)" |
  "rename_regs f (Minus a b) = Minus (rename_regs f a) (rename_regs f b)" |
  "rename_regs f (Times a b) = Times (rename_regs f a) (rename_regs f b)"

definition eq_upto_rename :: "vname aexp \<Rightarrow> vname aexp \<Rightarrow> bool" where
  "eq_upto_rename a1 a2 = (\<exists>f. bij f \<and> rename_regs f a1 = a2)"

end


(* Author: Alexander Bentkamp, Universität des Saarlandes
*)
section \<open>Matrix to Vector Conversion\<close>

theory DL_Flatten_Matrix
imports Jordan_Normal_Form.Matrix
begin

definition extract_matrix :: "(nat \<Rightarrow> 'a) \<Rightarrow> nat \<Rightarrow> nat \<Rightarrow> 'a mat" where
"extract_matrix a m n = mat m n (\<lambda>(i,j). a (i*n + j))"

definition flatten_matrix :: "'a mat \<Rightarrow> (nat \<Rightarrow> 'a)" where
"flatten_matrix A k = A $$ (k div dim_col A, k mod dim_col A)"

lemma two_digit_le:
  "i * n + j < m * n" if "i < m" "j < n" for i j :: nat
  using that by (auto dest!: less_imp_Suc_add simp add: algebra_simps)

lemma extract_matrix_cong:
assumes "\<And>i. i < m * n \<Longrightarrow> a i = b i"
shows "extract_matrix a m n = extract_matrix b m n"
proof -
  have "\<And>i j. i < m \<Longrightarrow> j < n \<Longrightarrow> a (i*n + j) = b (i*n + j)" using two_digit_le assms by blast
  then show ?thesis unfolding extract_matrix_def by auto
qed

lemma extract_matrix_flatten_matrix:
"extract_matrix (flatten_matrix A) (dim_row A) (dim_col A) = A"
unfolding extract_matrix_def flatten_matrix_def by auto

lemma extract_matrix_flatten_matrix_cong:
  assumes "\<And>x. x < dim_row A * dim_col A \<Longrightarrow> f x = flatten_matrix A x"
  shows "extract_matrix f (dim_row A) (dim_col A) = A"
  unfolding extract_matrix_def
  by (metis assms extract_matrix_cong extract_matrix_def extract_matrix_flatten_matrix)

lemma flatten_matrix_extract_matrix:
  "flatten_matrix (extract_matrix a m n) k = a k" if "k < m * n"
proof -
  from that have "m * n > 0"
    by (cases "m * n = 0") simp_all
  then have "m > 0" and "n > 0"
    by simp_all
  with that have "k div n < m"
    by (metis div_eq_0_iff div_mult2_eq mult.commute neq0_conv)
  moreover have "k mod n < n"
    using \<open>n > 0\<close> by simp
  ultimately show ?thesis
    by (auto simp add: extract_matrix_def flatten_matrix_def)
qed

lemma index_extract_matrix:
assumes "i<m" "j<n"
shows "extract_matrix a m n $$ (i,j) = a (i*n + j)"
  unfolding extract_matrix_def using assms by simp

lemma dim_extract_matrix:
shows "dim_row (extract_matrix as m n) = m"
and "dim_col (extract_matrix as m n) = n"
  unfolding extract_matrix_def by simp_all

end


(*<*)
theory Regex
  imports
    "MFOTL_Monitor.Trace"
    "HOL-Library.Extended_Nat"
begin

unbundle lattice_syntax
(*>*)

section \<open>Regular expressions\<close>

context begin

qualified datatype (atms: 'a) regex = Skip nat | Test 'a
  | Plus "'a regex" "'a regex" | Times "'a regex" "'a regex"  | Star "'a regex"

lemma finite_atms[simp]: "finite (atms r)"
  by (induct r) auto

definition "Wild = Skip 1"

lemma size_regex_estimation[termination_simp]: "x \<in> atms r \<Longrightarrow> y < f x \<Longrightarrow> y < size_regex f r"
  by (induct r) auto

lemma size_regex_estimation'[termination_simp]: "x \<in> atms r \<Longrightarrow> y \<le> f x \<Longrightarrow> y \<le> size_regex f r"
  by (induct r) auto

qualified definition "TimesL r S = Times r ` S"
qualified definition "TimesR R s = (\<lambda>r. Times r s) ` R"

qualified primrec fv_regex where
  "fv_regex fv (Skip n) = {}"
| "fv_regex fv (Test \<phi>) = fv \<phi>"
| "fv_regex fv (Plus r s) = fv_regex fv r \<union> fv_regex fv s"
| "fv_regex fv (Times r s) = fv_regex fv r \<union> fv_regex fv s"
| "fv_regex fv (Star r) = fv_regex fv r"

lemma fv_regex_cong[fundef_cong]:
  "r = r' \<Longrightarrow> (\<And>z. z \<in> atms r \<Longrightarrow> fv z = fv' z) \<Longrightarrow> fv_regex fv r = fv_regex fv' r'"
  by (induct r arbitrary: r') auto

lemma finite_fv_regex[simp]: "(\<And>z. z \<in> atms r \<Longrightarrow> finite (fv z)) \<Longrightarrow> finite (fv_regex fv r)"
  by (induct r) auto

lemma fv_regex_commute:
  "(\<And>z. z \<in> atms r \<Longrightarrow> x \<in> fv z \<longleftrightarrow> g x \<in> fv' z) \<Longrightarrow> x \<in> fv_regex fv r \<longleftrightarrow> g x \<in> fv_regex fv' r"
  by (induct r) auto

lemma fv_regex_alt: "fv_regex fv r = (\<Union>z \<in> atms r. fv z)"
  by (induct r) auto

qualified definition nfv_regex where
  "nfv_regex fv r = Max (insert 0 (Suc ` fv_regex fv r))"

lemma insert_Un: "insert x (A \<union> B) = insert x A \<union> insert x B"
  by auto

lemma nfv_regex_simps[simp]:
  assumes [simp]: "(\<And>z. z \<in> atms r \<Longrightarrow> finite (fv z))" "(\<And>z. z \<in> atms s \<Longrightarrow> finite (fv z))"
  shows
  "nfv_regex fv (Skip n) = 0"
  "nfv_regex fv (Test \<phi>) = Max (insert 0 (Suc ` fv \<phi>))"
  "nfv_regex fv (Plus r s) = max (nfv_regex fv r) (nfv_regex fv s)"
  "nfv_regex fv (Times r s) = max (nfv_regex fv r) (nfv_regex fv s)"
  "nfv_regex fv (Star r) = nfv_regex fv r"
  unfolding nfv_regex_def
  by (auto simp add: image_Un Max_Un insert_Un simp del: Un_insert_right Un_insert_left)

abbreviation "min_regex_default f r j \<equiv> (if atms r = {} then j else Min ((\<lambda>z. f z j) ` atms r))"

qualified primrec match :: "(nat \<Rightarrow> 'a \<Rightarrow> bool) \<Rightarrow> 'a regex \<Rightarrow> nat \<Rightarrow> nat \<Rightarrow> bool" where
  "match test (Skip n) = (\<lambda>i j. j = i + n)"
| "match test (Test \<phi>) = (\<lambda>i j. i = j \<and> test i \<phi>)"
| "match test (Plus r s) = match test r \<squnion> match test s"
| "match test (Times r s) = match test r OO match test s"
| "match test (Star r) = (match test r)\<^sup>*\<^sup>*"

lemma match_cong[fundef_cong]:
  "r = r' \<Longrightarrow> (\<And>i z. z \<in> atms r \<Longrightarrow> t i z = t' i z) \<Longrightarrow> match t r = match t' r'"
  by (induct r arbitrary: r') auto

qualified primrec eps where
  "eps test i (Skip n) = (n = 0)"
| "eps test i (Test \<phi>) = test i \<phi>"
| "eps test i (Plus r s) = (eps test i r \<or> eps test i s)"
| "eps test i (Times r s) = (eps test i r \<and> eps test i s)"
| "eps test i (Star r) = True"

qualified primrec lpd where
  "lpd test i (Skip n) = (case n of 0 \<Rightarrow> {} | Suc m \<Rightarrow> {Skip m})"
| "lpd test i (Test \<phi>) = {}"
| "lpd test i (Plus r s) = (lpd test i r \<union> lpd test i s)"
| "lpd test i (Times r s) = TimesR (lpd test i r) s \<union> (if eps test i r then lpd test i s else {})"
| "lpd test i (Star r) = TimesR (lpd test i r) (Star r)"

qualified primrec lpd\<kappa> where
  "lpd\<kappa> \<kappa> test i (Skip n) = (case n of 0 \<Rightarrow> {} | Suc m \<Rightarrow> {\<kappa> (Skip m)})"
| "lpd\<kappa> \<kappa> test i (Test \<phi>) = {}"
| "lpd\<kappa> \<kappa> test i (Plus r s) = lpd\<kappa> \<kappa> test i r \<union> lpd\<kappa> \<kappa> test i s"
| "lpd\<kappa> \<kappa> test i (Times r s) = lpd\<kappa> (\<lambda>t. \<kappa> (Times t s)) test i r \<union> (if eps test i r then lpd\<kappa> \<kappa> test i s else {})"
| "lpd\<kappa> \<kappa> test i (Star r) = lpd\<kappa> (\<lambda>t. \<kappa> (Times t (Star r))) test i r"

qualified primrec rpd where
  "rpd test i (Skip n) = (case n of 0 \<Rightarrow> {} | Suc m \<Rightarrow> {Skip m})"
| "rpd test i (Test \<phi>) = {}"
| "rpd test i (Plus r s) = (rpd test i r \<union> rpd test i s)"
| "rpd test i (Times r s) = TimesL r (rpd test i s) \<union> (if eps test i s then rpd test i r else {})"
| "rpd test i (Star r) = TimesL (Star r) (rpd test i r)"

qualified primrec rpd\<kappa> where
  "rpd\<kappa> \<kappa> test i (Skip n) = (case n of 0 \<Rightarrow> {} | Suc m \<Rightarrow> {\<kappa> (Skip m)})"
| "rpd\<kappa> \<kappa> test i (Test \<phi>) = {}"
| "rpd\<kappa> \<kappa> test i (Plus r s) = rpd\<kappa> \<kappa> test i r \<union> rpd\<kappa> \<kappa> test i s"
| "rpd\<kappa> \<kappa> test i (Times r s) = rpd\<kappa> (\<lambda>t. \<kappa> (Times r t)) test i s \<union> (if eps test i s then rpd\<kappa> \<kappa> test i r else {})"
| "rpd\<kappa> \<kappa> test i (Star r) = rpd\<kappa> (\<lambda>t. \<kappa> (Times (Star r) t)) test i r"

lemma lpd\<kappa>_lpd: "lpd\<kappa> \<kappa> test i r = \<kappa> ` lpd test i r"
  by (induct r arbitrary: \<kappa>) (auto simp: TimesR_def split: nat.splits)

lemma rpd\<kappa>_rpd: "rpd\<kappa> \<kappa> test i r = \<kappa> ` rpd test i r"
  by (induct r arbitrary: \<kappa>) (auto simp: TimesL_def split: nat.splits)

lemma match_le: "match test r i j \<Longrightarrow> i \<le> j"
proof (induction r arbitrary: i j)
  case (Times r s)
  then show ?case using order.trans by fastforce
next
  case (Star r)
  from Star.prems show ?case
    unfolding match.simps by (induct i j rule: rtranclp.induct) (force dest: Star.IH)+
qed auto

lemma match_rtranclp_le: "(match test r)\<^sup>*\<^sup>* i j \<Longrightarrow> i \<le> j"
  by (metis match.simps(5) match_le)

lemma eps_match: "eps test i r \<longleftrightarrow> match test r i i"
  by (induction r) (auto dest: antisym[OF match_le match_le])

lemma lpd_match: "i < j \<Longrightarrow> match test r i j \<longleftrightarrow> (\<Squnion>s \<in> lpd test i r. match test s) (i + 1) j"
proof (induction r arbitrary: i j)
  case (Times r s)
  have "match test (Times r s) i j \<longleftrightarrow> (\<exists>k. match test r i k \<and> match test s k j)"
    by auto
  also have "\<dots> \<longleftrightarrow> match test r i i \<and> match test s i j \<or>
    (\<exists>k>i. match test r i k \<and> match test s k j)"
    using match_le[of test r i] nat_less_le by auto
  also have "\<dots> \<longleftrightarrow> match test r i i \<and> (\<Squnion>t \<in> lpd test i s. match test t) (i + 1) j \<or>
    (\<exists>k>i. (\<Squnion>t \<in> lpd test i r. match test t) (i + 1) k \<and> match test s k j)"
    using Times.IH(1) Times.IH(2)[OF Times.prems] by metis
  also have "\<dots> \<longleftrightarrow> match test r i i \<and> (\<Squnion>t \<in> lpd test i s. match test t) (i + 1) j \<or>
    (\<exists>k. (\<Squnion>t \<in> lpd test i r. match test t) (i + 1) k \<and> match test s k j)"
    using Times.prems by (intro disj_cong[OF refl] iff_exI) (auto dest: match_le)
  also have "\<dots> \<longleftrightarrow> (\<Squnion> (match test ` lpd test i (Times r s))) (i + 1) j"
    by (force simp: TimesL_def TimesR_def eps_match)
  finally show ?case .
next
  case (Star r)
  have "\<exists>s\<in>lpd test i r. (match test s OO (match test r)\<^sup>*\<^sup>*) (i + 1) j" if "(match test r)\<^sup>*\<^sup>* i j"
    using that Star.prems match_le[of test _ "i + 1"]
  proof (induct rule: converse_rtranclp_induct)
    case (step i k)
    then show ?case
      by (cases "i < k") (auto simp: not_less Star.IH dest: match_le)
  qed simp
  with Star.prems show ?case using match_le[of test _  "i + 1"]
    by (auto simp: TimesL_def TimesR_def Suc_le_eq Star.IH[of i]
      elim!: converse_rtranclp_into_rtranclp[rotated])
qed (auto split: nat.splits)

lemma rpd_match: "i < j \<Longrightarrow> match test r i j \<longleftrightarrow> (\<Squnion>s \<in> rpd test j r. match test s) i (j - 1)"
proof (induction r arbitrary: i j)
  case (Times r s)
  have "match test (Times r s) i j \<longleftrightarrow> (\<exists>k. match test r i k \<and> match test s k j)"
    by auto
  also have "\<dots> \<longleftrightarrow> match test r i j \<and> match test s j j \<or>
    (\<exists>k<j. match test r i k \<and> match test s k j)"
    using match_le[of test s _ j] nat_less_le by auto
  also have "\<dots> \<longleftrightarrow> (\<Squnion>t \<in> rpd test j r. match test t) i (j - 1) \<and> match test s j j  \<or>
    (\<exists>k<j. match test r i k \<and> (\<Squnion>t \<in> rpd test j s. match test t) k (j - 1))"
    using Times.IH(1)[OF Times.prems] Times.IH(2) by metis
  also have "\<dots> \<longleftrightarrow> (\<Squnion>t \<in> rpd test j r. match test t) i (j - 1) \<and> match test s j j  \<or>
    (\<exists>k. match test r i k \<and> (\<Squnion>t \<in> rpd test j s. match test t) k (j - 1))"
    using Times.prems by (intro disj_cong[OF refl] iff_exI) (auto dest: match_le)
  also have "\<dots> \<longleftrightarrow> (\<Squnion> (match test ` rpd test j (Times r s))) i (j - 1)"
    by (force simp: TimesL_def TimesR_def eps_match)
  finally show ?case .
next
  case (Star r)
  have "\<exists>s\<in>rpd test j r. ((match test r)\<^sup>*\<^sup>* OO match test s) i (j - 1)" if "(match test r)\<^sup>*\<^sup>* i j"
    using that Star.prems match_le[of test _ _ "j - 1"]
  proof (induct rule: rtranclp_induct)
    case (step k j)
    then show ?case
      by (cases "k < j") (auto simp: not_less Star.IH dest: match_le)
  qed simp
  with Star.prems show ?case
    by (auto 0 3 simp: TimesL_def TimesR_def intro: Star.IH[of _ j, THEN iffD2]
      elim!: rtranclp.rtrancl_into_rtrancl dest: match_le[of test _ _ "j - 1", unfolded One_nat_def])
qed (auto split: nat.splits)

lemma lpd_fv_regex: "s \<in> lpd test i r \<Longrightarrow> fv_regex fv s \<subseteq> fv_regex fv r"
  by (induct r arbitrary: s) (auto simp: TimesR_def TimesL_def split: if_splits nat.splits)+

lemma rpd_fv_regex: "s \<in> rpd test i r \<Longrightarrow> fv_regex fv s \<subseteq> fv_regex fv r"
  by (induct r arbitrary: s) (auto simp: TimesR_def TimesL_def split: if_splits nat.splits)+

lemma match_fv_cong:
  "(\<And>i x. x \<in> atms r \<Longrightarrow> test i x = test' i x) \<Longrightarrow> match test r = match test' r"
  by (induct r) auto

lemma eps_fv_cong:
  "(\<And>i x. x \<in> atms r \<Longrightarrow> test i x = test' i x) \<Longrightarrow> eps test i r = eps test' i r"
  by (induct r) auto

datatype modality = Past | Futu
datatype safety = Strict | Lax

context
  fixes fv :: "'a \<Rightarrow> 'b set"
  and safe :: "safety \<Rightarrow> 'a \<Rightarrow> bool"
begin

qualified fun safe_regex :: "modality \<Rightarrow> safety \<Rightarrow> 'a regex \<Rightarrow> bool" where
  "safe_regex m _ (Skip n) = True"
| "safe_regex m g (Test \<phi>) = safe g \<phi>"
| "safe_regex m g (Plus r s) = ((g = Lax \<or> fv_regex fv r = fv_regex fv s) \<and> safe_regex m g r \<and> safe_regex m g s)"
| "safe_regex Futu g (Times r s) =
    ((g = Lax \<or> fv_regex fv r \<subseteq> fv_regex fv s) \<and> safe_regex Futu g s \<and> safe_regex Futu Lax r)"
| "safe_regex Past g (Times r s) =
    ((g = Lax \<or> fv_regex fv s \<subseteq> fv_regex fv r) \<and> safe_regex Past g r \<and> safe_regex Past Lax s)"
| "safe_regex m g (Star r) = ((g = Lax \<or> fv_regex fv r = {}) \<and> safe_regex m g r)"

lemmas safe_regex_induct = safe_regex.induct[case_names Skip Test Plus TimesF TimesP Star]

lemma safe_cosafe:
  "(\<And>x. x \<in> atms r \<Longrightarrow> safe Strict x \<Longrightarrow> safe Lax x) \<Longrightarrow> safe_regex m Strict r \<Longrightarrow> safe_regex m Lax r"
  by (induct r; cases m) auto

lemma safe_lpd_fv_regex_le: "safe_regex Futu Strict r \<Longrightarrow> s \<in> lpd test i r \<Longrightarrow> fv_regex fv r \<subseteq> fv_regex fv s"
  by (induct r) (auto simp: TimesR_def split: if_splits)

lemma safe_lpd_fv_regex: "safe_regex Futu Strict r \<Longrightarrow> s \<in> lpd test i r \<Longrightarrow> fv_regex fv s = fv_regex fv r"
  by (simp add: eq_iff lpd_fv_regex safe_lpd_fv_regex_le)

lemma cosafe_lpd: "safe_regex Futu Lax r \<Longrightarrow> s \<in> lpd test i r \<Longrightarrow> safe_regex Futu Lax s"
proof (induct r arbitrary: s)
  case (Plus r1 r2)
  from Plus(3,4) show ?case
    by (auto elim: Plus(1,2))
next
  case (Times r1 r2)
  from Times(3,4) show ?case
    by (auto simp: TimesR_def elim: Times(1,2) split: if_splits)
qed (auto simp: TimesR_def split: nat.splits)

lemma safe_lpd: "(\<forall>x \<in> atms r. safe Strict x \<longrightarrow> safe Lax x) \<Longrightarrow>
  safe_regex Futu Strict r \<Longrightarrow> s \<in> lpd test i r \<Longrightarrow> safe_regex Futu Strict s"
proof (induct r arbitrary: s)
  case (Plus r1 r2)
  from Plus(3,4,5) show ?case
    by (auto elim: Plus(1,2) simp: ball_Un)
next
  case (Times r1 r2)
  from Times(3,4,5) show ?case
    by (force simp: TimesR_def ball_Un elim: Times(1,2) cosafe_lpd dest: lpd_fv_regex split: if_splits)
next
  case (Star r)
  from Star(2,3,4) show ?case
    by (force simp: TimesR_def elim: Star(1) cosafe_lpd
      dest: safe_cosafe[rotated] lpd_fv_regex[where fv=fv] split: if_splits)
qed (auto split: nat.splits)

lemma safe_rpd_fv_regex_le: "safe_regex Past Strict r \<Longrightarrow> s \<in> rpd test i r \<Longrightarrow> fv_regex fv r \<subseteq> fv_regex fv s"
  by (induct r) (auto simp: TimesL_def split: if_splits)

lemma safe_rpd_fv_regex: "safe_regex Past Strict r \<Longrightarrow> s \<in> rpd test i r \<Longrightarrow> fv_regex fv s = fv_regex fv r"
  by (simp add: eq_iff rpd_fv_regex safe_rpd_fv_regex_le)

lemma cosafe_rpd: "safe_regex Past Lax r \<Longrightarrow> s \<in> rpd test i r \<Longrightarrow> safe_regex Past Lax s"
proof (induct r arbitrary: s)
  case (Plus r1 r2)
  from Plus(3,4) show ?case
    by (auto elim: Plus(1,2))
next
  case (Times r1 r2)
  from Times(3,4) show ?case
    by (auto simp: TimesL_def elim: Times(1,2) split: if_splits)
qed (auto simp: TimesL_def split: nat.splits)

lemma safe_rpd: "(\<forall>x \<in> atms r. safe Strict x \<longrightarrow> safe Lax x) \<Longrightarrow>
  safe_regex Past Strict r \<Longrightarrow> s \<in> rpd test i r \<Longrightarrow> safe_regex Past Strict s"
proof (induct r arbitrary: s)
  case (Plus r1 r2)
  from Plus(3,4,5) show ?case
    by (auto elim: Plus(1,2) simp: ball_Un)
next
  case (Times r1 r2)
  from Times(3,4,5) show ?case
    by (force simp: TimesL_def ball_Un elim: Times(1,2) cosafe_rpd dest: rpd_fv_regex split: if_splits)
next
  case (Star r)
  from Star(2,3,4) show ?case
    by (force simp: TimesL_def elim: Star(1) cosafe_rpd
      dest: safe_cosafe[rotated] rpd_fv_regex[where fv=fv] split: if_splits)
qed (auto split: nat.splits)

lemma safe_regex_safe: "(\<And>g r. safe g r \<Longrightarrow> safe Lax r) \<Longrightarrow>
  safe_regex m g r \<Longrightarrow> x \<in> atms r \<Longrightarrow> safe Lax x"
  by (induct m g r rule: safe_regex.induct) auto

lemma safe_regex_map_regex:
  "(\<And>g x. x \<in> atms r \<Longrightarrow> safe g x \<Longrightarrow>  safe g (f x)) \<Longrightarrow> (\<And>x. x \<in> atms r \<Longrightarrow> fv (f x) = fv x) \<Longrightarrow>
   safe_regex m g r \<Longrightarrow> safe_regex m g (map_regex f r)"
  by (induct m g r rule: safe_regex.induct) (auto simp: fv_regex_alt regex.set_map)

end

lemma safe_regex_cong[fundef_cong]:
  "(\<And>g x. x \<in> atms r \<Longrightarrow> safe g x = safe' g x) \<Longrightarrow>
  Regex.safe_regex fv safe m g r = Regex.safe_regex fv safe' m g r"
  by (induct m g r rule: safe_regex.induct) auto

lemma safe_regex_mono:
  "(\<And>g x. x \<in> atms r \<Longrightarrow> safe g x \<Longrightarrow> safe' g x) \<Longrightarrow>
  Regex.safe_regex fv safe m g r \<Longrightarrow> Regex.safe_regex fv safe' m g r"
  by (induct m g r rule: safe_regex.induct) auto

lemma match_map_regex: "match t (map_regex f r) = match (\<lambda>k z. t k (f z)) r"
  by (induct r) auto

lemma match_cong_strong:
  "(\<And>k z. k \<in> {i ..< j + 1} \<Longrightarrow> z \<in> atms r \<Longrightarrow> t k z = t' k z) \<Longrightarrow> match t r i j = match t' r i j"
proof (induction r arbitrary: i j)
  case (Times r s)
  from Times.prems show ?case
    by (auto 0 4 simp: relcompp_apply intro: le_less_trans match_le less_Suc_eq_le
      dest: Times.IH[THEN iffD1, rotated -1] Times.IH[THEN iffD2, rotated -1] match_le)
next
  case (Star r)
  show ?case unfolding match.simps
  proof (rule iffI)
    assume *: "(match t r)\<^sup>*\<^sup>* i j"
    then have "i \<le> j" unfolding match.simps(5)[symmetric]
      by (rule match_le)
    with * show "(match t' r)\<^sup>*\<^sup>* i j" using Star.prems
    proof (induction i j rule: rtranclp.induct)
      case (rtrancl_into_rtrancl a b c)
      from rtrancl_into_rtrancl(1,2,4,5) show ?case
        by (intro rtranclp.rtrancl_into_rtrancl[OF rtrancl_into_rtrancl.IH])
          (auto dest!: Star.IH[THEN iffD1, rotated -1]
            dest: match_le match_rtranclp_le simp: less_Suc_eq_le)
    qed simp
  next
    assume *: "(match t' r)\<^sup>*\<^sup>* i j"
    then have "i \<le> j" unfolding match.simps(5)[symmetric]
      by (rule match_le)
    with * show "(match t r)\<^sup>*\<^sup>* i j" using Star.prems
    proof (induction i j rule: rtranclp.induct)
      case (rtrancl_into_rtrancl a b c)
      from rtrancl_into_rtrancl(1,2,4,5) show ?case
        by (intro rtranclp.rtrancl_into_rtrancl[OF rtrancl_into_rtrancl.IH])
          (auto dest!: Star.IH[THEN iffD2, rotated -1]
            dest: match_le match_rtranclp_le simp: less_Suc_eq_le)
    qed simp
  qed
qed auto

end

(*<*)
end
(*>*)


theory Finite_Linear_Model

imports
  Main
begin

text \<open> This theory is an encoding of the Finite Linear model, the most discriminating CSP
       denotational model in the hierarchy of CSP models. It is just as discriminating as
       weak bisimulation over the operational semantics of CSP. \<close>

section \<open> Model \<close>

subsection \<open> Acceptances \<close>

text \<open> An acceptance is either \<bullet> or a set. \<close>

datatype 'e acceptance = acnil ("\<bullet>") | acset "'e set" ("[_]\<^sub>\<F>\<^sub>\<L>")

text \<open> Here we define an order on acceptances, whereby \<bullet> is related to
       any element, and otherwise sets need to be equal. \<close>

instantiation acceptance :: (type) order
begin
  fun less_eq_acceptance :: "'a acceptance \<Rightarrow> 'a acceptance \<Rightarrow> bool" where
  "\<bullet> \<le> S = True" |
  "R \<le> \<bullet> = (R = \<bullet>)" |
  "[X]\<^sub>\<F>\<^sub>\<L> \<le> [Y]\<^sub>\<F>\<^sub>\<L> = (if X = Y then True else False)"
  definition less_acceptance :: "'a acceptance \<Rightarrow> 'a acceptance \<Rightarrow> bool" where
  "less_acceptance S R = (S \<le> R \<and> \<not> R \<le> S)"
instance proof
  fix x y z :: "'a acceptance"
  show "(x < y) = (x \<le> y \<and> \<not> y \<le> x)"
    by (simp add: less_acceptance_def)
  show "x \<le> x"
    by (cases x, simp_all)
  show "x \<le> y \<Longrightarrow> y \<le> z \<Longrightarrow> x \<le> z"
    apply (cases x, simp_all, cases y, simp_all, cases z, simp_all)
    by presburger
  show "x \<le> y \<Longrightarrow> y \<le> x \<Longrightarrow> x = y"
    by (metis less_eq_acceptance.elims(2) less_eq_acceptance.simps(2))
qed
end

text \<open> We also define a plus operator on acceptances that allows \<bullet> to
       be augmented, but nothing else. \<close>

instantiation acceptance :: (type) plus
begin

fun plus_acceptance :: "'a acceptance \<Rightarrow> 'a acceptance \<Rightarrow> 'a acceptance"  where
  "plus_acceptance \<bullet> R = R" |
  "plus_acceptance S \<bullet> = S" |
  "plus_acceptance [S]\<^sub>\<F>\<^sub>\<L> [R]\<^sub>\<F>\<^sub>\<L> = [S]\<^sub>\<F>\<^sub>\<L>"

instance
  by intro_classes
end

text \<open> It is associative, and thus we have a semigroup. \<close>

lemma acceptance_assoc:
  fixes a b c :: "'a acceptance"
  shows "(a + b) + c = a + (b + c)"
  apply (induct a)
   apply (case_tac b, auto)
  apply (case_tac b, auto)
  by (case_tac c, auto)

instantiation acceptance :: (type) semigroup_add
begin

instance
  apply (intro_classes)
  by (simp add:acceptance_assoc)
end

lemma acceptance_right_zero[simp]:
  fixes a :: "'a acceptance"
  shows "a + \<bullet> = a"
  by (induct a, auto)

lemma acceptance_left_zero[simp]:
  fixes a :: "'a acceptance"
  shows "\<bullet> + a = a"
  by (induct a, auto)

text \<open> Taking \<bullet> as the zero, we have thus a monoid. \<close>

instantiation acceptance :: (type) monoid_add
begin

definition zero_acceptance where "zero_acceptance = \<bullet>"
instance
  apply (intro_classes)
  by (simp add:zero_acceptance_def)+
end

instantiation acceptance :: (type) minus
begin

fun minus_acceptance :: "'a acceptance \<Rightarrow> 'a acceptance \<Rightarrow> 'a acceptance"  where
  "minus_acceptance \<bullet> R = \<bullet>" |
  "minus_acceptance S \<bullet> = S" |
  "minus_acceptance [S]\<^sub>\<F>\<^sub>\<L> [R]\<^sub>\<F>\<^sub>\<L> = [S - R]\<^sub>\<F>\<^sub>\<L>"

instance
  by intro_classes
end

fun amember :: "'a \<Rightarrow> 'a acceptance \<Rightarrow> bool" ("(_/ \<in>\<^sub>\<F>\<^sub>\<L> _)" [51, 51] 50) where
"x \<in>\<^sub>\<F>\<^sub>\<L> \<bullet> = False" | "x \<in>\<^sub>\<F>\<^sub>\<L> [R]\<^sub>\<F>\<^sub>\<L> = (x \<in> R)"

abbreviation rnot_member ("(_/ \<notin>\<^sub>\<F>\<^sub>\<L> _)" [51, 51] 50)
  where "rnot_member x A \<equiv> \<not> (x \<in>\<^sub>\<F>\<^sub>\<L>  A)"

text \<open> Note that we adopt the following definition following page 257 of UCS, rather
       than use our definition of plus. \<close>

fun aunion :: "'a acceptance \<Rightarrow> 'a acceptance \<Rightarrow> 'a acceptance" (infixl "\<union>\<^sub>\<F>\<^sub>\<L>" 65) where
"aunion \<bullet> X = \<bullet>" |
"aunion X \<bullet> = \<bullet>" |
"aunion [S]\<^sub>\<F>\<^sub>\<L> [R]\<^sub>\<F>\<^sub>\<L> = [S \<union> R]\<^sub>\<F>\<^sub>\<L>"

lemma aunion_idem: "a \<union>\<^sub>\<F>\<^sub>\<L> a = a"
  by (cases a, auto)

lemma aunion_assoc: "(a \<union>\<^sub>\<F>\<^sub>\<L> b) \<union>\<^sub>\<F>\<^sub>\<L> c = a \<union>\<^sub>\<F>\<^sub>\<L> (b \<union>\<^sub>\<F>\<^sub>\<L> c)"
  by (cases a, auto, cases b, auto, cases c, auto)

lemma aunion_comm: "a \<union>\<^sub>\<F>\<^sub>\<L> b = b \<union>\<^sub>\<F>\<^sub>\<L> a"
  by (cases a, auto, cases b, auto, cases b, auto)

interpretation aunion_comm_monoid: comm_monoid "aunion" "[{}]\<^sub>\<F>\<^sub>\<L>"
  apply (unfold_locales)
  using aunion_assoc apply blast
  using aunion_comm apply blast
  by (case_tac a, auto)+

subsection \<open> Acceptance Events \<close>

text \<open> Here we define the type for an acceptance followed by an event as
       an aevent type definition of pairs (X,a) where the event a belongs
       to the acceptance X or X is \<bullet>. \<close>

typedef 'e aevent = "{(X :: 'e acceptance, a :: 'e). a \<in>\<^sub>\<F>\<^sub>\<L> X \<or> X = \<bullet>}" by auto

syntax
  "_revent"     :: "args \<Rightarrow> 'e \<Rightarrow> 'e aevent"    ("'(_,_')\<^sub>\<F>\<^sub>\<L>")

translations
  "(x,y)\<^sub>\<F>\<^sub>\<L>" == "CONST Abs_aevent (x,y)"

setup_lifting type_definition_aevent

thm Rep_aevent
thm Abs_aevent_inverse
thm type_definition_aevent

lift_definition acceptance :: "'e aevent \<Rightarrow> 'e acceptance" is fst .
lift_definition event   :: "'e aevent \<Rightarrow> 'e" is snd .

subsubsection \<open> Partial order \<close>

instantiation aevent :: (type) order
begin
  definition less_eq_aevent :: "'e aevent \<Rightarrow> 'e aevent \<Rightarrow> bool" where "less_eq_aevent a b \<equiv> acceptance a \<le> acceptance b \<and> event a = event b"
  definition less_aevent :: "'e aevent \<Rightarrow> 'e aevent \<Rightarrow> bool" where "less_aevent a b \<equiv> a \<le> b \<and> \<not>(b \<le> a)"

instance apply intro_classes
     apply (auto simp add:less_aevent_def less_eq_aevent_def)
  by (metis Rep_aevent_inject acceptance.rep_eq event.rep_eq prod.expand)
end

instantiation aevent :: (type) plus
begin

definition plus_aevent :: "'a aevent \<Rightarrow> 'a aevent \<Rightarrow> 'a aevent" where
"plus_aevent A B = (if event(B) \<in>\<^sub>\<F>\<^sub>\<L> acceptance(A)+acceptance(B) then
                        (acceptance(A)+acceptance(B),event(B))\<^sub>\<F>\<^sub>\<L>
                    else (\<bullet>,event(B))\<^sub>\<F>\<^sub>\<L> ) "

instance by intro_classes
end

subsubsection \<open> Simplification rules \<close>

text \<open> Next we define simplification rules that aid in the manipulation of acceptances and
       aevents. \<close>

lemma event_in_acceptance [simp]:
  assumes "acceptance(A) \<noteq> \<bullet>"
  shows "event(A) \<in>\<^sub>\<F>\<^sub>\<L> acceptance(A)"
  by (metis (mono_tags, lifting) Rep_aevent acceptance.rep_eq assms event.rep_eq mem_Collect_eq prod.collapse prod.simps(2))

lemma acceptance_set [simp]:
  assumes "b \<in>\<^sub>\<F>\<^sub>\<L> A \<or> A = \<bullet>"
  shows "acceptance (A,b)\<^sub>\<F>\<^sub>\<L> = A"
  by (simp add: Abs_aevent_inverse acceptance.rep_eq assms)

lemma acceptance_event [simp]: "e \<in>\<^sub>\<F>\<^sub>\<L> A \<or> A = \<bullet> \<Longrightarrow> event (A,e)\<^sub>\<F>\<^sub>\<L> = e"
  by (simp add: Abs_aevent_inverse event.rep_eq)

lemma acceptance_acceptance_pair [simp]: "acceptance (acceptance(a), event(a))\<^sub>\<F>\<^sub>\<L> = acceptance(a)"
  apply (auto simp add:acceptance_def Abs_aevent_inverse)
  by (simp add: Rep_aevent_inverse event.rep_eq)

lemma event_accepance_pair [simp]: "event (acceptance(a), event(a))\<^sub>\<F>\<^sub>\<L> = event(a)"
  apply (auto simp add:event_def Abs_aevent_inverse)
  by (simp add: Rep_aevent_inverse acceptance.rep_eq)

lemma acceptance_pair_eq:
  assumes "b \<in>\<^sub>\<F>\<^sub>\<L> a" "d \<in>\<^sub>\<F>\<^sub>\<L> c"
  shows "(a, b)\<^sub>\<F>\<^sub>\<L> = (c, d)\<^sub>\<F>\<^sub>\<L> \<longleftrightarrow> a = c \<and> b = d"
  using assms
  using acceptance_event acceptance_set by force

lemma unionA_sym [simp]: "A \<union>\<^sub>\<F>\<^sub>\<L> B = B \<union>\<^sub>\<F>\<^sub>\<L> A"
  by (cases A, auto, cases B, auto, cases B, auto)

lemma acceptance_of_aevent_aunion_acceptances_1 [simp]:
 "acceptance (acceptance A \<union>\<^sub>\<F>\<^sub>\<L> acceptance B,event A)\<^sub>\<F>\<^sub>\<L> = acceptance A \<union>\<^sub>\<F>\<^sub>\<L> acceptance B"
 by (cases A, auto, cases B, auto, case_tac a, auto, case_tac aa, auto, case_tac a, auto)

lemma acceptance_of_aevent_aunion_acceptances_2 [simp]:
 "acceptance (acceptance A \<union>\<^sub>\<F>\<^sub>\<L> acceptance B,event B)\<^sub>\<F>\<^sub>\<L> = acceptance A \<union>\<^sub>\<F>\<^sub>\<L> acceptance B"
  by (cases A, auto)

lemma prefix_aevent_acceptance_not_bullet:
  assumes "s \<le> (acceptance A \<union>\<^sub>\<F>\<^sub>\<L> X,event A)\<^sub>\<F>\<^sub>\<L>"
          "acceptance(s) \<noteq> \<bullet>"
    shows "event(A) \<in>\<^sub>\<F>\<^sub>\<L> acceptance(s)"
  using assms
  apply (simp add: less_eq_aevent_def)
  apply (cases A, auto, cases X, auto)
  apply (cases s, auto, case_tac a, auto)
  by (cases s, auto, case_tac a, auto)

lemma aevent_less_eq_iff_components:
  assumes "e \<in>\<^sub>\<F>\<^sub>\<L> A \<or> A = \<bullet>"
  shows "x \<le> (A,e)\<^sub>\<F>\<^sub>\<L> \<longleftrightarrow> x = (A,e)\<^sub>\<F>\<^sub>\<L> \<or> x = (\<bullet>,e)\<^sub>\<F>\<^sub>\<L>"
  using assms
  apply (simp add: less_eq_aevent_def)
  apply auto
    apply (cases A, auto, cases x, auto, case_tac a, auto)
   apply presburger
  by (cases A, auto, cases x, auto, case_tac a, auto)

subsection \<open> Finite Linear Trace \<close>

text \<open> Finally we define the finite linear trace as a datatype. Observe that this is an instance
       of a terminated list whereby the final element is an acceptance. \<close>

datatype 'e fltrace = Acceptance "'e acceptance" ("\<langle>_\<rangle>\<^sub>\<F>\<^sub>\<L>") | AEvent "'e aevent" "'e fltrace" (infix "#\<^sub>\<F>\<^sub>\<L>" 65)

syntax
  "_rtrace"     :: "args \<Rightarrow> 'e \<Rightarrow> 'e fltrace"    ("\<langle>(_),(_)\<rangle>\<^sub>\<F>\<^sub>\<L>")

translations
  "\<langle>x,xs,y\<rangle>\<^sub>\<F>\<^sub>\<L>" == "(x#\<^sub>\<F>\<^sub>\<L>\<langle>xs,y\<rangle>\<^sub>\<F>\<^sub>\<L>)"
  "\<langle>x,y\<rangle>\<^sub>\<F>\<^sub>\<L>" == "x#\<^sub>\<F>\<^sub>\<L>\<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L>"

lemma fl_cons_never_fixpoint [simp]: "\<not> aa = x #\<^sub>\<F>\<^sub>\<L> aa"
  by (induct aa, auto)

subsubsection \<open> Partial orders \<close>

text \<open> We can define the partial order on fltraces by using the order over acceptances
       in the following way. \<close>

instantiation fltrace :: (type) order
begin

fun less_eq_fltrace :: "'a fltrace \<Rightarrow> 'a fltrace \<Rightarrow> bool" where
"less_eq_fltrace \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> \<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L> = (x \<le> y)" |
"less_eq_fltrace \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> (y#\<^sub>\<F>\<^sub>\<L>ys) = (x \<le> acceptance y)" |
"less_eq_fltrace (x#\<^sub>\<F>\<^sub>\<L>xs) (y#\<^sub>\<F>\<^sub>\<L>ys) = (x \<le> y \<and> less_eq_fltrace xs ys)" |
"less_eq_fltrace (x#\<^sub>\<F>\<^sub>\<L>xs) \<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L> = False"

definition less_fltrace :: "'a fltrace \<Rightarrow> 'a fltrace \<Rightarrow> bool" where
    "less_fltrace a b \<equiv> a \<le> b \<and> \<not>(b \<le> a)"

lemma fltrace_refl:
  fixes x :: "'a fltrace"
  shows "x \<le> x"
  apply (induct x rule:fltrace.induct)
  by auto

lemma fltrace_trans:
  fixes x y z :: "'a fltrace"
  shows "x \<le> y \<Longrightarrow> y \<le> z \<Longrightarrow> x \<le> z"
  apply (induct x z arbitrary:y rule:less_eq_fltrace.induct)
  apply (case_tac ya)
      apply auto
     apply (metis less_eq_fltrace.simps(2) less_eq_fltrace.simps(3) less_eq_aevent_def less_eq_fltrace.simps(1) order_trans fltrace.exhaust)
  by (metis less_eq_fltrace.simps(3) less_eq_fltrace.simps(4) order_trans fltrace.exhaust)+

lemma fltrace_antisym:
  fixes x y z :: "'a fltrace"
  shows "x \<le> y \<Longrightarrow> y \<le> x \<Longrightarrow> x = y"
  apply (induct x y rule:less_eq_fltrace.induct)
  by auto

instance apply intro_classes
    apply (auto simp add:less_fltrace_def)
   apply (simp add: fltrace_refl)
  using fltrace_trans apply blast
  using fltrace_antisym by blast
end

fun strong_less_eq_fltrace :: "'a fltrace \<Rightarrow> 'a fltrace \<Rightarrow> bool" (infix "\<le>\<^sub>\<F>\<^sub>\<L>" 50) where
"strong_less_eq_fltrace \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> \<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L> = (x \<le> y)" |
"strong_less_eq_fltrace \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> (y#\<^sub>\<F>\<^sub>\<L>ys) = (False)" |
"strong_less_eq_fltrace (x#\<^sub>\<F>\<^sub>\<L>xs) (y#\<^sub>\<F>\<^sub>\<L>ys) = (x \<le> y \<and> strong_less_eq_fltrace xs ys)" |
"strong_less_eq_fltrace (x#\<^sub>\<F>\<^sub>\<L>xs) \<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L> = False"

lemma strong_less_eq_fltrace_refl:
  fixes x :: "'a fltrace"
  shows "x \<le>\<^sub>\<F>\<^sub>\<L> x"
  apply (induct x rule:fltrace.induct)
  by auto

lemma strong_less_eq_fltrace_trans:
  fixes x y z :: "'a fltrace"
  shows "x \<le>\<^sub>\<F>\<^sub>\<L> y \<Longrightarrow> y \<le>\<^sub>\<F>\<^sub>\<L> z \<Longrightarrow> x \<le>\<^sub>\<F>\<^sub>\<L> z"
  apply (induct x z arbitrary:y rule:strong_less_eq_fltrace.induct)
     apply (case_tac ya, auto, case_tac x, auto, case_tac ya, auto, case_tac ya, auto)
  apply (metis (no_types, hide_lams) dual_order.trans fltrace.exhaust strong_less_eq_fltrace.simps(3) strong_less_eq_fltrace.simps(4))
  apply (metis (no_types, hide_lams) fltrace.exhaust  strong_less_eq_fltrace.simps(3) strong_less_eq_fltrace.simps(4))
  by (case_tac y, auto, case_tac ya, auto, case_tac ya, auto)

lemma strong_less_eq_fltrace_antisym:
  fixes x y z :: "'a fltrace"
  shows "x \<le>\<^sub>\<F>\<^sub>\<L> y \<Longrightarrow> y \<le>\<^sub>\<F>\<^sub>\<L> x \<Longrightarrow> x = y"
  apply (induct x y rule:strong_less_eq_fltrace.induct)
  by auto

lemma strongFL_imp_less_eq [simp]:
  assumes "x \<le>\<^sub>\<F>\<^sub>\<L> y"
  shows "x \<le> y"
  using assms by(induct x y rule:less_eq_fltrace.induct, auto)

subsubsection \<open> Operators \<close>

text \<open> Below we define a concatenation operator that is associative. Observe that,
       in the context of a trace algebra, this definition is not suitable to define
       a prefix relation that captures prefix closure for the FL-model. A consequence
       of this is that it is rather impractical to use it for induction rules.

       Note: I think it is possible to define the appropriate prefix closure relation
             by using the disjoint trace algebra. \<close>

fun fltrace_concat :: "'e fltrace \<Rightarrow> 'e fltrace \<Rightarrow> 'e fltrace" (infix "@\<^sub>\<F>\<^sub>\<L>" 65) where
"fltrace_concat \<langle>A\<rangle>\<^sub>\<F>\<^sub>\<L> \<langle>B\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>B\<rangle>\<^sub>\<F>\<^sub>\<L>" |
"fltrace_concat (A #\<^sub>\<F>\<^sub>\<L> fl) b = (A #\<^sub>\<F>\<^sub>\<L> fltrace_concat fl b)" |
"fltrace_concat \<langle>A\<rangle>\<^sub>\<F>\<^sub>\<L> (B #\<^sub>\<F>\<^sub>\<L> fl) = (B #\<^sub>\<F>\<^sub>\<L> fl)"

lemma bullet_left_zero[simp]:
  "(\<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> @\<^sub>\<F>\<^sub>\<L> z) = z"
  by (case_tac z, auto simp add:Rep_aevent_inverse acceptance.rep_eq event.rep_eq)

lemma fltrace_concat_assoc: "(s @\<^sub>\<F>\<^sub>\<L> t) @\<^sub>\<F>\<^sub>\<L> z = s @\<^sub>\<F>\<^sub>\<L> (t @\<^sub>\<F>\<^sub>\<L> z)"
  apply (induct s, auto)
  apply (induct t, auto)
  apply (case_tac x, auto)
   apply (case_tac xa, auto)
   apply (induct z, auto)
  apply (case_tac xa, auto)
  by (induct z, auto)

fun fltrace_concat2 :: "'e fltrace \<Rightarrow> 'e fltrace \<Rightarrow> 'e fltrace" (infix "&\<^sub>\<F>\<^sub>\<L>" 65) where
"fltrace_concat2 \<langle>A\<rangle>\<^sub>\<F>\<^sub>\<L> \<langle>B\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>A+B\<rangle>\<^sub>\<F>\<^sub>\<L>" |
"fltrace_concat2 (A #\<^sub>\<F>\<^sub>\<L> fl) b = (A #\<^sub>\<F>\<^sub>\<L> fltrace_concat2 fl b)" |
"fltrace_concat2 \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> (B #\<^sub>\<F>\<^sub>\<L> fl) = (B #\<^sub>\<F>\<^sub>\<L> fl)" |
"fltrace_concat2 \<langle>A\<rangle>\<^sub>\<F>\<^sub>\<L> (B #\<^sub>\<F>\<^sub>\<L> fl) = \<langle>A\<rangle>\<^sub>\<F>\<^sub>\<L>"

lemma bullet_left_zero2[simp]:
  "(\<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> z) = z"
  by (case_tac z, auto simp add:Rep_aevent_inverse acceptance.rep_eq event.rep_eq)

lemma bullet_right_zero2[simp]:
  "x &\<^sub>\<F>\<^sub>\<L> \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> = x"
  by (induct x, auto)

lemma fltrace_concat2_assoc: "(s &\<^sub>\<F>\<^sub>\<L> t) &\<^sub>\<F>\<^sub>\<L> z = s &\<^sub>\<F>\<^sub>\<L> (t &\<^sub>\<F>\<^sub>\<L> z)"
  apply (induct s, auto)
  apply (induct t, auto)
  apply (case_tac x, auto)
   apply (case_tac xa, auto)
   apply (induct z, auto)
   apply (case_tac x, auto)
  apply (case_tac x, auto)
  apply (induct z, auto)
  by (case_tac x, auto)

lemma x_le_x_concat2:
  "x \<le> x &\<^sub>\<F>\<^sub>\<L> y"
  apply (induct x, auto)
  apply (case_tac x, auto)
   apply (case_tac y, auto)
  apply (case_tac y, auto)
  by (case_tac x1, auto)

text \<open> Below we define auxiliary functions on FL traces using the concatenation
       operators we defined earlier. \<close>

primrec rev :: "'a fltrace \<Rightarrow> 'a fltrace" where
"rev \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>" |
"rev (x #\<^sub>\<F>\<^sub>\<L> xs) = rev xs @\<^sub>\<F>\<^sub>\<L> \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"

primrec rev2 :: "'a fltrace \<Rightarrow> 'a fltrace" where
"rev2 \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L>" |
"rev2 (x #\<^sub>\<F>\<^sub>\<L> xs) = rev2 xs @\<^sub>\<F>\<^sub>\<L> \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"

primrec butlast :: "'a fltrace \<Rightarrow> 'a fltrace" where
"butlast \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>" |
"butlast (x #\<^sub>\<F>\<^sub>\<L> xs) = (x #\<^sub>\<F>\<^sub>\<L> butlast xs)"

primrec last :: "'a fltrace \<Rightarrow> 'a acceptance" where
"last \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> = x" |
"last (x #\<^sub>\<F>\<^sub>\<L> xs) = last xs"

primrec first :: "'a fltrace \<Rightarrow> 'a acceptance" where
"first \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> = x" |
"first (x #\<^sub>\<F>\<^sub>\<L> xs) = acceptance(x)"

lemma "rev \<langle>x,y\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  by (auto simp add: Rep_aevent_inverse acceptance.rep_eq event.rep_eq)

lemma "butlast \<langle>x,y\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  by auto

lemma rev2_little: "rev2((xs @\<^sub>\<F>\<^sub>\<L> \<langle>z,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) @\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = y #\<^sub>\<F>\<^sub>\<L> rev2(xs @\<^sub>\<F>\<^sub>\<L> \<langle>z,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>)"
  by (induct xs, auto simp add: Rep_aevent_inverse acceptance.rep_eq event.rep_eq)

lemma rev_little: "rev((xs @\<^sub>\<F>\<^sub>\<L> \<langle>z,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) @\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = y #\<^sub>\<F>\<^sub>\<L> rev(xs @\<^sub>\<F>\<^sub>\<L> \<langle>z,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>)"
  by (induct xs, auto simp add: Rep_aevent_inverse acceptance.rep_eq event.rep_eq)

lemma rev_little_cons: "rev(rev(x #\<^sub>\<F>\<^sub>\<L> xs)) = x #\<^sub>\<F>\<^sub>\<L> rev(rev(xs))"
  apply (induct xs, auto simp add: Rep_aevent_inverse acceptance.rep_eq event.rep_eq)
  by (simp add: rev_little)

lemma rev_rev_butlast: "rev(rev(x)) = butlast x"
  apply (induct x, auto)
  by (metis rev.simps(2) rev_little_cons)

lemma butlast_last_FL: "butlast x @\<^sub>\<F>\<^sub>\<L> \<langle>last x\<rangle>\<^sub>\<F>\<^sub>\<L> = x"
  by (induct x, auto)

lemma butlast_last_cons2_FL: "butlast x &\<^sub>\<F>\<^sub>\<L> \<langle>last x\<rangle>\<^sub>\<F>\<^sub>\<L> = x"
  by (induct x, auto)

lemma rev_rev_last: "rev(rev(x)) @\<^sub>\<F>\<^sub>\<L> \<langle>last x\<rangle>\<^sub>\<F>\<^sub>\<L>= x"
  by (simp add: butlast_last_FL rev_rev_butlast)

primrec rev3 :: "'a fltrace \<Rightarrow> 'a fltrace" where
"rev3 \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>" |
"rev3 (x #\<^sub>\<F>\<^sub>\<L> xs) = rev3 xs &\<^sub>\<F>\<^sub>\<L> \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"

definition rev4 :: "'a fltrace \<Rightarrow> 'a fltrace" where
"rev4 xs = rev3(xs) &\<^sub>\<F>\<^sub>\<L> \<langle>last xs\<rangle>\<^sub>\<F>\<^sub>\<L>"

lemma last_fltrace_acceptance: "last (\<langle>y,z\<rangle>\<^sub>\<F>\<^sub>\<L>) = z"
  by auto

lemma last_cons_fltrace_acceptance: "last (\<langle>ys,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<langle>y,z\<rangle>\<^sub>\<F>\<^sub>\<L>) = z"
  by auto

lemma last_dist_plus: "last(xs &\<^sub>\<F>\<^sub>\<L> ys) = last xs + last ys"
  apply (induct xs, auto)
  apply (induct ys, auto)
  apply (case_tac x, auto)
  by (metis acceptance.simps(3) plus_acceptance.elims)

lemma last_rev3_is_bullet: "last(rev3 xs) = \<bullet>"
  apply (induct xs, auto)
  by (auto simp add:last_dist_plus)

lemma last_rev3_acceptance: "last(rev3 xs &\<^sub>\<F>\<^sub>\<L> \<langle>z\<rangle>\<^sub>\<F>\<^sub>\<L>) = z"
  apply (auto simp add:last_dist_plus)
  by (simp add:last_rev3_is_bullet)

lemma last_rev3_cons2_is_last_cons: "last(rev3 xs &\<^sub>\<F>\<^sub>\<L> ys) = last ys"
  apply (auto simp add:last_dist_plus)
  by (simp add:last_rev3_is_bullet)

lemma "rev3 \<langle>x,y\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  by (auto simp add: Rep_aevent_inverse acceptance.rep_eq event.rep_eq)

lemma "rev4 \<langle>x,y\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>x,y\<rangle>\<^sub>\<F>\<^sub>\<L>"
  unfolding rev4_def by (auto simp add: Rep_aevent_inverse acceptance.rep_eq event.rep_eq)

lemma fltrace_cons2_last: "\<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L> = \<langle>x1a,last(xs)\<rangle>\<^sub>\<F>\<^sub>\<L>"
  by (induct xs, auto)

lemma rev3_little: "rev3(\<langle>xs,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = y #\<^sub>\<F>\<^sub>\<L> rev3(\<langle>xs,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>)"
  by (induct xs, auto)

lemma rev3_little_bullet: "rev3(\<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = y #\<^sub>\<F>\<^sub>\<L> rev3(\<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>)"
  by auto

lemma rev3_little_more:
  assumes "last(zs) = \<bullet>"
  shows "rev3(zs &\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = y #\<^sub>\<F>\<^sub>\<L> rev3(zs)"
  using assms
  apply (induct zs)
  by auto

lemma rev3_little_more_extra:
  assumes "last(zs) = \<bullet>"
  shows "rev3(zs &\<^sub>\<F>\<^sub>\<L> \<langle>y,z\<rangle>\<^sub>\<F>\<^sub>\<L>) = y #\<^sub>\<F>\<^sub>\<L> rev3(zs)"
  using assms
  apply (induct zs)
  by auto

lemma rev3_little_complete: "rev3(rev3 xs &\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = y #\<^sub>\<F>\<^sub>\<L> rev3(rev3(xs))"
  by (simp add: last_rev3_is_bullet rev3_little_more)

lemma rev3_little_complete_extensive: "rev3(rev3 xs &\<^sub>\<F>\<^sub>\<L> \<langle>y,z\<rangle>\<^sub>\<F>\<^sub>\<L>) = y #\<^sub>\<F>\<^sub>\<L> (rev3(rev3(xs)))"
  by (simp add: last_rev3_is_bullet rev3_little_more_extra)

lemma rev_little_cons2: "rev3(rev3(x #\<^sub>\<F>\<^sub>\<L> xs)) = x #\<^sub>\<F>\<^sub>\<L> rev3(rev3(xs))"
  apply (induct xs, auto simp add: Rep_aevent_inverse acceptance.rep_eq event.rep_eq)
  by (simp add: last_rev3_cons2_is_last_cons rev3_little_more)

lemma FL_concat_equiv: "x1a #\<^sub>\<F>\<^sub>\<L> xs = \<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> xs"
  by auto

lemma last_butlast_cons_bullet: "last(butlast x &\<^sub>\<F>\<^sub>\<L> \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = \<bullet>"
  by (induct x, auto)

lemma rev3_cons2_little:
   "rev3(xs &\<^sub>\<F>\<^sub>\<L> \<langle>x1a,z\<rangle>\<^sub>\<F>\<^sub>\<L>) = rev3(xs &\<^sub>\<F>\<^sub>\<L> \<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>)"
  apply (induct xs, auto)
  by (case_tac x, auto)

lemma rev3_rev3_const2_last: "rev3(rev3(xs)) &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L> = xs"
  apply (induct xs, auto)
  by (simp add: rev3_little_complete)

lemma rev4_rev4: "rev4(rev4(xs)) = xs"
(* No need for induction at all!
  unfolding rev4_def apply (case_tac xs, auto)
  sledgehammer[debug=true] *)
proof (induct xs)
  case (Acceptance x)
  then show ?case unfolding rev4_def by auto
next
  case (AEvent x1a xs)
  have "rev4 (rev4 (x1a #\<^sub>\<F>\<^sub>\<L> xs)) = (rev4(rev3(x1a #\<^sub>\<F>\<^sub>\<L> xs) &\<^sub>\<F>\<^sub>\<L> \<langle>last (x1a #\<^sub>\<F>\<^sub>\<L> xs)\<rangle>\<^sub>\<F>\<^sub>\<L>))"
    unfolding rev4_def by auto
  also have "... = (rev4(rev3(x1a #\<^sub>\<F>\<^sub>\<L> xs) &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>))"
    by auto
  also have "... = (rev4((rev3 xs &\<^sub>\<F>\<^sub>\<L> \<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>))"
    by auto
  also have "... = (rev4(rev3 xs &\<^sub>\<F>\<^sub>\<L> (\<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>)))"
    by (auto simp add:fltrace_concat2_assoc)
  also have "... = (rev3(rev3 xs &\<^sub>\<F>\<^sub>\<L> (\<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>))
                    &\<^sub>\<F>\<^sub>\<L> \<langle>last (rev3 xs &\<^sub>\<F>\<^sub>\<L> (\<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>))\<rangle>\<^sub>\<F>\<^sub>\<L>)"
    unfolding rev4_def by auto
  also have "... = (rev3(rev3 xs &\<^sub>\<F>\<^sub>\<L> (\<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>)) &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>)"
    by (auto simp add:last_rev3_cons2_is_last_cons)
  also have "... = (rev3(rev3 xs &\<^sub>\<F>\<^sub>\<L> \<langle>x1a,last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>) &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>)"
    by auto
  also have "... = (rev3(rev3 xs &\<^sub>\<F>\<^sub>\<L> \<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>)"
    using rev3_cons2_little
    by metis
  also have "... = (x1a #\<^sub>\<F>\<^sub>\<L> rev3(rev3(xs))) &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>"
    by (simp add: rev3_little_complete)
  also have "... = x1a #\<^sub>\<F>\<^sub>\<L> (rev3(rev3(xs)) &\<^sub>\<F>\<^sub>\<L> \<langle>last (xs)\<rangle>\<^sub>\<F>\<^sub>\<L>)"
    by simp
  also have "... = x1a #\<^sub>\<F>\<^sub>\<L> xs"
    by (auto simp add:rev3_rev3_const2_last)

  then show ?case using AEvent
    by (simp add: calculation)
qed

primrec length :: "'a fltrace \<Rightarrow> nat" where
"length \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L> = 0" |
"length (x #\<^sub>\<F>\<^sub>\<L> xs) = 1 + (length xs)"

lemma last_bullet_butlast:
  assumes "last xs = \<bullet>"
  shows "butlast(xs &\<^sub>\<F>\<^sub>\<L> \<langle>x1a,last x\<rangle>\<^sub>\<F>\<^sub>\<L>) = xs &\<^sub>\<F>\<^sub>\<L> \<langle>x1a,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  using assms by(induct xs, auto)

lemma last_bullet_butlast_last:
  assumes "last xs = \<bullet>"
  shows "butlast(xs &\<^sub>\<F>\<^sub>\<L> \<langle>last x\<rangle>\<^sub>\<F>\<^sub>\<L>) = xs"
  using assms by(induct xs, auto)

lemma length_cons:
  assumes "last xs = \<bullet>"
  shows "length(xs &\<^sub>\<F>\<^sub>\<L> ys) = length(xs) + length(ys)"
  using assms by (induct xs, auto)

lemma last_not_bullet_then_last_cons [simp]:
  assumes "last(xs) \<noteq> \<bullet>"
  shows "last (xs &\<^sub>\<F>\<^sub>\<L> \<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L>) = last (xs)"
  using assms apply (induct xs, auto)
  by (case_tac x, auto, case_tac y, auto)

lemma last_bullet_then_last_cons [simp]:
  assumes "last(xs) = \<bullet>"
  shows "last (xs &\<^sub>\<F>\<^sub>\<L> \<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L>) = last (\<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L>)"
  using assms by (induct xs, auto)

lemma last_bullet_concatmix:
  assumes "last xs = \<bullet>"
  shows "(xs &\<^sub>\<F>\<^sub>\<L> \<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L>) @\<^sub>\<F>\<^sub>\<L> \<langle>z,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> = (xs &\<^sub>\<F>\<^sub>\<L> (\<langle>y\<rangle>\<^sub>\<F>\<^sub>\<L> @\<^sub>\<F>\<^sub>\<L> \<langle>z,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>))"
  using assms by (induct xs, auto)

lemma xs_less_eq_z_two_only:
  assumes "xs &\<^sub>\<F>\<^sub>\<L> \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> \<le> z" "z \<le> xs &\<^sub>\<F>\<^sub>\<L> \<langle>(\<bullet>,tock)\<^sub>\<F>\<^sub>\<L>,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  shows "z = xs &\<^sub>\<F>\<^sub>\<L> \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> \<or> z = xs &\<^sub>\<F>\<^sub>\<L> \<langle>(\<bullet>,tock)\<^sub>\<F>\<^sub>\<L>,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  using assms
  apply (auto)
  apply (induct xs z rule:less_eq_fltrace.induct, auto)
  apply (metis acceptance_set fltrace_concat2.simps(3) less_eq_acceptance.elims(2) less_eq_fltrace.simps(2))
  apply (case_tac x, auto)
   apply (simp add: eq_iff less_eq_aevent_def)
  by (case_tac ys, auto, case_tac x1, auto)

section \<open> Finite Linear Traces \<close>

lemma last_cons_acceptance_not_bullet[simp]:
  "last (ys &\<^sub>\<F>\<^sub>\<L> \<langle>[x2]\<^sub>\<F>\<^sub>\<L>\<rangle>\<^sub>\<F>\<^sub>\<L>) \<noteq> \<bullet>"
  apply (induct ys, auto)
  by (case_tac x, auto)

lemma last_cons_bullet_iff:
  "last (xs &\<^sub>\<F>\<^sub>\<L> \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = \<bullet> \<longleftrightarrow> last(xs) = \<bullet>"
  apply (induct xs, auto)
  by (case_tac xa, auto)

lemma fl_cons_acceptance_consFL:
  "fl @\<^sub>\<F>\<^sub>\<L> \<langle>e,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> = butlast fl &\<^sub>\<F>\<^sub>\<L> \<langle>e,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  by (induct fl, auto)

lemma strong_less_eq_fltrace_cons_imp_lhs:
  assumes "(xs &\<^sub>\<F>\<^sub>\<L> \<langle>x\<rangle>\<^sub>\<F>\<^sub>\<L>) \<le>\<^sub>\<F>\<^sub>\<L> t"
  shows "xs \<le>\<^sub>\<F>\<^sub>\<L> t"
  using assms apply (induct xs t rule:strong_less_eq_fltrace.induct, auto)
  apply (cases x, auto)
  by (case_tac xa, auto)

lemma strong_less_eq_fltrace_eq_length:
  assumes "s \<le>\<^sub>\<F>\<^sub>\<L> t"
  shows "length s = length t"
  using assms
  by (induct s t rule:strong_less_eq_fltrace.induct, auto)

lemma strong_less_eq_fltrace_cons_last_less_eq:
  assumes "last xs = \<bullet>" "last ys = \<bullet>"
          "(xs &\<^sub>\<F>\<^sub>\<L> \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) \<le>\<^sub>\<F>\<^sub>\<L> (ys &\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>)"
    shows "x \<le> y"
  using assms apply(induct xs ys rule:strong_less_eq_fltrace.induct, auto)
  apply (metis fltrace.distinct(1) rev3.simps(1) rev3_little_more strong_less_eq_fltrace.elims(2))
  by (metis fltrace.distinct(1) rev3.simps(1) rev3_little_more strong_less_eq_fltrace.elims(2))

lemma strong_less_eq_fltrace_cons_iff_lhs_bullet:
  assumes "x \<le> y"
  shows "(xs &\<^sub>\<F>\<^sub>\<L> \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) \<le>\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> \<longleftrightarrow> xs = \<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  using assms apply (induct xs, auto)
   apply (case_tac xa, auto)
  apply (case_tac xs, auto)
  by (case_tac x1, auto)

lemma concat_FL_last_not_bullet_absorb:
  assumes "last xs \<noteq> \<bullet>"
  shows "xs &\<^sub>\<F>\<^sub>\<L> ys = xs"
  using assms
  by (metis Finite_Linear_Model.last.simps(1)  butlast_last_cons2_FL fltrace_concat2.simps(2) fltrace_concat2.simps(4) fltrace_concat2_assoc plus_acceptance.elims)

lemma strong_less_eq_fltrace_last_cons_bullet_imp_le:
  assumes "(xs &\<^sub>\<F>\<^sub>\<L> \<langle>x,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) \<le>\<^sub>\<F>\<^sub>\<L> (ys &\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>)"
          "last (ys &\<^sub>\<F>\<^sub>\<L> \<langle>y,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>) = \<bullet>"
  shows "x \<le> y"
  using assms apply(induct xs ys rule:strong_less_eq_fltrace.induct, auto)
  apply (metis Finite_Linear_Model.last.simps(1) concat_FL_last_not_bullet_absorb fltrace_concat2.simps(3)  strong_less_eq_fltrace.simps(2) strong_less_eq_fltrace.simps(3))
  apply (metis (no_types, lifting) FL_concat_equiv  concat_FL_last_not_bullet_absorb fltrace_concat2_assoc last_cons_bullet_iff last_rev3_cons2_is_last_cons rev3.simps(1) rev_little_cons2 strong_less_eq_fltrace.simps(2) strong_less_eq_fltrace_cons_last_less_eq)
  by (metis (no_types, lifting) Finite_Linear_Model.last.simps(1) antisym_conv fltrace.distinct(1) fltrace.inject(2) fltrace_concat2.simps(3) last_cons_bullet_iff rev3.simps(1) rev3_little_more rev3_rev3_const2_last strong_less_eq_fltrace.elims(2) strongFL_imp_less_eq x_le_x_concat2)

text \<open> The set of finite linear traces is fltraces. \<close>

type_synonym 'e fltraces = "('e fltrace) set"

subsection \<open> Healthiness Conditions \<close>

subsubsection \<open> FL0 \<close>

text \<open> The set of fltraces is non-empty. \<close>

definition FL0 :: "'a fltraces \<Rightarrow> bool" where
"FL0 P \<equiv> P \<noteq> {}"

subsubsection \<open> FL1 \<close>

text \<open> The set of fltraces is prefix-closed according to the order over {@type fltrace}. \<close>

definition FL1 :: "'a fltraces \<Rightarrow> bool" where
"FL1 P \<equiv> \<forall>s t. (t \<in> P \<and> s \<le> t) \<longrightarrow> s \<in> P"

definition mkFL1 :: "'a fltraces \<Rightarrow> 'a fltraces" where
"mkFL1 P \<equiv> P \<union> {s. \<exists>z. z \<in> P \<and> s \<le> z}"

lemma mkFL1_disj_univ: "mkFL1 (\<Union> P) = \<Union> ({mkFL1(x)|x. x \<in> P})"
  unfolding mkFL1_def by auto

lemma FL1_fixpoint: "FL1 P \<longleftrightarrow> mkFL1 P = P"
  unfolding FL1_def mkFL1_def by auto

subsubsection \<open> FL2 \<close>

text \<open> If an event a is offered in an acceptance A after a trace \<B> then it is also possible
       to find a pair (A,a) after \<B> also in P. \<close>

definition FL2 :: "'a fltraces \<Rightarrow> bool" where
"FL2 P \<equiv> \<forall>\<beta> A a. \<beta> &\<^sub>\<F>\<^sub>\<L> \<langle>A\<rangle>\<^sub>\<F>\<^sub>\<L> \<in> P \<and> a \<in>\<^sub>\<F>\<^sub>\<L> A \<longrightarrow> \<beta> &\<^sub>\<F>\<^sub>\<L> \<langle>(A,a)\<^sub>\<F>\<^sub>\<L>,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> \<in> P"

(*{t. \<exists>A B \<alpha> \<B>. (t = (\<langle>A \<union>\<^sub>\<F>\<^sub>\<L> B\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<alpha>) \<or> t = (\<langle>A \<union>\<^sub>\<F>\<^sub>\<L> B\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<B>)) \<and> \<langle>A\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<alpha> \<in> P \<and> \<langle>A\<rangle>\<^sub>\<F>\<^sub>\<L> &\<^sub>\<F>\<^sub>\<L> \<B> \<in> Q}" *)

lemma FL0_FL1_bullet_in [simp]:
  assumes "FL0 A" "FL1 A"
  shows "\<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> \<in> A"
  using assms unfolding FL0_def FL1_def apply simp
  by (metis all_not_in_conv fltrace.exhaust less_eq_acceptance.simps(1) less_eq_fltrace.simps(1) less_eq_fltrace.simps(2))

lemma FL0_FL1_bullet_in_so [simp]:
  assumes "FL1 P" "x \<in> P"
  shows "\<langle>\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> \<in> P"
  using FL0_FL1_bullet_in FL0_def assms(1) assms(2) by blast

lemma FL_cons_acceptance:
  assumes "FL1 A" "Aa #\<^sub>\<F>\<^sub>\<L> fl \<in> A"
  shows "\<langle>acceptance(Aa)\<rangle>\<^sub>\<F>\<^sub>\<L> \<in> A"
  using assms unfolding FL1_def apply auto
  by (meson dual_order.refl less_eq_fltrace.simps(2))

lemma x_le_concat2_FL1 [simp]:
  assumes "x &\<^sub>\<F>\<^sub>\<L> y \<in> P" "FL1 P"
  shows "x \<in> P"
  using assms x_le_x_concat2
  using FL1_def by blast

(*
lemma ExtChoiceH_emptyset:
  assumes "ExtChoiceH \<langle>[{}]\<^sub>\<F>\<^sub>\<L>\<rangle>\<^sub>\<F>\<^sub>\<L> B x" "B \<in> P"
  shows "x \<in> P"
  using assms by (cases B, auto, case_tac x1, auto)
*)

lemma bullet_event_acceptance [simp]:
  "(\<bullet>,event A)\<^sub>\<F>\<^sub>\<L> \<le> A"
  apply (cases A, auto, case_tac a, auto)
  by (simp add: less_eq_aevent_def)

lemma
  assumes "b \<in>\<^sub>\<F>\<^sub>\<L> [x2]\<^sub>\<F>\<^sub>\<L>"
  shows "\<langle>[x2]\<^sub>\<F>\<^sub>\<L>\<rangle>\<^sub>\<F>\<^sub>\<L> \<le> \<langle>([x2]\<^sub>\<F>\<^sub>\<L>,b)\<^sub>\<F>\<^sub>\<L>,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L>"
  using assms by auto

lemma aevent_less_eq_FL1:
  assumes "([x2]\<^sub>\<F>\<^sub>\<L>,b)\<^sub>\<F>\<^sub>\<L> #\<^sub>\<F>\<^sub>\<L> x22 \<in> P" "FL1 P" "b \<in>\<^sub>\<F>\<^sub>\<L> [x2]\<^sub>\<F>\<^sub>\<L>"
  shows "\<langle>[x2]\<^sub>\<F>\<^sub>\<L>\<rangle>\<^sub>\<F>\<^sub>\<L> \<in> P"
  using assms
  apply (induct x22, auto, case_tac x, auto)
  using FL_cons_acceptance by fastforce+

lemma fltrace_acceptance_FL1:
  assumes "A #\<^sub>\<F>\<^sub>\<L> xs \<in> P" "FL1 P"
  shows "\<langle>A,\<bullet>\<rangle>\<^sub>\<F>\<^sub>\<L> \<in> P"
  using assms by (induct xs, auto)

lemma fltrace_acceptance_FL1_less_eq:
  assumes "A #\<^sub>\<F>\<^sub>\<L> xs \<in> P" "FL1 P"
          "x \<le> A"
    shows "x #\<^sub>\<F>\<^sub>\<L> xs \<in> P"
  using assms
  apply (cases A, auto, cases x, auto)
  by (meson FL1_def eq_iff less_eq_fltrace.simps(3))+

lemma acceptance_bullet_event_FL1:
  assumes "A #\<^sub>\<F>\<^sub>\<L> xs \<in> P" "FL1 P"
  shows "(\<bullet>,event A)\<^sub>\<F>\<^sub>\<L> #\<^sub>\<F>\<^sub>\<L> xs \<in> P"
  using assms
  apply (cases A, auto)
  by (metis acceptance_event bullet_event_acceptance fltrace_acceptance_FL1_less_eq)

subsection \<open> Refinement \<close>

abbreviation Refines :: "'e fltraces \<Rightarrow> 'e fltraces \<Rightarrow> bool" (infixl "\<sqsubseteq>\<^sub>\<F>\<^sub>\<L>" 65) where
"P \<sqsubseteq>\<^sub>\<F>\<^sub>\<L> Q \<equiv> Q \<subseteq> P"

end


section \<open>Tests for Continued Fractions of Square Roots and Pell's Equation\<close>
theory Pell_Continued_Fraction_Tests
imports
  Pell.Efficient_Discrete_Sqrt
  "HOL-Library.Code_Lazy"
  "HOL-Library.Code_Target_Numeral"
  Pell_Continued_Fraction
  Pell_Lifting
begin

code_lazy_type stream

(* TODO Move *)
lemma lnth_code [code]:
 "lnth xs 0 = (if lnull xs then undefined (0 :: nat) else lhd xs)"
 "lnth xs (Suc n) = (if lnull xs then undefined (Suc n) else lnth (ltl xs) n)"
  by (auto simp: lnth.simps split: llist.splits)

value "let c = sqrt_cfrac 1339 in map (cfrac_nth c) [0..<30]"

fun arg_max_list where
  "arg_max_list _ [] = undefined"
| "arg_max_list f (x # xs) =
     foldl (\<lambda>(x, y) x'. let y' = f x' in if y' > y then (x', y') else (x, y)) (x, f x) xs"

value [code] "sqrt_cfrac_info 17"
value [code] "sqrt_cfrac_info 1339"
value [code] "sqrt_cfrac_info 121"
value [code] "sqrt_nat_period_length 410286423278424"

text \<open>
  For which number $D < 100000$ does $\sqrt{D}$ have the longest period?
\<close>
value [code] "arg_max_list sqrt_nat_period_length [0..<100000]"

subsection \<open>Fundamental solutions of Pell's equation\<close>

value [code] "pell.fund_sol 12"
value [code] "pell.fund_sol 13"
value [code] "pell.fund_sol 61"
value [code] "pell.fund_sol 661"
value [code] "pell.fund_sol 6661"
value [code] "pell.fund_sol 4729494"

text \<open>
  Project Euler problem \#66:
  For which $D < 1000$ does Pell's equation have the largest fundamental solution?
\<close>

value [code] "arg_max_list (fst \<circ> find_fund_sol) [0..<1001]"

text \<open>
  The same for $D < 100000$:
\<close>

value [code] "arg_max_list (fst \<circ> find_fund_sol) [0..<100000]"

text \<open>
  The solution to the next example, which is at the core of Archimedes' cattle problem,
  is so big that termifying the result takes extremely long.
  Therefore, we simply compute the number of decimal digits in the result instead.
\<close>

fun log10_aux :: "nat \<Rightarrow> nat \<Rightarrow> nat" where
  "log10_aux acc n =
     (if n \<ge> 10000000000 then log10_aux (acc + 10) (n div 10000000000)
      else if n = 0 then acc else log10_aux (Suc acc) (n div 10))"

definition log10 where "log10 = log10_aux 0"

value [code] "map_prod log10 log10 (pell.fund_sol 410286423278424)"

text \<open>
  Factoring out the square factor \<open>9314\<^sup>2\<close> does yield a significant speed-up in this case:
\<close>
value [code] "map_prod log10 log10 (find_fund_sol_fast 410286423278424)"

subsection \<open>Tests for other operations\<close>

value [code] "pell.nth_solution 13 100"
value [code] "pell.nth_solution 4729494 3"

value [code] "stake 10 (pell_solutions 13)"
value [code] "stake 10 (pell_solutions 61)"

value [code] "pell.nth_solution 23 8"

end