/-
Copyright (c) 2022 Mac Malone. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mac Malone
-/
import Lake.Util.Async
import Lake.Build.Trace
import Lake.Build.Context

open System
namespace Lake

/-- A Lake job. -/
abbrev Job α := OptionIOTask α

/-- The monad of Lake jobs. -/
abbrev JobM := BuildM

/-- The monad of a finished Lake job. -/
abbrev ResultM := OptionIO

namespace Job

@[inline] def nil : Job Unit :=
 pure ()

@[inline] protected def async (act : JobM α) : SchedulerM (Job α) :=
  async act

@[inline] protected def await (self : Job α) : ResultM α :=
  await self

@[inline] protected def bindSync
(self : Job α) (f : α → JobM β) : SchedulerM (Job β) :=
  bindSync self f

@[inline] protected def bindAsync
(self : Job α) (f : α → SchedulerM (Job β)) : SchedulerM (Job β)  :=
  bindAsync self f

end Job

/-- A Lake build job. -/
abbrev BuildJob α := Job (α × BuildTrace)

namespace BuildJob

@[inline] def mk (job : Job (α × BuildTrace)) : BuildJob α :=
  job

@[inline] def ofJob (self : Job BuildTrace) : BuildJob Unit :=
  mk <| ((), ·) <$> self

@[inline] def toJob (self : BuildJob α) : Job (α × BuildTrace) :=
  self

@[inline] def nil : BuildJob Unit :=
  mk <| pure ((), nilTrace)

@[inline] protected def pure (a : α) : BuildJob α :=
  mk <| pure (a, nilTrace)

instance : Pure BuildJob := ⟨BuildJob.pure⟩

@[inline] protected def map (f : α → β) (self : BuildJob α) : BuildJob β :=
  mk <| (fun (a,t) => (f a,t)) <$> self.toJob

instance : Functor BuildJob where
  map := BuildJob.map

@[inline] protected def bindSync
(self : BuildJob α) (f : α → BuildTrace → JobM β) : SchedulerM (Job β) :=
  self.toJob.bindSync fun (a, t) => f a t

@[inline] protected def bindAsync
(self : BuildJob α) (f : α → BuildTrace → SchedulerM (Job β)) : SchedulerM (Job β)  :=
  self.toJob.bindAsync fun (a, t) => f a t

@[inline] protected def await (self : BuildJob α) : ResultM α :=
  (·.1) <$> await self.toJob

instance : Await BuildJob ResultM := ⟨BuildJob.await⟩

@[inline] def materialize (self : BuildJob α) : ResultM Unit :=
  discard <| await self.toJob

def mix (t1 : BuildJob α) (t2 : BuildJob β) : BaseIO (BuildJob Unit) :=
  mk <$> seqWithAsync (fun (_,t) (_,t') => ((), mixTrace t t')) t1.toJob t2.toJob

def mixList (jobs : List (BuildJob α)) : BaseIO (BuildJob Unit) := ofJob <$> do
  jobs.foldrM (init := pure nilTrace) fun j a =>
    seqWithAsync (fun (_,t') t => mixTrace t t') j.toJob a

def mixArray (jobs : Array (BuildJob α)) : BaseIO (BuildJob Unit) := ofJob <$> do
  jobs.foldlM (init := pure nilTrace) fun a j =>
    seqWithAsync (fun t (_,t') => mixTrace t t') a j.toJob

protected def seqWithAsync
(f : α → β → γ) (t1 : BuildJob α) (t2 : BuildJob β) : BaseIO (BuildJob γ) :=
  mk <$> seqWithAsync (fun (a,t) (b,t') => (f a b, mixTrace t t')) t1.toJob t2.toJob

instance : SeqWithAsync BaseIO BuildJob := ⟨BuildJob.seqWithAsync⟩

def collectList (jobs : List (BuildJob α)) : BaseIO (BuildJob (List α)) :=
  jobs.foldrM (seqWithAsync List.cons) (pure [])

def collectArray (jobs : Array (BuildJob α)) : BaseIO (BuildJob (Array α)) :=
  jobs.foldlM (seqWithAsync Array.push) (pure #[])
