/-
Copyright (c) 2021 Mac Malone. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mac Malone
-/
import Lake.Util.Error
import Lake.Util.OptionIO

namespace Lake

inductive LogLevel
| info
| warning
| error

inductive Verbosity : Type u
| quiet
| normal
| verbose
deriving BEq

instance : Inhabited Verbosity := ⟨.normal⟩

/-! # Class -/

class MonadLog (m : Type u → Type v) where
  getVerbosity : m Verbosity
  log (message : String) (level : LogLevel) : m PUnit

export MonadLog (log getVerbosity)

def getIsVerbose [Functor m] [MonadLog m] : m Bool :=
  getVerbosity <&> (· == .verbose)

def getIsQuiet [Functor m] [MonadLog m] : m Bool :=
  getVerbosity <&> (· == .quiet)

@[inline] def logVerbose [Monad m] [MonadLog m] (message : String) : m PUnit := do
  if (← getIsVerbose) then log message .info

@[inline] def logInfo [Monad m] [MonadLog m] (message : String) : m PUnit := do
  if !(← getIsQuiet) then log message .info

abbrev logWarning [MonadLog m] (message : String) : m PUnit :=
  log message .warning

abbrev logError  [MonadLog m] (message : String) : m PUnit :=
  log message .error

namespace MonadLog

def nop [Pure m] : MonadLog m :=
  ⟨pure .normal, fun _ _ => pure ()⟩

instance [Pure m] : Inhabited (MonadLog m) := ⟨MonadLog.nop⟩

def io [MonadLiftT BaseIO m] (verbosity := Verbosity.normal) : MonadLog m where
  getVerbosity := (pure verbosity : BaseIO _)
  log msg
    | .info => IO.println msg.trim |>.catchExceptions fun _ => pure ()
    | .warning => IO.eprintln s!"warning: {msg.trim}" |>.catchExceptions fun _ => pure ()
    | .error => IO.eprintln s!"error: {msg.trim}" |>.catchExceptions fun _ => pure ()

def eio [MonadLiftT BaseIO m] (verbosity := Verbosity.normal) : MonadLog m where
  getVerbosity := (pure verbosity : BaseIO _)
  log msg
    | .info => IO.eprintln s!"info: {msg.trim}" |>.catchExceptions fun _ => pure ()
    | .warning => IO.eprintln s!"warning: {msg.trim}" |>.catchExceptions fun _ => pure ()
    | .error => IO.eprintln s!"error: {msg.trim}" |>.catchExceptions fun _ => pure ()

def lift [MonadLiftT m n] (self : MonadLog m) : MonadLog n where
  getVerbosity := liftM <| self.getVerbosity
  log msg lv := liftM <| self.log msg lv

instance [MonadLift m n] [methods : MonadLog m] : MonadLog n := lift methods

/-- Log the given error message and then fail. -/
protected def error [Alternative m] [MonadLog m] (msg : String) : m α :=
  logError msg *> failure

end MonadLog

/-! # Transformers -/

abbrev MonadLogT (m : Type u → Type v) (n : Type v → Type w) :=
   ReaderT (MonadLog m) n

instance [Pure n] [Inhabited α] : Inhabited (MonadLogT m n α) :=
  ⟨fun _ => pure Inhabited.default⟩

instance [Monad n] [MonadLiftT m n] : MonadLog (MonadLogT m n) where
  getVerbosity := do (← read).getVerbosity
  log msg lv := do (← read).log msg lv

abbrev MonadLogT.adaptMethods [Monad n]
(f : MonadLog m → MonadLog m') (self : MonadLogT m' n α) : MonadLogT m n α :=
  ReaderT.adapt f self

abbrev LogIO :=
  MonadLogT BaseIO OptionIO

instance : MonadError LogIO := ⟨MonadLog.error⟩
instance : MonadLift IO LogIO := ⟨MonadError.runIO⟩

abbrev LogT (m : Type → Type) :=
  MonadLogT m m
