Ltac fold_closed_hyps :=
  repeat match goal with h: shift _ ?t = ?t |- _ =>
    generalize (fold_closed _ h); clear h; intro h
  end.(* The following tactics help invert (destruct) a closedness hypothesis.
   [inversion_closed_in h] destructs a specific closedness hypothesis [h],
   whereas [inversion_closed] destructs all closedness hypotheses. *)

Ltac inversion_closed_in_internal h :=
  (* Unfold the definition of [closed]. This exposes [lift]. *)
  unfold closed in h;
  (* If [lift] is applied to a constructor, simplify it. *)
  rewrite expand_lift in h; simpl (@traverse _ _ _) in h; simpl_lift;
  (* This may result in an equality that involves two constructors. Decompose it. *)
  try (injection h; clear h; intros).(* The above line assumes that [lift] is opaque. Otherwise, the following would
     be required:
     try (protects @lift do (injection h; clear h; intros))
     We make [lift] opaque at the end of this file.
  *)
  (* At this point, [closed] is still unfolded. This is intentional. *)

Ltac inversion_closed_in h :=
  inversion_closed_in_internal h;
  fold_closed_hyps.Ltac inversion_closed :=
  repeat match goal with h: closed _ _ |- _ =>
    inversion_closed_in_internal h
  end;
  fold_closed_hyps.(* more hints to be added later on into this database *)

Ltac construction_closed :=
  solve [
    (* Expose the definition of [closed] in terms of [lift]. *)
    unfold closed in *;
    (* Presumably [lift] is applied to a constructor in the goal. Simplify it. *)
    simpl_lift_goal;
    (* If we are looking at a variable, the equation should look like this:
       [var (shift k x) = var x]. Simplify [var] away, and apply the tactic
       [lift_idx] to simplify [shift] away. *)
    try (simpl; lift_idx);
    (* Conclude. *)
    eauto with omega construction_closed
  ].(* The following hint proves a goal of the form [shift x v = v] when
   the value [v] is [k]-closed for some [k] that is less than or equal
   to [x]. *)

Hint Extern 1 (shift ?x ?v = ?v) =>
  solve [
    eapply closed_monotonic; [
      eauto with typeclass_instances (* LiftLift *)
    | construction_closed (* [v] is [k]-closed *)
    | omega (* [k <= x] *)
    ]
  ]
: shift_closed.(* The following tactic replaces [shift x v] with [v] in the goal
   when an explicit hypothesis states that [v] is closed. *)

Ltac shift_closed :=
  match goal with h: closed ?x ?v |- context[shift ?x ?v] =>
    replace (shift x v) with v (* no subgoals are generated;
    apparently the hypothesis [h] allows Coq to recognize
    that these terms are equal *)
  end.(* The following hint proves a goal of the form [subst w x v = v] when
   the value [v] is [k]-closed for some [k] that is less than or equal
   to [x]. *)

Hint Extern 1 (subst ?w ?x ?v = ?v) =>
  solve [
    eapply closed_subst_invariant; [
      eauto with typeclass_instances
    | eauto with typeclass_instances
    | construction_closed
    | omega
    ]
  ]
: subst_closed.

Lemma rotate_characterization: forall n k, (k = 0 -> rotate n k = n) /\ (k > 0 -> k <= n -> rotate n k = k - 1) /\ (k > n -> rotate n k = k).