Require Import List ssrfun.

Definition Config : Set := * ( * ).

Definition state (x: Config) : := fst x.
Arguments state !x /.
Definition (x: Config) : := fst (snd x).
Arguments !x /.
Definition (x: Config) : := snd (snd x).
Arguments !x /.

Inductive Instruction : Set :=
  | halt : Instruction
  | zero : bool Instruction
  | inc : bool Instruction
  | dec : bool Instruction.

Definition : Set := list Instruction.

Definition step (M: ) (x: Config) : option Config :=
  match nth_error M (state x) with
  | None None
  | Some halt None
  | Some (zero b)
    Some (1 + (state x), ((if b then ( x) else 0), (if b then 0 else ( x))))
  | Some (inc b)
    Some (1 + (state x), ((if b then 0 else 1) + ( x), (if b then 1 else 0) + ( x)))
  | Some (dec b y)
    Some (
      if b then
        match x with
        | 0 (y, ( x, 0))
        | S n (1 + (state x), ( x, n))
        end
      else
        match x with
        | 0 (y, (0, x))
        | S n (1 + (state x), (n, x))
        end)
  end.

Definition steps (M: ) (n: ) (x: Config) : option Config :=
  Nat.iter n (obind (step M)) (Some x).

Definition terminating (M: ) (x: Config) :=
   n, steps M n x = None.

Definition MPM2_HALT : * Config Prop :=
   '(M, c) terminating M c.

Require Import List PeanoNat Lia Operators_Properties.
Import ListNotations.

Require Import ssreflect ssrbool ssrfun.

Set Default Goal Selector "!".

Module Facts.

Lemma list_sum_map_le {X: Type} f g (L: list X) :
  ( x, f x g x)
  list_sum (map f L) list_sum (map g L).
Proof.
  move Hfg. elim: L; first done.
  move x L IH /=. have := Hfg x. .
Qed.


Lemma list_sum_map_lt {X: Type} {f g} {L: list X} {x} :
  ( x, f x g x)
  In x L f x < g x
  list_sum (map f L) < list_sum (map g L).
Proof.
  move Hfg + . elim: L; first done.
  move y L IH /= [|].
  - have := list_sum_map_le f g L Hfg. .
  - move /IH. have := Hfg y. .
Qed.


Lemma oiter_None {X : Type} (f : X option X) k : Nat.iter k (obind f) None = None.
Proof. elim: k; [done | by move /= ? ]. Qed.

Lemma obind_oiter {X : Type} (f : X option X) k x :
  obind f (Nat.iter k (obind f) (Some x)) = Nat.iter k (obind f) (f x).
Proof. elim: k; [done|by move k /= ]. Qed.

End Facts.

Import Facts.

Section Construction.
Variable M : .

#[local] Notation step := (step M).
#[local] Notation steps := (steps M).
#[local] Notation terminating := (terminating M).
#[local] Notation l := (length M).

Lemma steps_bound {k p a b p' a' b'} : steps k (p, (a, b)) = Some (p', (a', b'))
  a' k + a b' k + b (a' k a k + a') (b' k b k + b').
Proof.
  elim: k p' a' b'.
  { move p' a' b' /= []. . }
  move k + p' a' b' /=.
  case: (steps k (p, (a, b))) [[p'' [a'' b'']]|]; last done.
  move /(_ p'' a'' b'' ltac:(done)) IH.
  rewrite /= /(step _).
  case: (nth_error M (state (p'', (a'', b'')))); last done.
  case.
  - done.
  - case; case; .
  - case; case; .
  - case q /=.
    + move: b'' IH [|b''] ? []; .
    + move: a'' IH [|a''] ? []; .
Qed.


Definition reaches (x y: Config) := k, steps k x = Some y.
Definition reaches_plus (x y: Config) := k, 0 < k steps k x = Some y.
Definition non_terminating x := k, steps k x None.

Lemma steps_plus {k x k' y} :
  steps k x = Some y steps (k + k') x = steps k' y.
Proof. by rewrite /steps Nat.add_comm /Nat.iter nat_rect_plus /= . Qed.

Lemma reaches_plus_reaches {x y z} : reaches_plus x y reaches y z reaches_plus x z.
Proof.
  move [k [? Hk]] [k' Hk']. exists (k+k'). split; first by .
  by rewrite (steps_plus Hk).
Qed.


Lemma reaches_reaches_plus {x y z} : reaches x y reaches_plus y z reaches_plus x z.
Proof.
  move [k Hk] [k' [? Hk']]. exists (k+k'). split; first by .
  by rewrite (steps_plus Hk).
Qed.


Lemma reaches_plus_incl {x y} : reaches_plus x y reaches x y.
Proof. move [k [? Hk]]. by exists k. Qed.

Lemma reaches_terminating {x y} : reaches x y terminating y terminating x.
Proof.
  move [k Hk] [k' Hk']. exists (k+k').
  by rewrite (steps_plus Hk).
Qed.


Lemma steps_k_monotone {k x} k' : steps k x = None k k' steps k' x = None.
Proof.
  move + ?. have : k' = (k' - k) + k by .
  elim: (k' - k); first done.
  by move ? IH /IH /= .
Qed.


Lemma reaches_non_terminating {x y} : reaches x y non_terminating y non_terminating x.
Proof.
  move [k Hk] Hy k'.
  have [|] : k' k k' = k + (k' - k) by .
  - by move: Hk + /steps_k_monotone H /H .
  - rewrite (steps_plus Hk). by apply: Hy.
Qed.


Lemma reaches_non_terminating' {x y} : reaches x y non_terminating x non_terminating y.
Proof.
  move [k' Hk'] Hx k Hk.
  apply: (Hx (k' + k)).
  by rewrite (steps_plus Hk').
Qed.


Lemma reaches_plus_state_bound {x y} : reaches_plus x y state x < l.
Proof.
  move [k [? Hk]].
  suff: not (l state x) by .
  move /nth_error_None Hx.
  move: Hk. have : k = S (k - 1) by .
  by rewrite /= obind_oiter /step Hx oiter_None.
Qed.


Lemma reaches_plus_trans {x y z} : reaches_plus x y reaches_plus y z reaches_plus x z.
Proof. by move /reaches_plus_incl /reaches_reaches_plus H /H. Qed.

Lemma reaches_trans {x y z} : reaches x y reaches y z reaches x z.
Proof. move [k Hk] [k' Hk']. exists (k+k'). by rewrite (steps_plus Hk). Qed.

Lemma next_waypoint p a b :
  terminating (p, (a, b)) +
  { '(p', a') | n, k, 0 < k l - p steps k (p, (n+a, b)) = Some (p', (n+a', 0)) } +
  { '(p', b') | n, k, 0 < k l - p steps k (p, (a, n+b)) = Some (p', (0, n+b')) }.
Proof.
  move Hn: (l - p) n. elim: n p Hn a b.
  { move p ? a b. left. left. exists 1. rewrite /= /step.
    by have : nth_error M (state (p, (a, b))) = None
      by (rewrite nth_error_None /=; ). }
  move n IH p ? a b.
  case Hi: (nth_error M (state (p, (a, b)))) [i|]; first last.
  { left. left. exists 1. by rewrite /= /step Hi. }
  move: i Hi /= [].
  - move Hi. left. left. exists 1. by rewrite /= /step Hi.
  - case.
    + move Hi. left. right. exists (S p, a).
      move n'. exists 1. split; first by .
      by rewrite /= /step /= Hi.
    + move Hi. right. exists (S p, b).
      move n'. exists 1. split; first by .
      by rewrite /= /step /= Hi.
  - case.
    + move Hi.
      have [[|[[p' a'] HSp]]|[[p' b'] HSp]] := IH (S p) ltac:() a (S b).
      * move /reaches_terminating HSp. left. left.
        apply: HSp. exists 1. by rewrite /= /step Hi.
      * left. right. exists (p', a').
        move n'.
        have [k [? ]] := (HSp n').
        exists (1+k). split; first by .
        apply: steps_plus. by rewrite /= /step Hi.
      * right. exists (p', b').
        move n'.
        have [k [? ]] := (HSp n').
        exists (1+k). split; first by .
        apply: steps_plus. by rewrite /= /step Hi Nat.add_succ_r.
    + move Hi.
      have [[|[[p' a'] HSp]]|[[p' b'] HSp]] := IH (S p) ltac:() (S a) b.
      * move /reaches_terminating HSp. left. left.
        apply: HSp. exists 1. by rewrite /= /step Hi.
      * left. right. exists (p', a').
        move n'.
        have [k [? ]] := (HSp n').
        exists (1+k). split; first by .
        apply: steps_plus. by rewrite /= /step Hi Nat.add_succ_r.
      * right. exists (p', b').
        move n'.
        have [k [? ]] := (HSp n').
        exists (1+k). split; first by .
        apply: steps_plus. by rewrite /= /step Hi.
  - case q Hi.
    + move: b [|b].
      { left. right. exists (q, a) n'. exists 1.
        split; first by . by rewrite /= /step Hi. }
      have [[|[[p' a'] HSp]]|[[p' b'] HSp]] := IH (S p) ltac:() a b.
      * move /reaches_terminating HSp. left. left.
        apply: HSp. exists 1. by rewrite /= /step Hi.
      * left. right. exists (p', a').
        move n'.
        have [k [? ]] := (HSp n').
        exists (1+k). split; first by .
        apply: steps_plus. by rewrite /= /step Hi.
      * right. exists (p', b').
        move n'.
        have [k [? ]] := (HSp n').
        exists (1+k). split; first by .
        apply: steps_plus. by rewrite /= /step Hi Nat.add_succ_r.
    + move: a [|a].
      { right. exists (q, b) n'. exists 1.
        split; first by . by rewrite /= /step Hi. }
      have [[|[[p' a'] HSp]]|[[p' b'] HSp]] := IH (S p) ltac:() a b.
      * move /reaches_terminating HSp. left. left.
        apply: HSp. exists 1. by rewrite /= /step Hi.
      * left. right. exists (p', a').
        move n'.
        have [k [? ]] := (HSp n').
        exists (1+k). split; first by .
        apply: steps_plus. by rewrite /= /step Hi Nat.add_succ_r.
      * right. exists (p', b').
        move n'.
        have [k [? ]] := (HSp n').
        exists (1+k). split; first by .
        apply: steps_plus. by rewrite /= /step Hi.
Qed.


Corollary next_waypoint_a p a :
  terminating (p, (a, 0)) +
  { '(p', a') | n, reaches_plus (p, (n+a, 0)) (p', (n+a', 0)) } +
  { '(p', (a', b')) | reaches (p, (a, 0)) (p', (a', b')) a' l b' l }.
Proof.
  have [[?|[[p' a'] Hp]]|[[p' b'] Hp]] := next_waypoint p a 0.
  - left. by left.
  - left. right. exists (p', a') n.
    have [k [? Hk]] := Hp n. exists k.
    split; [|done].
  - right. exists (p', (0, b')).
    have [k [? Hk]] := Hp 0.
    have ? := steps_bound Hk.
    split; [by exists k|].
Qed.


Corollary next_waypoint_b p b :
  terminating (p, (0, b)) +
  { '(p', b') | n, reaches_plus (p, (0, n+b)) (p', (0, n+b')) } +
  { '(p', (a', b')) | reaches (p, (0, b)) (p', (a', b')) a' l b' l }.
Proof.
  have [[?|[[p' a'] Hp]]|[[p' b'] Hp]] := next_waypoint p 0 b.
  - left. by left.
  - right. exists (p', (a', 0)).
    have [k [? Hk]] := Hp 0.
    have ? := steps_bound Hk.
    split; [by exists k|].
  - left. right. exists (p', b') n.
    have [k [? Hk]] := Hp n. exists k.
    split; [|done].
Qed.


Lemma loop_a {p a a' b} :
  a a'
  ( n : , reaches_plus (p, (n + a, b)) (p, (n + a', b)))
  non_terminating (p, (a, b)).
Proof.
  move ? Hp k.
  suff: m, steps k (p, (m + a, b)) None by move /(_ 0).
  elim: k; first done.
  move k IH m.
  have [k' [? Hk']] := Hp m.
  move /(steps_k_monotone (k' + k)) /(_ ltac:()).
  move: Hk' /steps_plus .
  have : m + a' = (m + a' - a) + a by .
  by apply: IH.
Qed.


Lemma loop_b {p a b b'} :
  b b'
  ( n : , reaches_plus (p, (a, n + b)) (p, (a, n + b')))
  non_terminating (p, (a, b)).
Proof.
  move ? Hp k.
  suff: m, steps k (p, (a, m + b)) None by move /(_ 0).
  elim: k; first done.
  move k IH m.
  have [k' [? Hk']] := Hp m.
  move /(steps_k_monotone (k' + k)) /(_ ltac:()).
  move: Hk' /steps_plus .
  have : m + b' = (m + b' - b) + b by .
  by apply: IH.
Qed.


Lemma reaches_plus_self_loop x : reaches_plus x x non_terminating x.
Proof.
  move [k [? Hk]]. elim; first done.
  move k' Hk'.
  move /(steps_k_monotone (k + k')) /(_ ltac:()).
  by rewrite (steps_plus Hk).
Qed.


Definition update {X : Type} (f : X) n x :=
   m if Nat.eq_dec m n is left _ then x else f m.

Inductive R : ( option ) ( option ) Prop :=
  | R_None f p c : p < l f p = None R (update f p (Some c)) f
  | R_Some c' f p c : p < l f p = Some c' c < c' R (update f p (Some c)) f.

Lemma wf_R : well_founded R.
Proof.
  pose F := ( (f : option )
    list_sum (map ( p if f p is None then 1 else 0) (seq 0 l))).
  pose G := ( (f : option )
    list_sum (map ( p if f p is Some c then c else 0) (seq 0 l))).
  elim /(Nat.measure_induction _ F). elim /(Nat.measure_induction _ G).
  move f IHG IHF. constructor g Hgf.
  case: Hgf IHG IHF.
  - move {}f p c ? Hf IHG IHF.
    apply: IHF. rewrite /F.
    have /list_sum_map_lt : In p (seq 0 l) by (apply /in_seq; ).
    apply.
    + move p''. case Hp'': (f p'') [c''|].
      * rewrite /update Hp''. by case: (Nat.eq_dec p'' p).
      * rewrite /update Hp''. case: (Nat.eq_dec p'' p); .
    + rewrite /update Hf. case: (Nat.eq_dec p p); .
  - move c' {}f p c Hl Hf ? IHG IHF.
    apply: IHG; first last.
    { move h Hh. apply: IHF.
      suff: F (update f p (Some c)) F f by .
      apply: list_sum_map_le.
      move p''. case Hp'': (f p'') [c''|].
      - rewrite /update Hp''. by case: (Nat.eq_dec p'' p).
      - rewrite /update Hp''. case: (Nat.eq_dec p'' p); . }
    rewrite /G.
    have : In p (seq 0 l) by (apply /in_seq; ).
    move /list_sum_map_lt. apply.
    + move p''. case Hp'': (f p'') [c''|].
      * rewrite /update. case: (Nat.eq_dec p'' p).
        ** move ?. subst. move: Hf Hp'' []. .
        ** rewrite Hp''. .
      * rewrite /update. case: (Nat.eq_dec p'' p).
        ** move ?. subst. by move: Hf Hp'' .
        ** rewrite Hp''. .
    + rewrite /update Hf. by case: (Nat.eq_dec p p).
Qed.


Lemma big_wf_a p a (f : option ) :
  ( p' a', f p' = Some a' n, reaches_plus (p', (n+a', 0)) (p, (n+a, 0)))
  terminating (p, (a, 0)) +
  non_terminating (p, (a, 0)) +
  { '(p', (a', b')) | reaches (p, (a, 0)) (p', (a', b')) a' l b' l }.
Proof.
  elim /(well_founded_induction_type wf_R) : f p a.
  move f IH p a Hf.
  have [[Hp|[[p' a'] Hp]]|[[p' [a' b']] [Hp ?]]] := next_waypoint_a p a.
  - left. by left.
  - have /= ? := reaches_plus_state_bound (Hp 0).
    set P := (_ + _ + _)%type.
    have HR : R (update f p (Some a)) f P.
    { move /IH /(_ p' a') /=. case; [|case|].
      - move p''' a'''. rewrite /update.
        case: (Nat.eq_dec p''' p).
        { by move []. }
        move ? /Hf Hp''' n.
        by apply: (reaches_plus_trans (Hp''' n) (Hp n)).
      - move /reaches_terminating Hp'. left. left.
        by apply /Hp' /reaches_plus_incl /(Hp 0).
      - move /reaches_non_terminating Hp'. left. right.
        by apply /Hp' /reaches_plus_incl /(Hp 0).
      - move [[p''' [a''' b''']]] [Hp'] ?. right.
        exists (p''', (a''', b''')). split; last done.
        by apply: (reaches_trans (reaches_plus_incl (Hp 0))). }
    case : (f p) [a''|].
    + case E: (a''-a) [|?].
      { left. right. move: /Hf{Hf} .
        apply: (reaches_non_terminating' (reaches_plus_incl ( 0))).
        apply: (loop_a _ ). . }
      apply /HR /(R_Some a''); [done|done|].
    + by apply /HR /R_None.
  - right. by exists (p', (a', b')).
Qed.


Lemma big_wf_b p b (f : option ) :
  ( p' b', f p' = Some b' n, reaches_plus (p', (0, n+b')) (p, (0, n+b)))
  terminating (p, (0, b)) +
  non_terminating (p, (0, b)) +
  { '(p', (a', b')) | reaches (p, (0, b)) (p', (a', b')) a' l b' l }.
Proof.
  elim /(well_founded_induction_type wf_R) : f p b.
  move f IH p b Hf.
  have [[Hp|[[p' b'] Hp]]|[[p' [a' b']] [Hp ?]]] := next_waypoint_b p b.
  - left. by left.
  - have /= ? := reaches_plus_state_bound (Hp 0).
    set P := (_ + _ + _)%type.
    have HR : R (update f p (Some b)) f P.
    { move /IH /(_ p' b') /=. case; [|case|].
      - move p''' b'''. rewrite /update.
        case: (Nat.eq_dec p''' p).
        { by move []. }
        move ? /Hf Hp''' n.
        by apply: (reaches_plus_trans (Hp''' n) (Hp n)).
      - move /reaches_terminating Hp'. left. left.
        by apply /Hp' /reaches_plus_incl /(Hp 0).
      - move /reaches_non_terminating Hp'. left. right.
        by apply /Hp' /reaches_plus_incl /(Hp 0).
      - move [[p''' [a''' b''']]] [Hp'] ?. right.
        exists (p''', (a''', b''')). split; last done.
        by apply: (reaches_trans (reaches_plus_incl (Hp 0))). }
    case : (f p) [b''|].
    + case E: (b''-b) [|?].
      { left. right. move: /Hf{Hf} .
        apply: (reaches_non_terminating' (reaches_plus_incl ( 0))).
        apply: (loop_b _ ). . }
      apply /HR /(R_Some b''); [done|done|].
    + by apply /HR /R_None.
  - right. by exists (p', (a', b')).
Qed.


Lemma next_small_waypoint p a b :
  terminating (p, (a, b)) +
  non_terminating (p, (a, b)) +
  { '(p', (a', b')) | reaches_plus (p, (a, b)) (p', (a', b')) a' l b' l }.
Proof.
  have [[?|[[p' a'] Hp]]|[[p' b'] Hp]] := next_waypoint p a b.
  - left. by left.
  - have [[|]|] := big_wf_a p' a' ( _ None) ltac:(done).
    * move /reaches_terminating Hp'. left. left.
      apply: Hp'. have [k [? Hk]] := Hp 0. by exists k.
    * move /reaches_non_terminating Hp'. left. right.
      apply: Hp'. have [k [? Hk]] := Hp 0. by exists k.
    * move [[p'' [a'' b'']]] [Hp'] ?. right.
      exists (p'', (a'', b'')). split; last done.
      apply: (reaches_plus_reaches _ Hp').
      have [k [? Hk]] := Hp 0. exists k. split; [|done].
  - have [[|]|] := big_wf_b p' b' ( _ None) ltac:(done).
    * move /reaches_terminating Hp'. left. left.
      apply: Hp'. have [k [? Hk]] := Hp 0. by exists k.
    * move /reaches_non_terminating Hp'. left. right.
      apply: Hp'. have [k [? Hk]] := Hp 0. by exists k.
    * move [[p'' [a'' b'']]] [Hp'] ?. right.
      exists (p'', (a'', b'')). split; last done.
      apply: (reaches_plus_reaches _ Hp').
      have [k [? Hk]] := Hp 0. exists k. split; [|done].
Qed.


Lemma small_decider p a b L : a l b l
  Forall ( '(p', (a', b')) reaches_plus (p', (a', b')) (p, (a, b)) p' < l a' l b' l ) L
  NoDup L
  terminating (p, (a, b)) + non_terminating (p, (a, b)).
Proof.
  move Hn: ((l*(l+1)*(l+1)+1) - length L) n.
  elim: n L Hn p a b.
  { move L ? ????? H1L /NoDup_incl_length H2L.
    have : incl L (list_prod (seq 0 l) (list_prod (seq 0 (l+1)) (seq 0 (l+1)))).
    { move [p [a b]].
      move: H1L /Forall_forall H1L /H1L ?.
      apply /in_prod; [|apply /in_prod]; apply /in_seq; . }
    move /H2L. rewrite ?prod_length ?seq_length. . }
  move n IH L ? p a b ? ? H1L H2L.
  have [[|]|[[p' [a' b']] [Hp ?]]] := next_small_waypoint p a b; [tauto|tauto|].
  have := In_dec _ (p, (a, b)) L. case.
  { do ? decide equality. }
  - move . right. apply: reaches_plus_self_loop.
    by move: H1L /Forall_forall H /H [].
  - move .
    have := (IH ((p, (a, b)) :: L) _ p' a' b'). case.
    + move /=. .
    + .
    + .
    + constructor.
      * split; first done.
        move: Hp /reaches_plus_state_bound /=. .
      * apply: Forall_impl H1L.
        move [p'' [a'' b'']] [? ?]. split; last done.
        by apply: (reaches_plus_reaches _ (reaches_plus_incl Hp)).
    + by constructor.
    + move /reaches_terminating H. left. by apply /H /reaches_plus_incl.
    + move /reaches_non_terminating H. right. by apply /H /reaches_plus_incl.
Qed.


Lemma decision x : terminating x + non_terminating x.
Proof.
  move: x [p [a b]].
  have [[|]|] := next_small_waypoint p a b; [tauto|tauto|].
  move [[p' [a' b']]] [/reaches_plus_incl Hp'] [Ha' Hb'].
  have := small_decider p' a' b' [] Ha' Hb' ltac:(by constructor) ltac:(by constructor).
  case.
  - move: Hp' /reaches_terminating H /H ?. by left.
  - move: Hp' /reaches_non_terminating H /H ?. by right.
Qed.


End Construction.

Require Import Undecidability.Synthetic.Definitions.

Definition decide : * Config bool :=
   '(M, c)
    match decision M c with
    | inl _ true
    | inr _ false
    end.

Lemma decide_spec : decider decide MPM2_HALT.
Proof.
  rewrite /decider /reflects /decide - [M c].
  case: (decision M c).
  - tauto.
  - move H. split; [by move [k /H] | done].
Qed.