// Copyright 2020, 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package runfiles

import (
	"regexp"
	"runtime"
	"sync"
)

// Rlocation returns the absolute path name of a runfile.  The runfile name
// must be a relative path, using the slash (not backslash) as directory
// separator.  If the runfiles manifest maps s to an empty name (indicating an
// empty runfile not present in the filesystem), Rlocation returns an error
// that wraps [ErrEmpty].
func Rlocation(path string) (string, error) {
	return RlocationFrom(path, CallerRepository())
}

func RlocationFrom(path string, sourceRepo string) (string, error) {
	r, err := g.get()
	if err != nil {
		return "", err
	}
	return r.WithSourceRepo(sourceRepo).Rlocation(path)
}

// Env returns additional environmental variables to pass to subprocesses.
// Each element is of the form “key=value”.  Pass these variables to
// Bazel-built binaries so they can find their runfiles as well.  See the
// [Runfiles] example for an illustration of this.
//
// The return value is a newly-allocated slice; you can modify it at will.
func Env() ([]string, error) {
	r, err := g.get()
	if err != nil {
		return nil, err
	}
	return r.Env(), nil
}

var legacyExternalGeneratedFile = regexp.MustCompile(`^bazel-out[/][^/]+/bin/external/([^/]+)/`)
var legacyExternalFile = regexp.MustCompile(`^external/([^/]+)/`)

// CurrentRepository returns the canonical name of the Bazel repository that
// contains the source file of the caller of CurrentRepository.
func CurrentRepository() string {
	return callerRepository(1)
}

// CallerRepository returns the canonical name of the Bazel repository that
// contains the source file of the caller of the function that itself calls
// CallerRepository.
func CallerRepository() string {
	return callerRepository(2)
}

func callerRepository(skip int) string {
	_, file, _, _ := runtime.Caller(skip + 1)
	if match := legacyExternalGeneratedFile.FindStringSubmatch(file); match != nil {
		return match[1]
	}
	if match := legacyExternalFile.FindStringSubmatch(file); match != nil {
		return match[1]
	}
	// If a file is not in an external repository, it is in the main repository,
	// which has the empty string as its canonical name.
	return ""
}

type global struct {
	once     sync.Once
	runfiles *Runfiles
	err      error
}

func (g *global) get() (*Runfiles, error) {
	g.once.Do(g.init)
	return g.runfiles, g.err
}

func (g *global) init() {
	g.runfiles, g.err = New()
}

var g global
