/-
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

namespace Lake

inductive LogLevel
| info
| warning
| error

-- # Class

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

export MonadLog (log)

abbrev logInfo [MonadLog m] (message : String) : m PUnit :=
  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 :=
  ⟨fun _ _ => pure ()⟩

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

def io [MonadLiftT BaseIO m] : MonadLog m where
  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] : MonadLog m where
  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
  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
  log msg lv := do (← read).log msg lv

namespace MonadLogT

abbrev run (methods : MonadLog m) (self : MonadLogT m n α) : n α :=
  ReaderT.run self methods

abbrev runWith (methods : MonadLog m) (self : MonadLogT m n α) : n α :=
  ReaderT.run self methods

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

end MonadLogT

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

namespace LogT

def run (methods : MonadLog m) (self : LogT m α) : m α :=
  ReaderT.run self methods

def runWith (methods : MonadLog m) (self : LogT m α) : m α :=
  ReaderT.run self methods

end LogT
