Require Import List ListDec PeanoNat Lia Relation_Operators Operators_Properties.
Import ListNotations.
Require Import Undecidability.CounterMachines.CM2.
From Undecidability.CounterMachines.Util Require Import Facts CM2_facts.
Require Import ssreflect ssrbool ssrfun.

Set Default Goal Selector "!".
Set Default Proof Using "Type".

Section Construction.
Variable M : .

#[local] Notation steps := (CM2.steps M).
#[local] Notation bounded := (CM2.bounded M).
#[local] Notation step := (CM2.step M).
#[local] Notation reaches := (CM2.reaches M).
#[local] Notation path := (CM2_facts.path M).

Lemma pointwise_decision K x : {bounded K x} + {not (bounded K x)}.
Proof.
  case HK: (steps K x) [y|].
  - have [Hy|Hy] := In_dec option_Config_eq_dec (Some y) (path K x).
    + left. apply: loop_bounded. by rewrite HK.
    + right. apply: (NoDup_not_bounded HK).
      apply: path_noloopI. by rewrite HK.
  - left. by apply: mortal_bounded.
Qed.


Lemma shift_steps_a k K p a b :
  k K
  steps k (p, (K + a, b)) =
  match steps k (p, (K, b)) with
  | Some (p', (a', b')) Some (p', (a' + a, b'))
  | None None
  end.
Proof.
  move ?. rewrite [LHS]shift_steps [in RHS]shift_steps.
  have : Nat.min k (K + a) = k by .
  have : Nat.min k K = k by .
  case: (steps k _) [[? [? ?]]|]; last done.
  congr (Some (_, (_, _))); .
Qed.


Lemma shift_steps_b k K p a b :
  k K
  steps k (p, (a, K + b)) =
  match steps k (p, (a, K)) with
  | Some (p', (a', b')) Some (p', (a', b' + b))
  | None None
  end.
Proof.
  move ?. rewrite [LHS]shift_steps [in RHS]shift_steps.
  have : Nat.min k (K + b) = k by .
  have : Nat.min k K = k by .
  case: (steps k _) [[? [? ?]]|]; last done.
  congr (Some (_, (_, _))); .
Qed.


Lemma shift_path_a K p a b :
  path (K+1) (p, (K+a, b)) =
  map ( oy if oy is Some (p', (a', b')) then Some (p', (a'+a, b')) else None) (path (K+1) (p, (K, b))).
Proof.
  rewrite /path map_map. apply: map_ext_in k /in_seq ?.
  apply: shift_steps_a. .
Qed.


Lemma shift_path_b K p a b :
  path (K+1) (p, (a, K+b)) =
  map ( oy if oy is Some (p', (a', b')) then Some (p', (a', b'+b)) else None) (path (K+1) (p, (a, K))).
Proof.
  rewrite /path map_map. apply: map_ext_in k /in_seq ?.
  apply: shift_steps_b. .
Qed.


Lemma path_NoDup_a_bound K p a b : K a NoDup (path (K+1) (p, (a, b))) NoDup (path (K+1) (p, (K, b))).
Proof.
  move ?. have : a = K+(a-K) by .
  rewrite shift_path_a. by apply: NoDup_map_inv.
Qed.


Lemma path_NoDup_b_bound K p a b : K b NoDup (path (K+1) (p, (a, b))) NoDup (path (K+1) (p, (a, K))).
Proof.
  move ?. have : b = K+(b-K) by .
  rewrite shift_path_b. by apply: NoDup_map_inv.
Qed.


Lemma fixed_decision K :
  { x : Config, bounded K x} + { ( x : Config, bounded K x)}.
Proof.
  have := Forall_dec ( 'x bounded K x) (pointwise_decision K)
    (list_prod (seq 0 (length M)) (list_prod (seq 0 (K+1)) (seq 0 (K+1)))).
  case; first last.
  { move H. right HK. apply /H /Forall_forall. move ??. by apply: HK. }
  wlog: K /(0 < K).
  { move: K [|K] H HK.
    - right. move /(_ (0, (0, 0))) [[|? L] [/= ? /(_ (0, (0, 0))) HL]].
      + apply: HL. by exists 0.
      + .
    - apply: H; [|done]. }
  move ? HK. left x.
  have [|/path_noloopI] := In_dec option_Config_eq_dec (steps K x) (path K x).
  { by apply: loop_bounded. }
  case Hz: (steps K x) [z|]; first last.
  { move _. by apply: mortal_bounded. }
  
  move Hx. exfalso.
  move: x Hx Hz [p [a b]] Hx Hz.
  have Hp : not (length M p).
  { move /nth_error_None HM. move: Hz. have : K = S (K - 1) by .
    by rewrite /steps /= obind_oiter /step /= HM oiter_None. }
  move: Hx Hz.
  wlog: a b z /(a K b K); first last.
  { move ? Hx Hz. suff: bounded K (p, (a, b)).
    { by apply: (NoDup_not_bounded Hz). }
    move: HK /Forall_forall.
    apply. apply: in_prod; [|apply: in_prod]; apply /in_seq; . }
  move .
  wlog: a b z /(a K).
  { move H. have [/H|] : a K a = K + (a - K) by .
    { by apply. }
    move /path_NoDup_a_bound /(_ ltac:()) /H {H}.
    rewrite shift_steps_a; first done.
    case: (steps K (p, (K, b))) [y|]; last done.
    move H _. by apply: (H y). }
  move ?. have [?|] : b K b = K + (b - K) by .
  { move / H /H. by apply. }
  move /path_NoDup_b_bound /(_ ltac:()) /.
  rewrite shift_steps_b; first done.
  case: (steps K (p, (a, K))) [y|]; last done.
  move H _. by apply: (H y).
Qed.


Notation l := (length M).

Lemma uniform_decision_aux1 x : let n := l*(l+1) in
  (bounded (l*n*n+1) x) +
  { y | ( k, k l*n*n steps k x = Some y) (n y n y) }.
Proof.
  move n.
  have [|/path_noloopI Hx] :=
    In_dec option_Config_eq_dec (steps (l*n*n) x) (path (l*n*n) x).
  { move /loop_bounded H. left. apply: (bounded_monotone _ H). . }
  case Hxy: (steps (l*n*n+1) x) [y|]; first last.
  { left. by apply: mortal_bounded. }
  right.
  have [/pigeonhole|] := incl_dec option_Config_eq_dec (path (l*n*n+1) x)
    (map Some (list_prod (seq 0 l) (list_prod (seq 0 n) (seq 0 n)))).
  { move H. exfalso. apply: (H _ Hx).
    rewrite path_length map_length ?prod_length ?seq_length. . }
  move /(not_inclE option_Config_eq_dec) [[z|]].
  - move H. exists z.
    move: H [/in_map_iff] [k] [Hk] /in_seq ? H.
    have : not (l state z).
    { move /nth_error_None Hz.
      have : steps (S k) x = None by rewrite /= Hk /= /step Hz.
      move /(steps_k_monotone (l*n*n+1)) /(_ ltac:()).
      by rewrite Hxy. }
    split. { exists k. split; [|done]. }
    suff : not ( z < n z < n) by .
    move H'. apply: H. apply /in_map_iff. exists z. split; first done.
    move: z {Hk} H' [? [? ?]] /= ??.
    apply /in_prod; [|apply /in_prod]; apply /in_seq; .
  - move [/in_map_iff] H. exfalso.
    move: H [k] [+ /in_seq ?].
    move /(steps_k_monotone (l*n*n+1)) /(_ ltac:()).
    by rewrite Hxy.
Qed.


Lemma k_step_iter k p a1 b1 a2 b2 :
  (k = ) (k = )
  steps k (p, (, )) = Some (p, (, ))
  let ca := if Nat.eq_dec then 0 else 1 in
  let cb := if Nat.eq_dec then 0 else 1 in
   i n a b, i n
    steps (i*k) (p, (+ca*(a+n*),+cb*(b+n*))) =
      Some (p, (+ca*(a+(n-i)*+i*),+cb*(b+(n-i)*+i*))).
Proof.
  move Ha Hb Hk ca cb. elim.
  { move ????. congr (Some (_, (_, _))); . }
  move i IH [|n] a b; first by .
  move /(iffRL (Nat.succ_le_mono _ _)) /IH {}IH.
  rewrite /= /steps iter_plus -/(steps _ _).
  have := IH (+a) (+b). congr eq; first last.
  { congr (Some (_, (_, _))); . }
  congr Nat.iter.
  rewrite /ca /cb.
  move: (Nat.eq_dec ) (Nat.eq_dec ) [?|?] [?|?] /=.
  - rewrite ?Nat.add_0_r Hk.
    congr (Some (_, (_, _))); .
  - rewrite shift_steps_b; first .
    rewrite ?Nat.add_0_r Hk.
    congr (Some (_, (_, _))); .
  - rewrite shift_steps_a; first .
    rewrite ?Nat.add_0_r Hk.
    congr (Some (_, (_, _))); .
  - rewrite shift_steps_a; first .
    rewrite shift_steps_b; first .
    rewrite ?Nat.add_0_r Hk.
    congr (Some (_, (_, _))); .
Qed.


Lemma not_uniformly_boundedI k p a1 b1 a2 b2 :
  (k = ) (k = )
  ( )
  steps k (p, (, )) = Some (p, (, ))
  not (uniformly_bounded M).
Proof.
  move /k_step_iter H /H {}H Hab /H /= + [K].
  move /(_ _ K 0 0) /=.
  move Ha: ( + _ * _) a.
  move Hb: ( + _ * _) b.
  move {}H /(_ (p, (a, b))) [L [? HL]].
  have : incl (map ( i steps (i * k) (p, (a, b))) (seq 0 (K+1))) (map Some L).
  { move z /in_map_iff [i] [ /in_seq ?]. rewrite H; first by .
    apply: in_map. apply: HL. exists (i*k). rewrite H; [|done]. }
  move /pigeonhole. apply.
  { rewrite ?map_length seq_length. . }
  under map_ext_in.
  { move i /in_seq ?. rewrite H; first by . over. }
  apply: NoDup_map_ext; last by apply: seq_NoDup.
  move /in_seq ? /in_seq ? [].
  move: (Nat.eq_dec ) (Nat.eq_dec ) [?|?] [?|?] /=; nia.
Qed.


Lemma uniform_decision_aux2 x : ({l*(l+1) x} + {l*(l+1) x})
  (bounded (l*l+1) x) + (not (uniformly_bounded M)) +
    { y | ( k, k l*l steps k x = Some y) (l y l y) }.
Proof.
  move Hlx.
  have [|/path_noloopI Hx] :=
    In_dec option_Config_eq_dec (steps (l*l) x) (path (l*l) x).
  { move /loop_bounded H. left. left. apply: (bounded_monotone _ H). . }
  case Hxy: (steps (l*l+1) x) [y|]; first last.
  { left. left. by apply: mortal_bounded. }
  pose P oz := if oz is Some z then
    (if Hlx then l z else l z) else True.
  have HP : x, {P x} + {not (P x)}.
  { move [? /= |]; last by left. clear P.
    case: Hlx ?; by apply: Compare_dec.le_dec. }
  have [|] := Exists_dec P (path (l * l + 1) x) HP.
  { move /(Exists_sig P HP) [[z|]] [Hz /= ].
    - right. exists z. move: Hz /In_pathE [k [? Hk]]. split.
      + exists k. split; [|done].
      + move: Hk /steps_values_bound. clear P HP.
        case: Hlx /=; .
    - exfalso. by move: Hz Hxy /In_None_pathE . }
  
  left. right. subst P.
  have /pigeonhole : incl
    (map ( oz if oz is Some (p, (a, b)) then (if Hlx then (p, b) else (p, a)) else (0, 0)) (path (l * l + 1) x))
    (list_prod (seq 0 l) (seq 0 l)).
  { move [p c] /in_map_iff [[[p' [a' b']]|]]; first last.
    { move [_ /In_pathE].
      move [?] [?] /(steps_k_monotone (l * l + 1)) /(_ ltac:()).
      by rewrite Hxy. }
    move [+ H]. have ? : not (l p').
    { move /nth_error_None Hp. move: H /in_map_iff [k] [Hk /in_seq ?].
      have : steps (S k) x = None by rewrite /= Hk /step /= Hp.
      move /(steps_k_monotone (l*l+1)) /(_ ltac:()).
      by rewrite Hxy. }
    case: Hlx HP /= ? HP.
    - move [? ?]. subst p c.
      move: H /Forall_Exists_neg /Forall_forall H /H{H} /= ?.
      apply /in_prod; apply /in_seq; .
    - move [? ?]. subst p c.
      move: H /Forall_Exists_neg /Forall_forall H /H{H} /= ?.
      apply /in_prod; apply /in_seq; . }
  apply: unnest. { rewrite map_length ?prod_length ?seq_length path_length. . }
  rewrite /path map_map. move /(dup_seq prod_nat_nat_eq_dec) [[i j]].
  move [+ ?].
  case Hi: (steps i x) [[p [ ]]|]; first last.
  { move: Hi /(steps_k_monotone (l*l+1)) /(_ ltac:()). by rewrite Hxy. }
  case Hj: (steps j x) [[p' [ ]]|]; first last.
  { move: Hj /(steps_k_monotone (l*l+1)) /(_ ltac:()). by rewrite Hxy. }
  clear .
  have ? : not (p = p' = = ).
  { move [? [? ?]]. subst p' .
    move: Hx. rewrite /path.
    have : l*l+1 = i + (S (j-i-1)) + (S (l*l -j)) by .
    rewrite seq_app seq_app /= ?map_app /= (NoDup_count_occ option_Config_eq_dec).
    move /(_ (Some (p, (, )))). have : i + S (j - i - 1) = j by .
    rewrite Hi Hj ?count_occ_app /=. case: (option_Config_eq_dec _ _); [|done]. }
  case: Hlx HP /= ? HP.
  - move [? ?]. subst p' .
    have ? : by .
    have ? : j-i .
    { move: Hi /steps_values_bound /=. . }
    move: Hi Hj /steps_sub H /H{H} /(_ ltac:()).
    move /not_uniformly_boundedI. apply; .
  - move [? ?]. subst p' .
    have ? : by .
    have ? : j-i .
    { move: Hi /steps_values_bound /=. . }
    move: Hi Hj /steps_sub H /H{H} /(_ ltac:()).
    move /not_uniformly_boundedI. apply; .
Qed.


Lemma uniform_decision_aux3 x : l x l x (bounded (l+1) x) + (not (uniformly_bounded M)).
Proof.
  move ??.
  have [|/path_noloopI Hx] :=
    In_dec option_Config_eq_dec (steps (l) x) (path (l) x).
  { move /loop_bounded H. left. apply: (bounded_monotone _ H). . }
  case Hxy: (steps (l+1) x) [y|]; first last.
  { left. by apply: mortal_bounded. }
  right.   have /pigeonhole : incl
    (map ( oz if oz is Some (p, (a, b)) then p else 0) (path (l + 1) x))
    (seq 0 l).
  { move p /in_map_iff [[[p' [a' b']]|]]; first last.
    { move [_ /In_pathE].
      move [?] [?] /(steps_k_monotone (l + 1)) /(_ ltac:()).
      by rewrite Hxy. }
    move [] H. apply /in_seq. suff : not (l p) by .
    move /nth_error_None Hp. move: H /in_map_iff [k] [Hk /in_seq ?].
    have : steps (S k) x = None by rewrite /= Hk /step /= Hp.
    move /(steps_k_monotone (l+1)) /(_ ltac:()).
    by rewrite Hxy. }
  apply: unnest. { rewrite map_length ?seq_length path_length. . }
  rewrite /path map_map. move /(dup_seq Nat.eq_dec) [[i j]].
  move [+ ?].
  case Hi: (steps i x) [[p [ ]]|]; first last.
  { move: Hi /(steps_k_monotone (l+1)) /(_ ltac:()). by rewrite Hxy. }
  case Hj: (steps j x) [[p' [ ]]|]; first last.
  { move: Hj /(steps_k_monotone (l+1)) /(_ ltac:()). by rewrite Hxy. }
  move ?. subst p'.
  have ? : .
  { suff : not ( = = ) by . move [??]. subst .
    move: Hx. rewrite /path.
    have : l+1 = i + (S (j-i-1)) + (S (l -j)) by .
    rewrite seq_app seq_app /= ?map_app /= (NoDup_count_occ option_Config_eq_dec).
    move /(_ (Some (p, (, )))). have : i + S (j - i - 1) = j by .
    rewrite Hi Hj ?count_occ_app /=. case: (option_Config_eq_dec _ _); [|done]. }
  have ?: j - i j - i .
  { move: Hi /steps_values_bound /=. . }
  move: Hi Hj /steps_sub H /H{H} /(_ ltac:()).
  move /not_uniformly_boundedI. apply; .
Qed.


Lemma uniformly_bounded_empty : uniformly_bounded [].
Proof.
  exists 1. move x. exists [x]. split; first done.
  move y [[|k]].
  - move []. by left.
  - rewrite /= obind_oiter /CM2.step.
    by case: (state x); rewrite /= oiter_None.
Qed.


Lemma bound_on_uniform_bound : uniformly_bounded M
   x, bounded ((l+1)*(l+1)*(l+1)*(l+1)*(l+1)) x.
Proof.
  move HM x.
  set K := ((l+1)*(l+1)*(l+1)*(l+1)*(l+1)).
  have [|[y [[ [ Hxy]] ?]]] := uniform_decision_aux1 x.
  { apply: bounded_monotone. . }
  have : K = + (K - ) by .
  apply: (reaches_bounded Hxy).
  have : {l * (l + 1) y} + {l * (l + 1) y}.
  { case H: (l * (l + 1) - y) [|?]; [left|right]; . }
  move /uniform_decision_aux2 [[|]|[z]].
  - apply: bounded_monotone. .
  - by move /(_ HM).
  - move [[ [? Hyz]]] [/uniform_decision_aux3 H /H{H}].
    move [/bounded_monotone Hz|].
    + have : K - = + (K - - ) by .
      apply: (reaches_bounded Hyz). apply: Hz. .
    + by move /(_ HM).
Qed.


Theorem decision : (uniformly_bounded M) + (not (uniformly_bounded M)).
Proof.
  wlog ? : /(l > 0).
  { move: (M) [|? ?] /=.
    - move _. left. by apply: uniformly_bounded_empty.
    - apply. . }
  pose K := (l+1)*(l+1)*(l+1)*(l+1)*(l+1).
  have [?|HK] := fixed_decision K. { left. by exists K. }
  
  right. move /bound_on_uniform_bound HM.
  apply: HK x. by apply: HM.
Qed.


End Construction.

Require Import Undecidability.Synthetic.Definitions.

Definition decide : bool :=
   M
    match decision M with
    | inl _ true
    | inr _ false
    end.

Lemma decide_spec : decider decide CM2_UBOUNDED.
Proof.
  rewrite /decider /reflects /decide M.
  case: (decision M); intuition done.
Qed.