// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package modconv

import (
	"fmt"
	"os"
	"runtime"
	"sort"
	"strings"

	"cmd/go/internal/base"

	"golang.org/x/mod/modfile"
	"golang.org/x/mod/module"
	"golang.org/x/mod/semver"
)

// ConvertLegacyConfig converts legacy config to modfile.
// The file argument is slash-delimited.
func ConvertLegacyConfig(f *modfile.File, file string, data []byte, queryPackage func(path, rev string) (module.Version, error)) error {
	i := strings.LastIndex(file, "/")
	j := -2
	if i >= 0 {
		j = strings.LastIndex(file[:i], "/")
	}
	convert := Converters[file[i+1:]]
	if convert == nil && j != -2 {
		convert = Converters[file[j+1:]]
	}
	if convert == nil {
		return fmt.Errorf("unknown legacy config file %s", file)
	}
	mf, err := convert(file, data)
	if err != nil {
		return fmt.Errorf("parsing %s: %v", file, err)
	}

	// Convert requirements block, which may use raw SHA1 hashes as versions,
	// to valid semver requirement list, respecting major versions.
	versions := make([]module.Version, len(mf.Require))
	replace := make(map[string]*modfile.Replace)

	for _, r := range mf.Replace {
		replace[r.New.Path] = r
		replace[r.Old.Path] = r
	}

	type token struct{}
	sem := make(chan token, runtime.GOMAXPROCS(0))
	for i, r := range mf.Require {
		m := r.Mod
		if m.Path == "" {
			continue
		}
		if re, ok := replace[m.Path]; ok {
			m = re.New
		}
		sem <- token{}
		go func(i int, m module.Version) {
			defer func() { <-sem }()
			version, err := queryPackage(m.Path, m.Version)
			if err != nil {
				fmt.Fprintf(os.Stderr, "go: converting %s: stat %s@%s: %v\n", base.ShortPath(file), m.Path, m.Version, err)
				return
			}

			versions[i] = version
		}(i, m)
	}
	// Fill semaphore channel to wait for all tasks to finish.
	for n := cap(sem); n > 0; n-- {
		sem <- token{}
	}

	need := map[string]string{}
	for _, v := range versions {
		if v.Path == "" {
			continue
		}
		// Don't use semver.Max here; need to preserve +incompatible suffix.
		if needv, ok := need[v.Path]; !ok || semver.Compare(needv, v.Version) < 0 {
			need[v.Path] = v.Version
		}
	}
	paths := make([]string, 0, len(need))
	for path := range need {
		paths = append(paths, path)
	}
	sort.Strings(paths)
	for _, path := range paths {
		if re, ok := replace[path]; ok {
			err := f.AddReplace(re.Old.Path, re.Old.Version, path, need[path])
			if err != nil {
				return fmt.Errorf("add replace: %v", err)
			}
		}
		f.AddNewRequire(path, need[path], false)
	}

	f.Cleanup()
	return nil
}
