// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package cores

import (
	"errors"
	"fmt"

	"github.com/pmylund/sortutil"
)

// Packages represents a set of Packages
type Packages map[string]*Package // Maps packager name to Package

// NewPackages creates a new Packages object
func NewPackages() Packages {
	return map[string]*Package{}
}

// PackageHelp contains info on how to reach maintainers for help
type PackageHelp struct {
	Online string `json:"online,omitempty"`
}

// Package represents a package in the system.
type Package struct {
	Name       string               // Name of the package.
	Maintainer string               // Name of the maintainer.
	WebsiteURL string               // Website of maintainer.
	URL        string               // origin URL for package index json file.
	Email      string               // Email of maintainer.
	Platforms  map[string]*Platform // The platforms in the system.
	Tools      map[string]*Tool     // The tools in the system.
	Help       PackageHelp          `json:"-"`
	Packages   Packages             `json:"-"`
}

// GetOrCreatePackage returns the specified Package or create an empty one
// filling all the cross-references
func (packages Packages) GetOrCreatePackage(packager string) *Package {
	if targetPackage, ok := packages[packager]; ok {
		return targetPackage
	}
	targetPackage := &Package{
		Name:      packager,
		Platforms: map[string]*Platform{},
		Tools:     map[string]*Tool{},
		Packages:  packages,
	}
	packages[packager] = targetPackage
	return targetPackage
}

// Names returns the array containing the name of the packages.
func (packages Packages) Names() []string {
	res := make([]string, len(packages))
	i := 0
	for n := range packages {
		res[i] = n
		i++
	}
	sortutil.CiAsc(res)
	return res
}

// GetDepsOfPlatformRelease returns the deps of a specified release of a core.
func (packages Packages) GetDepsOfPlatformRelease(release *PlatformRelease) ([]*ToolRelease, error) {
	if release == nil {
		return nil, errors.New("release cannot be nil")
	}
	ret := []*ToolRelease{}
	for _, dep := range release.Dependencies {
		pkg, exists := packages[dep.ToolPackager]
		if !exists {
			return nil, fmt.Errorf("package %s not found", dep.ToolPackager)
		}
		tool, exists := pkg.Tools[dep.ToolName]
		if !exists {
			return nil, fmt.Errorf("tool %s not found", dep.ToolName)
		}
		toolRelease, exists := tool.Releases[dep.ToolVersion.String()]
		if !exists {
			return nil, fmt.Errorf("tool version %s not found", dep.ToolVersion)
		}
		ret = append(ret, toolRelease)
	}
	return ret, nil
}

// GetOrCreatePlatform returns the Platform object with the specified architecture
// or creates a new one if not found
func (targetPackage *Package) GetOrCreatePlatform(architecture string) *Platform {
	if platform, ok := targetPackage.Platforms[architecture]; ok {
		return platform
	}
	targetPlatform := &Platform{
		Architecture: architecture,
		Releases:     map[string]*PlatformRelease{},
		Package:      targetPackage,
	}
	targetPackage.Platforms[architecture] = targetPlatform
	return targetPlatform
}

// GetOrCreateTool returns the Tool object with the specified name
// or creates a new one if not found
func (targetPackage *Package) GetOrCreateTool(name string) *Tool {
	if tool, ok := targetPackage.Tools[name]; ok {
		return tool
	}
	tool := &Tool{
		Name:     name,
		Package:  targetPackage,
		Releases: map[string]*ToolRelease{},
	}
	targetPackage.Tools[name] = tool
	return tool
}

func (targetPackage *Package) String() string {
	return targetPackage.Name
}

func (tdep ToolDependency) extractTool(sc Packages) (*Tool, error) {
	pkg, exists := sc[tdep.ToolPackager]
	if !exists {
		return nil, errors.New("package not found")
	}
	tool, exists := pkg.Tools[tdep.ToolName]
	if !exists {
		return nil, errors.New("tool not found")
	}
	return tool, nil
}

func (tdep ToolDependency) extractRelease(sc Packages) (*ToolRelease, error) {
	tool, err := tdep.extractTool(sc)
	if err != nil {
		return nil, err
	}
	release, exists := tool.Releases[tdep.ToolVersion.String()]
	if !exists {
		return nil, errors.New("release not found")
	}
	return release, nil
}
