/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Core

universe u v w w'

class Coe (α : Sort u) (β : Sort v) where
  coe : α → β

/-- Auxiliary class that contains the transitive closure of `Coe`. -/
class CoeTC (α : Sort u) (β : Sort v) where
  coe : α → β

/- Expensive coercion that can only appear at the beginning of a sequence of coercions. -/
class CoeHead (α : Sort u) (β : Sort v) where
  coe : α → β

/- Expensive coercion that can only appear at the end of a sequence of coercions. -/
class CoeTail (α : Sort u) (β : Sort v) where
  coe : α → β

/-- Auxiliary class that contains `CoeHead` + `CoeTC` + `CoeTail`. -/
class CoeHTCT (α : Sort u) (β : Sort v) where
  coe : α → β

class CoeDep (α : Sort u) (a : α) (β : Sort v) where
  coe : β

/- Combines CoeHead, CoeTC, CoeTail, CoeDep -/
class CoeT (α : Sort u) (a : α) (β : Sort v) where
  coe : β

class CoeFun (α : Sort u) (γ : outParam (α → Sort v)) where
  coe : (a : α) → γ a

class CoeSort (α : Sort u) (β : outParam (Sort v)) where
  coe : α → β

abbrev coeB {α : Sort u} {β : Sort v} [Coe α β] (a : α) : β :=
  Coe.coe a

abbrev coeHead {α : Sort u} {β : Sort v} [CoeHead α β] (a : α) : β :=
  CoeHead.coe a

abbrev coeTail {α : Sort u} {β : Sort v} [CoeTail α β] (a : α) : β :=
  CoeTail.coe a

abbrev coeD {α : Sort u} {β : Sort v} (a : α) [CoeDep α a β] : β :=
  CoeDep.coe a

abbrev coeTC {α : Sort u} {β : Sort v} [CoeTC α β] (a : α) : β :=
  CoeTC.coe a

/-- Apply coercion manually. -/
abbrev coe {α : Sort u} {β : Sort v} (a : α) [CoeT α a β] : β :=
  CoeT.coe a

prefix:max "↑" => coe

abbrev coeFun {α : Sort u} {γ : α → Sort v} (a : α) [CoeFun α γ] : γ a :=
  CoeFun.coe a

abbrev coeSort {α : Sort u} {β : Sort v} (a : α) [CoeSort α β] : β :=
  CoeSort.coe a

instance coeTrans {α : Sort u} {β : Sort v} {δ : Sort w} [Coe β δ] [CoeTC α β] : CoeTC α δ where
  coe a := coeB (coeTC a : β)

instance coeBase {α : Sort u} {β : Sort v} [Coe α β] : CoeTC α β where
  coe a := coeB a

instance coeOfHeafOfTCOfTail {α : Sort u} {β : Sort v} {δ : Sort w} {γ : Sort w'} [CoeHead α β] [CoeTail δ γ] [CoeTC β δ] : CoeHTCT α γ where
  coe a := coeTail (coeTC (coeHead a : β) : δ)

instance coeOfHeadOfTC {α : Sort u} {β : Sort v} {δ : Sort w} [CoeHead α β] [CoeTC β δ] : CoeHTCT α δ  where
  coe a := coeTC (coeHead a : β)

instance coeOfTCOfTail {α : Sort u} {β : Sort v} {δ : Sort w} [CoeTail β δ] [CoeTC α β] : CoeHTCT α δ where
  coe a := coeTail (coeTC a : β)

instance coeOfHead {α : Sort u} {β : Sort v} [CoeHead α β] : CoeHTCT α β where
  coe a := coeHead a

instance coeOfTail {α : Sort u} {β : Sort v} [CoeTail α β] : CoeHTCT α β where
  coe a := coeTail a

instance coeOfTC {α : Sort u} {β : Sort v} [CoeTC α β] : CoeHTCT α β where
  coe a := coeTC a

instance coeOfHTCT {α : Sort u} {β : Sort v} [CoeHTCT α β] (a : α) : CoeT α a β where
  coe := CoeHTCT.coe a

instance coeOfDep {α : Sort u} {β : Sort v} (a : α) [CoeDep α a β] : CoeT α a β where
  coe := coeD a

instance coeId {α : Sort u} (a : α) : CoeT α a α where
  coe := a

/- Basic instances -/

@[inline] instance boolToProp : Coe Bool Prop where
  coe b := b = true

instance boolToSort : CoeSort Bool Prop where
  coe b := b

@[inline] instance coeDecidableEq (x : Bool) : Decidable (coe x) :=
  inferInstanceAs (Decidable (x = true))

instance decPropToBool (p : Prop) [Decidable p] : CoeDep Prop p Bool where
  coe := decide p

instance optionCoe {α : Type u} : CoeTail α (Option α) where
  coe := some

instance subtypeCoe {α : Sort u} {p : α → Prop} : CoeHead { x // p x } α where
  coe v := v.val

/- Coe & OfNat bridge -/

/-
  Remark: one may question why we use `OfNat α` instead of `Coe Nat α`.
  Reason: `OfNat` is for implementing polymorphic numeric literals, and we may
  want to have numeric literals for a type α and **no** coercion from `Nat` to `α`. -/
instance hasOfNatOfCoe [Coe α β] [OfNat α n] : OfNat β n where
  ofNat := coe (OfNat.ofNat n : α)

@[inline] def liftCoeM {m : Type u → Type v} {n : Type u → Type w} {α β : Type u} [MonadLiftT m n] [∀ a, CoeT α a β] [Monad n] (x : m α) : n β := do
  let a ← liftM x
  pure (coe a)

@[inline] def coeM {m : Type u → Type v} {α β : Type u} [∀ a, CoeT α a β] [Monad m] (x : m α) : m β := do
  let a ← x
  pure <| coe a

instance [CoeFun α β] (a : α) : CoeDep α a (β a) where
  coe := coeFun a

instance [CoeFun α (fun _ => β)] : CoeTail α β  where
  coe a := coeFun a

instance [CoeSort α β] : CoeTail α β where
  coe a := coeSort a

/- Coe and heterogeneous operators, we use `CoeHTCT` instead of `CoeT` to avoid expensive coercions such as `CoeDep` -/

instance [CoeHTCT α β] [Add β] : HAdd α β β where
  hAdd a b := Add.add a b

instance [CoeHTCT α β] [Add β] : HAdd β α β where
  hAdd a b := Add.add a b

instance [CoeHTCT α β] [Sub β] : HSub α β β where
  hSub a b := Sub.sub a b

instance [CoeHTCT α β] [Sub β] : HSub β α β where
  hSub a b := Sub.sub a b

instance [CoeHTCT α β] [Mul β] : HMul α β β where
  hMul a b := Mul.mul a b

instance [CoeHTCT α β] [Mul β] : HMul β α β where
  hMul a b := Mul.mul a b

instance [CoeHTCT α β] [Div β] : HDiv α β β where
  hDiv a b := Div.div a b

instance [CoeHTCT α β] [Div β] : HDiv β α β where
  hDiv a b := Div.div a b

instance [CoeHTCT α β] [Mod β] : HMod α β β where
  hMod a b := Mod.mod a b

instance [CoeHTCT α β] [Mod β] : HMod β α β where
  hMod a b := Mod.mod a b

instance [CoeHTCT α β] [Append β] : HAppend α β β where
  hAppend a b := Append.append a b

instance [CoeHTCT α β] [Append β] : HAppend β α β where
  hAppend a b := Append.append a b

instance [CoeHTCT α β] [OrElse β] : HOrElse α β β where
  hOrElse a b := OrElse.orElse a b

instance [CoeHTCT α β] [OrElse β] : HOrElse β α β where
  hOrElse a b := OrElse.orElse a b

instance [CoeHTCT α β] [AndThen β] : HAndThen α β β where
  hAndThen a b := AndThen.andThen a b

instance [CoeHTCT α β] [AndThen β] : HAndThen β α β where
  hAndThen a b := AndThen.andThen a b
