// 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 libraries

import (
	"fmt"

	"github.com/arduino/arduino-cli/arduino/cores"
	paths "github.com/arduino/go-paths-helper"
	properties "github.com/arduino/go-properties-orderedmap"
	semver "go.bug.st/relaxed-semver"
)

// MandatoryProperties FIXMEDOC
var MandatoryProperties = []string{"name", "version", "author", "maintainer"}

// OptionalProperties FIXMEDOC
var OptionalProperties = []string{"sentence", "paragraph", "url"}

// ValidCategories FIXMEDOC
var ValidCategories = map[string]bool{
	"Display":             true,
	"Communication":       true,
	"Signal Input/Output": true,
	"Sensors":             true,
	"Device Control":      true,
	"Timing":              true,
	"Data Storage":        true,
	"Data Processing":     true,
	"Other":               true,
	"Uncategorized":       true,
}

// Library represents a library in the system
type Library struct {
	Name          string
	Author        string
	Maintainer    string
	Sentence      string
	Paragraph     string
	Website       string
	Category      string
	Architectures []string

	Types []string `json:"types,omitempty"`

	InstallDir             *paths.Path
	SourceDir              *paths.Path
	UtilityDir             *paths.Path
	Location               LibraryLocation
	ContainerPlatform      *cores.PlatformRelease `json:""`
	Layout                 LibraryLayout
	RealName               string
	DotALinkage            bool
	Precompiled            bool
	PrecompiledWithSources bool
	LDflags                string
	IsLegacy               bool
	Version                *semver.Version
	License                string
	Properties             *properties.Map
	Examples               paths.PathList
	sourceHeaders          []string
}

func (library *Library) String() string {
	if library.Version.String() == "" {
		return library.Name
	}
	return library.Name + "@" + library.Version.String()
}

// SupportsAnyArchitectureIn returns true if any of the following is true:
// - the library supports at least one of the given architectures
// - the library is architecture independent
// - the library doesn't specify any `architecture` field in library.properties
func (library *Library) SupportsAnyArchitectureIn(archs ...string) bool {
	if library.IsArchitectureIndependent() {
		return true
	}
	for _, arch := range archs {
		if arch == "*" || library.IsOptimizedForArchitecture(arch) {
			return true
		}
	}
	return false
}

// IsOptimizedForArchitecture returns true if the library declares to be
// explicitly compatible for a specific architecture (the `architecture` field
// in library.properties contains the architecture passed as parameter)
func (library *Library) IsOptimizedForArchitecture(arch string) bool {
	for _, libArch := range library.Architectures {
		if libArch == arch {
			return true
		}
	}
	return false
}

// IsArchitectureIndependent returns true if the library declares to be
// compatible with all architectures (the `architecture` field in
// library.properties contains the `*` item)
func (library *Library) IsArchitectureIndependent() bool {
	return library.IsOptimizedForArchitecture("*") || library.Architectures == nil || len(library.Architectures) == 0
}

// SourceDir represents a source dir of a library
type SourceDir struct {
	Dir     *paths.Path
	Recurse bool
}

// SourceDirs return all the source directories of a library
func (library *Library) SourceDirs() []SourceDir {
	dirs := []SourceDir{}
	dirs = append(dirs,
		SourceDir{
			Dir:     library.SourceDir,
			Recurse: library.Layout == RecursiveLayout,
		})
	if library.UtilityDir != nil {
		dirs = append(dirs,
			SourceDir{
				Dir:     library.UtilityDir,
				Recurse: false,
			})
	}
	return dirs
}

// LocationPriorityFor returns a number representing the location priority for the given library
// using the given platform and referenced-platform. Higher value means higher priority.
func (library *Library) LocationPriorityFor(platformRelease, refPlatformRelease *cores.PlatformRelease) int {
	if library.Location == IDEBuiltIn {
		return 1
	} else if library.ContainerPlatform == refPlatformRelease {
		return 2
	} else if library.ContainerPlatform == platformRelease {
		return 3
	} else if library.Location == User {
		return 4
	}
	return 0
}

// SourceHeaders returns the C++ headers in the library.
func (library *Library) SourceHeaders() ([]string, error) {
	if library.sourceHeaders == nil {
		cppHeaders, err := library.SourceDir.ReadDir()
		if err != nil {
			return nil, fmt.Errorf("reading lib src dir: %s", err)
		}
		cppHeaders.FilterSuffix(".h", ".hpp", ".hh")
		res := []string{}
		for _, cppHeader := range cppHeaders {
			res = append(res, cppHeader.Base())
		}
		library.sourceHeaders = res
	}
	return library.sourceHeaders, nil
}
