fs = require 'fs'
{Directory} = require 'pathwatcher'
GitRepository = require './git-repository'

# Returns the .gitdir path in the agnostic Git symlink .git file given, or
# null if the path is not a valid gitfile.
#
# * `gitFile` {String} path of gitfile to parse
gitFileRegex = RegExp "^gitdir: (.+)"
pathFromGitFile = (gitFile) ->
  try
    gitFileBuff = fs.readFileSync(gitFile, 'utf8')
    return gitFileBuff?.match(gitFileRegex)[1]

# Checks whether a valid `.git` directory is contained within the given
# directory or one of its ancestors. If so, a Directory that corresponds to the
# `.git` folder will be returned. Otherwise, returns `null`.
#
# * `directory` {Directory} to explore whether it is part of a Git repository.
findGitDirectorySync = (directory) ->
  # TODO: Fix node-pathwatcher/src/directory.coffee so the following methods
  # can return cached values rather than always returning new objects:
  # getParent(), getFile(), getSubdirectory().
  gitDir = directory.getSubdirectory('.git')
  gitDirPath = pathFromGitFile(gitDir.getPath?())
  if gitDirPath
    gitDir = new Directory(directory.resolve(gitDirPath))
  if gitDir.existsSync?() and isValidGitDirectorySync gitDir
    gitDir
  else if directory.isRoot()
    return null
  else
    findGitDirectorySync directory.getParent()

# Returns a boolean indicating whether the specified directory represents a Git
# repository.
#
# * `directory` {Directory} whose base name is `.git`.
isValidGitDirectorySync = (directory) ->
  # To decide whether a directory has a valid .git folder, we use
  # the heuristic adopted by the valid_repository_path() function defined in
  # node_modules/git-utils/deps/libgit2/src/repository.c.
  return directory.getSubdirectory('objects').existsSync() and
      directory.getFile('HEAD').existsSync() and
      directory.getSubdirectory('refs').existsSync()

# Provider that conforms to the atom.repository-provider@0.1.0 service.
module.exports =
class GitRepositoryProvider

  constructor: (@project, @config) ->
    # Keys are real paths that end in `.git`.
    # Values are the corresponding GitRepository objects.
    @pathToRepository = {}

  # Returns a {Promise} that resolves with either:
  # * {GitRepository} if the given directory has a Git repository.
  # * `null` if the given directory does not have a Git repository.
  repositoryForDirectory: (directory) ->
    # TODO: Currently, this method is designed to be async, but it relies on a
    # synchronous API. It should be rewritten to be truly async.
    Promise.resolve(@repositoryForDirectorySync(directory))

  # Returns either:
  # * {GitRepository} if the given directory has a Git repository.
  # * `null` if the given directory does not have a Git repository.
  repositoryForDirectorySync: (directory) ->
    # Only one GitRepository should be created for each .git folder. Therefore,
    # we must check directory and its parent directories to find the nearest
    # .git folder.
    gitDir = findGitDirectorySync(directory)
    unless gitDir
      return null

    gitDirPath = gitDir.getPath()
    repo = @pathToRepository[gitDirPath]
    unless repo
      repo = GitRepository.open(gitDirPath, {@project, @config})
      return null unless repo
      repo.onDidDestroy(=> delete @pathToRepository[gitDirPath])
      @pathToRepository[gitDirPath] = repo
      repo.refreshIndex()
      repo.refreshStatus()
    repo
