From Undecidability Require Import TM.Util.TM_facts.
Require Import Undecidability.Shared.Libs.PSL.FiniteTypes.DepPairs EqdepFacts.

(* * Switch Combinator *)

Section Switch.

  Variable n : .
  Variable sig : finType.

  Variable F : finType.

  Variable : pTM sig F n.
  Variable F' : finType.
  Variable pMf : F pTM sig F' n.

  (* The (unlabelled) machine M *)
  Notation M1 := ( ).
  (* The labelling function of the machine M *)
  Notation p1 := ( ).

  (* The (unlabelled) case-machine M' y *)
  Notation "'Mf' y" := ( (pMf y)) (at level 10).
  (* The labelling function of the case-machine M' y *)
  Notation "'p2' y" := ( (pMf y)) (at level 10).

  Definition Switch_trans :
    (TM.state M1 + { f : F & TM.state (Mf f) }) * Vector.t (option sig) n
    (TM.state M1 + { f : F & TM.state (Mf f) }) * Vector.t (option sig * move) n :=
     '(q, s)
        match q with
        | inl q
          if halt q
          then (inr (existT _ (p1 q) (start (Mf (p1 q)))), nop_action)
          else let (q', a) := trans (q, s) in (inl q', a)
        | inr q
          let (q', a) := trans ( q, s) in
          (inr (existT _ ( q) q'), a)
        end.

  Definition Switch_halt : (TM.state M1 + { f : F & TM.state (Mf f) }) bool :=
     q
      match q with
      | inl _ false
      | inr q halt ( q)
      end.

  Definition SwitchTM : TM sig n :=
    {|
      trans := Switch_trans;
      halt := Switch_halt;
      start := inl (start M1);
    |}.

  Definition Switch_p : (state SwitchTM) F' :=
     q match q with
          | inl q (p1 q) (start (Mf ( q))) (* Canonical value *)
          | inr q ( q) ( q)
          end.

  Definition Switch : pTM sig F' n := (SwitchTM; Switch_p).


  (* Lift configurations of  to configurations of Switch *)
  Definition lift_confL (c : mconfig sig (state M1) n) : mconfig sig (state SwitchTM) n :=
    mk_mconfig (inl (cstate c)) (ctapes c).

  (* Lift configuration of  to configurations of Switch *)
  Definition lift_confR (f : F) (c : mconfig sig (state (Mf f) ) n) : mconfig sig (state SwitchTM) n :=
    mk_mconfig (inr (existT ( f0 : F state (Mf )) f (cstate c))) (ctapes c).

  (* Lifted Steps of  are compatible with steps in , for non-halting state *)
  Lemma step_comp_liftL (c : mconfig sig (state M1) n) :
    haltConf c = false step (lift_confL c) = lift_confL (step c).
  Proof.
    unfold lift_confL, step, haltConf. cbn. destruct c as [q t]; cbn in *. intros H. rewrite H.
    destruct (trans _) eqn:E. cbn. reflexivity.
  Qed.


  (* Lifted steps of case-machines Mf f are compatible with steps in Mf f *)
  Lemma step_comp_liftR f (c : mconfig sig (state (Mf f)) n) :
    step (lift_confR c) = lift_confR (step c).
  Proof.
    destruct c. unfold lift_confR, step. cbn.
    destruct (trans _) eqn:E. cbn. reflexivity.
  Qed.


  (* Lifted halting state of  *)
  Definition halt_liftL (c : mconfig sig (state (SwitchTM)) n) :=
    match cstate c with
    | inl q halt (m := M1) q
    | inr q true
    end.

  (* Non-halting state of  are non-halting state of Switch *)
  Lemma halt_conf_liftL (c : mconfig sig (state SwitchTM) n) :
    halt_liftL c = false halt (cstate c) = false.
  Proof.
    intros H. cbn. unfold Switch_halt.
    destruct c as [q t]; cbn.
    destruct q; cbn in *; auto.
  Qed.


  (* The "nop" transition jumps from a halting configuration of  to the initial configuration of the corresponding case-machine. *)
  Lemma step_nop_transition (c : mconfig sig (state M1) n) :
    haltConf c = true
     step (lift_confL c) = lift_confR (initc (Mf (p1 (cstate c))) (ctapes c)).
  Proof.
    intros Halt.
    unfold lift_confL, lift_confR. cbn. unfold haltConf in Halt.
    unfold step at 1; cbn.
    rewrite Halt. f_equal.
    apply doAct_nop.
  Qed.


  (* The starting configuration of Switch corresponds to the starting configuration of . *)
  Lemma lift_initc t :
    initc SwitchTM t = lift_confL (initc M1 t).
  Proof. reflexivity. Qed.

  (* This lemma is needed for the termination part. Suppose  terminates in .  The case machine Mf f starts with the tapes of  and terminates in a configuration . Then, if we start Switch with the same tapes as Switch terminates in the lifted configuration of . *)
  Lemma Switch_merge t (k1 k2 : )
        (c1 : mconfig sig (state M1) n)
        (c2 : mconfig sig (state (Mf (p1 (cstate )))) n) :
    loopM (initc M1 t) = Some
    loopM (initc (Mf (p1 (cstate ))) (ctapes )) = Some
    loopM (initc SwitchTM t) ( + (1 + )) = Some (lift_confR ).
  Proof.
    intros . unfold loopM in *.
    apply loop_merge with (h := halt_liftL) ( := lift_confL ).
    - apply halt_conf_liftL.
    - rewrite lift_initc.
      apply loop_lift with (h := haltConf (M := M1)) (f := step (M := M1)).
      + unfold haltConf. intros. cbn. reflexivity.
      + apply step_comp_liftL.
      + apply .
    - (* execute one step *)
      rewrite loop_step by auto.
      rewrite step_nop_transition by apply (loop_fulfills ).
      eapply loop_lift with (lift := lift_confR (f := p1 (cstate ))) (f' := step (M := SwitchTM)) (h' := haltConf (M := SwitchTM)) in .
      + apply .
      + intros. cbn. now destruct x.
      + intros. apply step_comp_liftR.
  Qed.


  (* The Switch machine must take the "nop" action if it is in a final state of . *)
  Lemma step_nop_split (k2 : ) (c2 : mconfig sig (state M1) n) (outc : mconfig sig (state SwitchTM) n) :
    haltConf = true
    loopM (M := SwitchTM) (lift_confL ) = Some outc
     k2' c2',
       = S
      loopM (M := Mf (p1 (cstate ))) (initc _ (ctapes )) = Some
      outc = lift_confR .
  Proof.
    unfold loopM. intros HHalt . unfold haltConf in HHalt.
    destruct as [ | ].
    - inv .
    - exists . cbn in .
      rewrite step_nop_transition in by assumption.
      apply loop_unlift with
          (f := step (M := Mf (p1 (cstate ))))
          (h := haltConf (M := Mf (p1 (cstate )))) in as
          (&&).
      + exists . repeat split. exact .
      + intros. reflexivity.
      + intros. apply step_comp_liftR.
  Qed.


  Lemma Switch_split k t (outc : mconfig sig (state SwitchTM) n) :
    loopM (initc SwitchTM t) k = Some outc
     k1 (c1 : mconfig sig (state M1) n) k2 (c2 : mconfig sig (state (Mf (p1 (cstate )))) n),
      loopM (initc M1 t) = Some
      loopM (initc (Mf (p1 (cstate ))) (ctapes )) = Some
      outc = lift_confR .
  Proof.
    unfold loopM. intros H.
    apply loop_split with (h := halt_liftL) in H as (&&&&&_).
    - rewrite lift_initc in .
      apply loop_unlift with (lift := lift_confL) (f := step (M := M1)) (h := haltConf (M := M1)) in as (&&).
      + apply step_nop_split in as (&&_&&). 2: now apply (loop_fulfills ).
        exists , , , . auto.
      + intros. cbn. reflexivity.
      + intros. now apply step_comp_liftL.
    - apply halt_conf_liftL.
  Qed.



  (* Correctness *)
  Lemma Switch_Realise (R1 : Rel _ (F * _)) (R2 : F Rel _ (F' * _)) :
    
    ( f : F, pMf f f) Switch (_f ( |_ f) f).
  Proof.
    intros . hnf in .
    hnf. intros t i outc HLoop.
    apply Switch_split in HLoop as (&&&&&&). cbn.
    exists (p1 (cstate )), (ctapes ). split.
    - apply ( _ _ _ ).
    - apply ( _ _ _ _ ).
  Qed.


  (* Runtime *)
  Lemma Switch_TerminatesIn (R1 : Rel _ (F * _)) T1 T2 :
     M1 ( f : F, Mf f ↓( f))
     Switch ( tin i i1 i2, tin 1 + + i tout yout, tin (yout, tout) yout tout ).
  Proof.
    unfold Switch. intros . hnf in , .
    hnf. intros t i (&&&Hk&H).
    specialize with (1 := ) as (&).
    specialize with (1 := ).
    specialize H with (1 := ).
    specialize ( _ _ _ H) as (&).
    pose proof Switch_merge as HLoop.
    exists (lift_confR ). eapply loop_monotone; eauto. .
  Qed.


  (* This is a stronger version where we can choose the running time of Mf depending on the output label of . This is usually not needed. *)
  Lemma Switch_TerminatesIn' (R1 : Rel _ (F * _)) T1 T2 :
     M1 ( f : F, Mf f ↓( f))
     Switch ( tin i i1, tin tout yout, tin (yout, tout) i2, 1 + + i yout tout ).
  Proof.
    unfold Switch. intros . hnf in , .
    hnf. intros t k (&&H).
    specialize with (1 := ) as (&).
    specialize with (1 := ).
    specialize H with (1 := ) as (&Hi&).
    unfold TerminatesIn in . specialize with (1 := ) as (&).
    pose proof Switch_merge as HLoop.
    exists (lift_confR ). eapply loop_monotone; eauto. .
  Qed.


  (* Correct + constant running time *)
  Lemma Switch_RealiseIn (R1 : Rel _ (F * _)) (R2 : F Rel _ (F' * _)) k1 k2:
     c()
    ( f : F, pMf f c() f)
    Switch c(1 + + ) (_f ( |_ f) f).
  Proof.
    intros (&) % Realise_total . apply Realise_total. split.
    - eapply Switch_Realise; eauto. intros ?. eapply Realise_total; eauto.
    - eapply TerminatesIn_monotone.
      + apply Switch_TerminatesIn; eauto. intros ?. eapply Realise_total; eauto.
      + firstorder.
  Qed.


End Switch.

Arguments Switch : simpl never.

(* Deprecated names *)
Notation MATCH := Switch (only parsing).
Notation Match := Switch (only parsing).