/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import init.meta.tactic init.function

namespace tactic
open nat tactic environment expr list

private meta def at_end₂ (e₁ e₂ : expr) : ℕ → tactic (list (option expr))
| 2 := return [some e₁, some e₂]
| (n+3) :=  (λ xs, none :: xs) <$> at_end₂ (n+2)
| _ := fail "at_end expected arity > 1"

private meta def mk_next_name : name → nat → tactic (name × nat)
| n i := do
  let n' := n.append_after i,
  (get_local n' >> mk_next_name n (i+1))
  <|>
  (return (n', i+1))

/- Auxiliary function for introducing the new equalities produced by the
   injection tactic. -/
private meta def injection_intro : expr → nat → list name → tactic (list expr × list name)
| (pi n bi b d) i ns := do
  (hname, i) ← if ns.empty then mk_next_name `h i else return (head ns, i),
  h ← intro hname,
  (l, ns') ← injection_intro d i (tail ns),
  return (h :: l, ns')
| e  i ns            := return ([], ns)

/- Tries to decompose the given expression by constructor injectivity.
   Returns the list of new hypotheses, and the remaining names from the given list. -/
meta def injection_with (h : expr) (ns : list name) : tactic (list expr × list name) :=
do
  ht ← infer_type h,
  (lhs0, rhs0) ← match_eq ht,
  env ← get_env,
  lhs ← whnf lhs0,
  rhs ← whnf rhs0,
  let n_fl := const_name (get_app_fn lhs),
  let n_fr := const_name (get_app_fn rhs),
  if n_fl = n_fr then do
    let n_inj := n_fl <.> "inj_arrow",
    if env.contains n_inj then do
      c_inj  ← mk_const n_inj,
      arity  ← get_arity c_inj,
      tgt ← target,
      args   ← at_end₂ h tgt (arity - 1),
      pr     ← mk_mapp n_inj args,
      pr_type ← infer_type pr,
      pr_type ← whnf pr_type,
      eapply pr,
      injection_intro (binding_domain pr_type) 1 ns
    else fail "injection tactic failed, argument must be an equality proof where lhs and rhs are of the form (c ...), where c is a constructor"
  else do
    tgt ← target,
    let I_name := name.get_prefix n_fl,
    pr ← mk_app (I_name <.> "no_confusion") [tgt, lhs, rhs, h],
    exact pr,
    return ([], ns)

meta def injection (h : expr) : tactic (list expr) :=
do (t, _) ← injection_with h [], return t

private meta def injections_with_inner : nat → list expr → list name → tactic unit
| 0     lc        ns := fail "recursion depth exceeded"
| (n+1) []        ns := skip
| (n+1) (h :: lc) ns :=
  do o ← try_core (injection_with h ns), match o with
  | none          := injections_with_inner (n+1) lc ns
  | some ([], _)  := skip -- This means that the contradiction part was triggered and the goal is done
  | some (t, ns') := injections_with_inner n (t ++ lc) ns'
  end

meta def injections_with (ns : list name) : tactic unit :=
do lc ← local_context,
   injections_with_inner 5 lc ns

end tactic
