/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich, Mac Malone
-/
import Std.Data.RBMap

open Std
namespace Lake

--------------------------------------------------------------------------------
-- # Topological / Suspending Builder
--------------------------------------------------------------------------------

-- ## Abstractions

/-- A recursive object builder. -/
def RecBuild.{u,v,w} (i : Type u) (o : Type v) (m : Type v → Type w) :=
  i → (i → m o) → m o

/-- A monad equipped with a key-object store. -/
class MonadStore (k : Type u) (o : outParam $ Type v) (m : Type v → Type w) where
  fetch? : k → m (Option o)
  store : k → o → m PUnit

export MonadStore (fetch? store)

instance [Monad m] [MonadStore k o m] : MonadStore k o (ExceptT ε m) where
  fetch? key := liftM (m := m) <| fetch? key
  store key obj := liftM (m := m) <| store key obj

-- ## Algorithm

/-- Auxiliary function for `buildTop`. -/
partial def buildTopCore [BEq k] [Inhabited o] [Monad m] [MonadStore k o m]
(parents : List k) (build : RecBuild i o (ExceptT (List k) m)) (keyOf : i → k) (info : i) : ExceptT (List k) m o := do
  let key := keyOf info
  -- detect cyclic builds
  if parents.contains key then
    throw <| key :: (parents.partition (· != key)).1 ++ [key]
  -- return previous output if already built
  if let some output ← fetch? key then
    return output
  -- build the key recursively
  let output ← build info <| buildTopCore (key :: parents) build keyOf
  -- save the output (to prevent repeated builds of the same key)
  store key output
  return output

/--
Recursively fills a `MonadStore` of key-object pairs by
building objects topologically (i.e., via a depth-first search with memoization).
If a cycle is detected, the list of keys traversed is thrown.

Called a suspending scheduler in "Build systems à la carte".
-/
def buildTop [BEq k] [Inhabited o] [Monad m] [MonadStore k o m]
(build : RecBuild i o (ExceptT (List k) m)) (keyOf : i → k) (info : i) : ExceptT (List k) m o :=
  buildTopCore [] build keyOf info

--------------------------------------------------------------------------------
-- # RBMap Version
--------------------------------------------------------------------------------

/-- A exception plus state monad transformer (i.e., `ExceptT` + `StateT`). -/
abbrev EStateT.{u,v} (ε : Type u) (σ : Type u) (m : Type u → Type v) :=
  ExceptT ε <| StateT σ m

namespace EStateT
variable {ε : Type u} {σ : Type u} {m : Type u → Type v}

def run (state : σ) (self : EStateT ε σ m α) : m (Except ε α × σ) :=
  ExceptT.run self |>.run state

def run' [Monad m] (state : σ) (self : EStateT ε σ m α) : m (Except ε α) :=
  ExceptT.run self |>.run' state

end EStateT

/-- A transformer that adds an `RBMap` store to a monad. -/
abbrev RBMapT.{u,v} (k : Type u) (o : Type u) (cmp : k → k → Ordering) (m : Type u → Type v) :=
  StateT (RBMap k o cmp) m

instance (cmp) [Monad m] : MonadStore k o (RBMapT k o cmp m) where
  fetch? key := do (← get).find? key
  store key obj := modify (·.insert key obj)

/-- Monad transformer for an `RBMap`-based topological walk. -/
abbrev RBTopT.{u,v} (k : Type u) (o : Type u) (cmp : k → k → Ordering) (m : Type u → Type v) :=
  EStateT (List k) (RBMap k o cmp) m

namespace RBTopT
variable {k : Type u} {o : Type u} {cmp : k → k → Ordering} {m : Type u → Type v}

def runWith [Monad m] (map : RBMap.{u,u} k o cmp) (self : RBTopT k o cmp m α) :=
  EStateT.run map self

def run [Monad m] (self : RBTopT k o cmp m α) :=
  self.runWith {}

def run' [Monad m] (self : RBTopT k o cmp m α) :=
  Functor.map (·.1) self.run

end RBTopT

/-- The `RBMap` version of `buildTop`. -/
def buildRBTop {k o} {cmp} {m} [BEq k] [Inhabited o] [Monad m]
(build : RecBuild i o (RBTopT k o cmp m)) (keyOf : i → k) (info : i) : RBTopT k o cmp m o :=
  buildTop build keyOf info
