/-
Copyright (c) 2022 Mac Malone. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mac Malone
-/
import Lean.Data.Name
import Lake.Util.Compare

open Lean

namespace Lake

export Lean (Name NameMap)

-- # Name Helpers

namespace Name

@[simp] theorem beq_rfl (n : Name) : (n == n) = true := by
  simp only [BEq.beq]; induction n <;> simp [Name.beq, *]

@[simp] theorem isPrefixOf_self {n : Name} : n.isPrefixOf n = true := by
  cases n <;> simp [Name.isPrefixOf]

@[simp] theorem isPrefixOf_append {n m : Name} : n.isPrefixOf (n ++ m) = true := by
  simp only [HAppend.hAppend, Append.append]
  induction m <;> simp [Name.isPrefixOf, Name.append, *]

/--
The propositional condition of a `Name` being well-formed.
A well-formed name has its has hash computable in the standard manner
(i.e., via the internals of `mkStr` and `mkNum`).
-/
inductive WellFormed : Name → Prop
| anonymousWff : WellFormed Name.anonymous
| strWff {n p s} : WellFormed p → n = Name.mkStr p s → WellFormed n
| numWff {n p v} : WellFormed p → n = Name.mkNum p v → WellFormed n

def WellFormed.elimStr : WellFormed (Name.str p s h) → WellFormed p
| strWff w rfl => w

def WellFormed.elimNum : WellFormed (Name.num p v h) → WellFormed p
| numWff w rfl => w

theorem eq_of_wf_quickCmpAux
(n : Name) (w : Name.WellFormed n) (n' : Name) (w' : Name.WellFormed n')
: Name.quickCmpAux n n' = Ordering.eq → n = n' := by
  revert n'
  induction w with
  | anonymousWff =>
    intro n' w'; cases w' <;> simp [Name.quickCmpAux, *]
  | @strWff n p s _ h ih =>
    intro n' w'
    cases w' with
    | anonymousWff =>
      simp [h, Name.quickCmpAux]
    | @strWff n' p' s' w' h' =>
      simp only [h, h', Name.quickCmpAux]
      intro h_cmp
      split at h_cmp
      next h_cmp_s =>
        let p_eq := ih p' w' h_cmp
        let s_eq := String.eq_of_compare h_cmp_s
        rw [s_eq, p_eq]
      next =>
        contradiction
    | @numWff n' p' v' w' h' =>
      simp [h, h', Name.quickCmpAux]
  | @numWff m p v _ h ih =>
    intro n' w'
    cases w' with
    | anonymousWff =>
      simp [h, Name.quickCmpAux]
    | @strWff n' p' s' w' h' =>
      simp [h, h', Name.quickCmpAux]
    | @numWff n' p' v' w' h' =>
      simp only [h, h', Name.quickCmpAux]
      intro h_cmp
      split at h_cmp
      next h_cmp_v =>
        let p_eq := ih p' w' h_cmp
        let v_eq := Nat.eq_of_compare h_cmp_v
        rw [v_eq, p_eq]
      next =>
        contradiction

end Name

-- # Subtype Helpers

namespace Subtype

theorem val_eq_of_eq {a b : Subtype p} (h : a = b) : a.val = b.val :=
  h ▸ rfl

theorem eq_of_val_eq : ∀ {a b : Subtype p}, a.val = b.val → a = b
  | ⟨_, _⟩, ⟨_, _⟩, rfl => rfl

theorem eq_iff_val_eq {a b : Subtype p} : a = b ↔ a.val = b.val :=
  Iff.intro val_eq_of_eq eq_of_val_eq

theorem ne_of_val_ne {a b : Subtype p} (h : a.val ≠ b.val) : a ≠ b :=
  fun h' => absurd (val_eq_of_eq h') h

theorem val_ne_of_ne {a b : Subtype p} (h : a ≠ b) : a.val ≠ b.val :=
  fun h' => absurd (eq_of_val_eq h') h

theorem ne_iff_val_ne {a b : Subtype p} : a ≠ b ↔ a.val ≠ b.val :=
  Iff.intro val_ne_of_ne ne_of_val_ne

end Subtype

-- # Well-formed Names


/--
A `WellFormed` `Name`.
Such a name has its hash provably computed in the standard manner.
This allows us to prove that the `beq` and `quickCmp` functions' equality
corresponds to propositional equality for this subtype.
-/
abbrev WfName :=
  {n : Name // Name.WellFormed n}

namespace WfName

def anonymous : WfName :=
  ⟨Name.anonymous, Name.WellFormed.anonymousWff⟩

instance : Inhabited WfName := ⟨anonymous⟩

def isAnonymous : WfName → Bool
| ⟨.anonymous, _⟩ => true
| _ => false

@[inline] def mkStr : WfName → String → WfName
| ⟨p, h⟩, s => ⟨Name.mkStr p s, Name.WellFormed.strWff h rfl⟩

@[inline] def mkNum : WfName → Nat → WfName
| ⟨p, h⟩, v => ⟨Name.mkNum p v, Name.WellFormed.numWff h rfl⟩

def ofName : Name → WfName
| .anonymous => anonymous
| .str p s _ => mkStr (ofName p) s
| .num p v _ => mkNum (ofName p) v

@[inline] protected def toString (escape := true) : WfName → String
| ⟨n, _⟩ => n.toString escape

@[inline] protected def toStringWithSep (sep : String) (escape := true) : WfName → String
| ⟨n, _⟩ => n.toStringWithSep sep escape

instance : ToString WfName := ⟨(·.toString)⟩

@[inline] protected def hash : WfName → UInt64
| ⟨n, _⟩ => n.hash

instance : Hashable WfName := ⟨WfName.hash⟩

@[inline] protected def beq : WfName → WfName → Bool
| ⟨n, _⟩, ⟨n', _⟩ => n.beq n'

instance : BEq WfName := ⟨WfName.beq⟩

theorem eq_of_beq_true : {n n' : WfName} → (n == n') = true → n = n' := by
  intro ⟨n, w⟩
  simp only [BEq.beq, WfName.beq, Subtype.eq_iff_val_eq]
  induction w with
  | anonymousWff =>
    intro ⟨n', w'⟩; cases w' <;> simp [Name.beq, *]
  | @strWff n p s _ h ih =>
    intro ⟨n', w'⟩
    simp only [Subtype.eq_iff_val_eq]
    cases w' with
    | anonymousWff =>
      simp [Name.mkStr, Name.beq, h]
    | @strWff n' p' s' w' h' =>
      simp only [Name.mkStr, Name.beq, h, h']
      simp only [BEq.beq, Bool.and_eq_true, decide_eq_true_eq]
      intro ⟨s_eq, p_beq⟩
      simp [s_eq, @ih ⟨p', w'⟩ p_beq]
    | @numWff n' p' v' w' h' =>
      simp only [Name.mkNum, Name.beq, h, h']
      exact False.elim
  | @numWff n p v _ h ih =>
    intro ⟨n', w'⟩
    simp only [Subtype.eq_iff_val_eq]
    cases w' with
    | anonymousWff =>
      simp [Name.mkNum, Name.beq, h]
    | @strWff n' p' s' w' h' =>
      simp only [Name.mkNum, Name.beq, h, h']
      exact False.elim
    | @numWff n' p' v' w' h' =>
      simp only [Name.mkNum, Name.beq, h, h']
      simp only [BEq.beq, Bool.and_eq_true, decide_eq_true_eq]
      intro ⟨n_beq, p_beq⟩
      simp [Nat.eq_of_beq_eq_true n_beq, @ih ⟨p', w'⟩ p_beq]

instance : LawfulBEq WfName where
  eq_of_beq := WfName.eq_of_beq_true
  rfl {n} := Name.beq_rfl n

theorem ne_of_beq_false {n n' : WfName} : (n == n') = false → n ≠ n' :=
  _root_.ne_of_beq_false

instance : DecidableEq WfName :=
  fun n n' => match h:WfName.beq n n' with
  | true  => isTrue (eq_of_beq_true h)
  | false => isFalse (ne_of_beq_false h)

@[inline] def quickCmp : WfName → WfName → Ordering
| ⟨n, _⟩, ⟨n', _⟩ => n.quickCmp n'

theorem eq_of_quickCmp :
{n n' : WfName} → quickCmp n n' = Ordering.eq → n = n' := by
  intro ⟨n, w⟩ ⟨n', w'⟩
  simp only [quickCmp, Name.quickCmp, Subtype.eq_iff_val_eq]
  intro h_cmp; split at h_cmp
  next => exact Name.eq_of_wf_quickCmpAux n w n' w' h_cmp
  next => contradiction

instance : EqOfCmp WfName WfName.quickCmp where
  eq_of_cmp h := WfName.eq_of_quickCmp h

open Syntax in
protected def quoteFrom (ref : Syntax) : WfName → Syntax
| ⟨n, w⟩ => match n with
  | .anonymous => mkCIdentFrom ref ``anonymous
  | .str p s _ => mkApp (mkCIdentFrom ref ``mkStr)
    #[WfName.quoteFrom ref ⟨p, w.elimStr⟩, quote s]
  | .num p v _ => mkApp (mkCIdentFrom ref ``mkNum)
    #[WfName.quoteFrom ref ⟨p, w.elimNum⟩, quote v]

instance : Quote WfName := ⟨WfName.quoteFrom Syntax.missing⟩

end WfName

-- ## Notation

/--
A proven well-formed, unchecked name literal.

Lake augments name literals to produce well-formed names (`WfName`)
instead of their plain counterparts. Well-formed names have additional
properties that help ensure certain features of Lake work as intended.
-/
scoped macro:max (name := wfNameLit) "&" noWs stx:name : term =>
  if let some nm := stx.isNameLit? then
    return WfName.quoteFrom stx <| WfName.ofName nm
  else
    Macro.throwErrorAt stx "ill-formed name literal"

scoped notation "&`✝" => WfName.anonymous

@[scoped appUnexpander WfName.mkStr]
def unexpandWfNameMkStr : PrettyPrinter.Unexpander
| `($(_) &`✝ $s) => do
  let some s := s.isStrLit? | throw ()
  let qn := quote <| Name.mkStr Name.anonymous s
  `(&$(qn[0]):name)
| `($(_) &$n:name $s) => do
  let some s := s.isStrLit? | throw ()
  let some n := n.isNameLit? | throw ()
  let qn := quote <| Name.mkStr n s
  `(&$(qn[0]):name)
| _ => throw ()
