/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner, Sebastian Ullrich, Mac Malone
-/
import Lake.Util.Git
import Lake.Load.Manifest
import Lake.Config.Dependency

open Std System Lean

namespace Lake

/-- Update the Git package in `repo` to `rev` if not already at it. -/
def updateGitPkg (repo : GitRepo) (rev? : Option String) : LogIO PUnit := do
  let rev ← repo.findRemoteRevision rev?
  if (← repo.headRevision) == rev then return
  logInfo s!"updating {repo} to revision {rev}"
  repo.checkoutDetach rev

/-- Clone the Git package as `repo`. -/
def cloneGitPkg (repo : GitRepo) (url : String) (rev? : Option String) : LogIO PUnit := do
  logInfo s!"cloning {url} to {repo}"
  repo.clone url
  if let some rev := rev? then
    let hash ← repo.resolveRemoteRevision rev
    repo.checkoutDetach hash

/--
Materializes a Git package in `dir`, cloning and/or updating it as necessary.

Attempts to reproduce the `PackageEntry` in the manifest (if one exists) unless
`shouldUpdate` is true. Otherwise, produces the package based on `url` and `rev`
and returns a record of the result to later save into the manifest.
-/
def materializeGitPkg (name : String) (dir : FilePath)
(url : String) (rev? : Option String) (manifestEntry? : Option PackageEntry)
 : LogIO PackageEntry := do
  let repo := GitRepo.mk dir
  if let some entry := manifestEntry? then
    if entry.shouldUpdate then
      if (← repo.dirExists) then
        if url = entry.url then
          updateGitPkg repo rev?
        else
          logInfo s!"{name}: URL has changed; deleting {dir} and cloning again"
          IO.FS.removeDirAll dir
          cloneGitPkg repo url rev?
      else
        cloneGitPkg repo url rev?
      let rev ← repo.headRevision
      return {entry with url, rev, inputRev? := rev?}
    else
      if let some rev := rev? then
        if let some inputRev := entry.inputRev? then
          if inputRev ≠ rev then
            logWarning <|
              s!"{name}: revision `{inputRev}` listed in manifest " ++
              s!"does not match `{rev}` listed in the configuration file; " ++
              "you may wish to run `lake update` to update"
      if (← repo.dirExists) then
        if url = entry.url then
          /-
          Do not update (fetch remote) if already on revision
          Avoids errors when offline e.g. [leanprover/lake#104][104]
          [104]: https://github.com/leanprover/lake/issues/104
          -/
          unless (← repo.headRevision) = entry.rev do
            updateGitPkg repo entry.rev
        else
          logWarning <|
            s!"{name}: URL has changed; " ++
            "still using old package, use `lake update` to update"
      else
        cloneGitPkg repo entry.url entry.rev
      return entry
  else
    if (← repo.dirExists) then
      logInfo s!"{name}: no manifest entry; deleting {dir} and cloning again"
      IO.FS.removeDirAll dir
      cloneGitPkg repo url rev?
    else
      cloneGitPkg repo url rev?
    let rev ← repo.headRevision
    return {name, url, rev, inputRev? := rev?}

structure MaterializeResult where
  pkgDir : FilePath
  remoteUrl? : Option String
  gitTag? : Option String
  manifestEntry? : Option PackageEntry

/--
Materializes a `Dependency`, downloading nd/or updating it as necessary.
Local dependencies are materialized relative to `localRoot` and remote
dependencies are stored in `packagesDir`.
-/
def materializeDep (packagesDir localRoot : FilePath)
(dep : Dependency) (manifestEntry? : Option PackageEntry)
: LogIO MaterializeResult :=
  match dep.src with
  | Source.path dir =>
    return ⟨localRoot / dir, none, none, none⟩
  | Source.git url rev? subDir? => do
    let name := dep.name.toString (escape := false)
    let gitDir := packagesDir / name
    let entry? ← materializeGitPkg name gitDir url rev? manifestEntry?
    let pkgDir :=
      match subDir? with
      | some subDir => gitDir / subDir
      | none => gitDir
    let tag? ← GitRepo.mk gitDir |>.findTag?
    let url? := Git.filterUrl? url
    return ⟨pkgDir, url?, tag?, entry?⟩
