/-
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.BuildTask
import Lake.BuildTrace

namespace Lake

--------------------------------------------------------------------------------
-- # Target
--------------------------------------------------------------------------------

structure Target (t : Type) (m : Type) (a : Type) where
  artifact  : a
  trace     : t
  task      : m
  deriving Inhabited

namespace Target

def withTrace (trace : t) (self : Target r m a) : Target t m a :=
  {self with trace := trace}

def discardTrace (self : Target t m a) : Target PUnit m a :=
  self.withTrace ()

def withArtifact (artifact : a) (self : Target t m b) : Target t m a :=
  {self with artifact := artifact}

def discardArtifact (self : Target t m α) : Target t m PUnit :=
  self.withArtifact ()

def mtime (self : Target MTime m a) : MTime :=
  self.trace

end Target

--------------------------------------------------------------------------------
-- # Build Targets
--------------------------------------------------------------------------------

-- ## Build Target

abbrev BuildTarget (t : Type) (a : Type) := Target t (IO BuildTask) a

namespace BuildTarget

def mk (artifact : a) (trace : t) (task : IO BuildTask) : BuildTarget t a :=
  ⟨artifact, trace, task⟩

def opaque (trace : t) (task : IO BuildTask) : BuildTarget t PUnit :=
  ⟨(), trace, task⟩

def pure (artifact : a) (trace : t) : BuildTarget t a :=
  ⟨artifact, trace, BuildTask.nop⟩

def nil [Inhabited t] : BuildTarget t PUnit :=
  pure () Inhabited.default

def spawn (self : BuildTarget t α) : IO BuildTask :=
  self.task

def materialize (self : BuildTarget t α) : IO PUnit := do
  (← self.task).await

end BuildTarget

-- ## Active Build Target

abbrev ActiveBuildTarget (t : Type) (a : Type) := Target t BuildTask a

namespace ActiveBuildTarget

def mk (artifact : a) (trace : t) (task : BuildTask) : ActiveBuildTarget t a :=
  ⟨artifact, trace, task⟩

def opaque (trace : t) (task : BuildTask) : ActiveBuildTarget t PUnit :=
  ⟨(), trace, task⟩

def pure (artifact : a) (trace : t) : ActiveBuildTarget t a :=
  ⟨artifact, trace, BuildTask.nop⟩

def nil [Inhabited t] : ActiveBuildTarget t PUnit :=
  pure () Inhabited.default

def materialize (self : ActiveBuildTarget t α) : IO PUnit :=
  self.task.await

-- ### Combinators

def after (target : ActiveBuildTarget t a) (act : IO PUnit)  : IO BuildTask :=
  afterTask target.task act

def afterList (targets : List (ActiveBuildTarget t a)) (act : IO PUnit) : IO BuildTask :=
  afterTaskList (targets.map (·.task)) act

instance : HAndThen (ActiveBuildTarget t a) (IO PUnit) (IO BuildTask) :=
  ⟨ActiveBuildTarget.after⟩

instance : HAndThen (List (ActiveBuildTarget t a)) (IO PUnit) (IO BuildTask) :=
  ⟨ActiveBuildTarget.afterList⟩

end ActiveBuildTarget

--------------------------------------------------------------------------------
-- # File Targets
--------------------------------------------------------------------------------

section
open System

-- ## File Target

abbrev FileTarget := BuildTarget MTime FilePath

namespace FileTarget

def mk (file : FilePath) (depMTime : MTime) (task : IO BuildTask) : FileTarget :=
  ⟨file, depMTime, task⟩

def pure (file : FilePath) (depMTime : MTime) : FileTarget :=
  BuildTarget.pure file depMTime

end FileTarget

-- ## Active File Target

abbrev ActiveFileTarget := ActiveBuildTarget MTime FilePath

namespace ActiveFileTarget

def mk (file : FilePath) (depMTime : MTime) (task : BuildTask) : ActiveFileTarget :=
  ActiveBuildTarget.mk file depMTime task

def pure (file : FilePath) (depMTime : MTime) : ActiveFileTarget :=
  ActiveBuildTarget.pure file depMTime

end ActiveFileTarget

end

--------------------------------------------------------------------------------
-- # Lean Target
--------------------------------------------------------------------------------

abbrev LeanTarget a := ActiveBuildTarget LeanTrace a

namespace LeanTarget

def hash (self : LeanTarget a) := self.trace.hash
def mtime (self : LeanTarget a) := self.trace.mtime

def all (targets : List (LeanTarget a)) : IO (LeanTarget PUnit) := do
  let hash := Hash.foldList 0 <| targets.map (·.hash)
  let mtime := MTime.listMax <| targets.map (·.mtime)
  let task ← BuildTask.all <| targets.map (·.task)
  return ActiveBuildTarget.mk () ⟨hash, mtime⟩ task

def fromMTimeTarget (target : ActiveBuildTarget MTime a) : LeanTarget a :=
  {target with trace := LeanTrace.fromMTime target.trace}

end LeanTarget
