/-
Copyright (c) 2018 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import init.data.option.basic init.lean.name init.lean.format

namespace Lean

inductive Level
| zero   : Level
| succ   : Level → Level
| max    : Level → Level → Level
| imax   : Level → Level → Level
| Param  : Name → Level
| mvar   : Name → Level

attribute [extern "level_mk_succ"]  Level.succ
attribute [extern "level_mk_max"]   Level.max
attribute [extern "level_mk_imax"]  Level.imax
attribute [extern "level_mk_param"] Level.Param
attribute [extern "level_mk_mvar"]  Level.mvar

instance levelIsInhabited : Inhabited Level :=
⟨Level.zero⟩

def Level.one : Level := Level.succ Level.zero

def Level.hasParam : Level → Bool
| (Level.Param _)    := true
| (Level.succ l)     := Level.hasParam l
| (Level.max l₁ l₂)  := Level.hasParam l₁ || Level.hasParam l₂
| (Level.imax l₁ l₂) := Level.hasParam l₁ || Level.hasParam l₂
| _                  := false

def Level.hasMvar : Level → Bool
| (Level.mvar _)     := true
| (Level.succ l)     := Level.hasParam l
| (Level.max l₁ l₂)  := Level.hasParam l₁ || Level.hasParam l₂
| (Level.imax l₁ l₂) := Level.hasParam l₁ || Level.hasParam l₂
| _                  := false

def Level.ofNat : Nat → Level
| 0     := Level.zero
| (n+1) := Level.succ (Level.ofNat n)

def Nat.imax (n m : Nat) : Nat :=
if m = 0 then 0 else Nat.max n m

def Level.toNat : Level → Option Nat
| Level.zero         := some 0
| (Level.succ l)     := Nat.succ <$> Level.toNat l
| (Level.max l₁ l₂)  := Nat.max  <$> Level.toNat l₁ <*> Level.toNat l₂
| (Level.imax l₁ l₂) := Nat.imax <$> Level.toNat l₁ <*> Level.toNat l₂
| _                  := none

def Level.toOffset : Level → Level × Nat
| Level.zero     := (Level.zero, 0)
| (Level.succ l) := let (l', k) := Level.toOffset l in (l', k+1)
| l              := (l, 0)

def Level.instantiate (s : Name → Option Level) : Level → Level
| Level.zero         := Level.zero
| (Level.succ l)     := Level.succ (Level.instantiate l)
| (Level.max l₁ l₂)  := Level.max (Level.instantiate l₁) (Level.instantiate l₂)
| (Level.imax l₁ l₂) := Level.imax (Level.instantiate l₁) (Level.instantiate l₂)
| l@(Level.Param n)  :=
  match s n with
  | some l' := l'
  | none    := l
| l                  := l

@[extern "lean_level_hash"]
constant Level.hash (n : @& Level) : USize := default USize

/- Level to Format -/
namespace LevelToFormat
inductive Result
| leaf      : Format → Result
| num       : Nat → Result
| offset    : Result → Nat → Result
| maxNode   : List Result → Result
| imaxNode  : List Result → Result

def Result.succ : Result → Result
| (Result.offset f k) := Result.offset f (k+1)
| (Result.num k)      := Result.num (k+1)
| f                   := Result.offset f 1

def Result.max : Result → Result → Result
| f (Result.maxNode Fs) := Result.maxNode (f::Fs)
| f₁ f₂                 := Result.maxNode [f₁, f₂]

def Result.imax : Result → Result → Result
| f (Result.imaxNode Fs) := Result.imaxNode (f::Fs)
| f₁ f₂                  := Result.imaxNode [f₁, f₂]

def parenIfFalse : Format → Bool → Format
| f true  := f
| f false := f.paren

@[specialize] private def formatLst (fmt : Result → Format) : List Result → Format
| []      := Format.nil
| (r::rs) := Format.line ++ fmt r ++ formatLst rs

partial def Result.format : Result → Bool → Format
| (Result.leaf f)         _ := f
| (Result.num k)          _ := toString k
| (Result.offset f 0)     r := Result.format f r
| (Result.offset f (k+1)) r :=
  let f' := Result.format f false in
  parenIfFalse (f' ++ "+" ++ fmt (k+1)) r
| (Result.maxNode fs)    r := parenIfFalse (Format.group $ "max"  ++ formatLst (λ r, Result.format r false) fs) r
| (Result.imaxNode fs)   r := parenIfFalse (Format.group $ "imax" ++ formatLst (λ r, Result.format r false) fs) r

def Level.toResult : Level → Result
| Level.zero         := Result.num 0
| (Level.succ l)     := Result.succ (Level.toResult l)
| (Level.max l₁ l₂)  := Result.max (Level.toResult l₁) (Level.toResult l₂)
| (Level.imax l₁ l₂) := Result.imax (Level.toResult l₁) (Level.toResult l₂)
| (Level.Param n)    := Result.leaf (fmt n)
| (Level.mvar n)     := Result.leaf (fmt n)

def Level.format (l : Level) : Format :=
(Level.toResult l).format true

instance levelHasFormat : HasFormat Level := ⟨Level.format⟩
instance levelHasToString : HasToString Level := ⟨Format.pretty ∘ Level.format⟩
end LevelToFormat

end Lean
