//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package cpu

import (
	"fmt"

	"github.com/jaypipes/ghw/pkg/context"
	"github.com/jaypipes/ghw/pkg/marshal"
	"github.com/jaypipes/ghw/pkg/option"
)

// ProcessorCore describes a physical host processor core. A processor core is
// a separate processing unit within some types of central processing units
// (CPU).
type ProcessorCore struct {
	// ID is the `uint32` identifier that the host gave this core. Note that
	// this does *not* necessarily equate to a zero-based index of the core
	// within a physical package. For example, the core IDs for an Intel Core
	// i7 are 0, 1, 2, 8, 9, and 10
	ID int `json:"id"`
	// NumThreads is the number of hardware threads associated with the core
	NumThreads uint32 `json:"total_threads"`
	// LogicalProcessors is a slice of ints representing the logical processor
	// IDs assigned to any processing unit for the core. These are sometimes
	// called the "thread siblings". Logical processor IDs are the *zero-based*
	// index of the processor on the host and are *not* related to the core ID.
	LogicalProcessors []int `json:"logical_processors"`
}

// String returns a short string indicating important information about the
// processor core
func (c *ProcessorCore) String() string {
	return fmt.Sprintf(
		"processor core #%d (%d threads), logical processors %v",
		c.ID,
		c.NumThreads,
		c.LogicalProcessors,
	)
}

// Processor describes a physical host central processing unit (CPU).
type Processor struct {
	// ID is the physical processor `uint32` ID according to the system
	ID int `json:"id"`
	// NumCores is the number of physical cores in the processor package
	NumCores uint32 `json:"total_cores"`
	// NumThreads is the number of hardware threads in the processor package
	NumThreads uint32 `json:"total_threads"`
	// Vendor is a string containing the vendor name
	Vendor string `json:"vendor"`
	// Model` is a string containing the vendor's model name
	Model string `json:"model"`
	// Capabilities is a slice of strings indicating the features the processor
	// has enabled
	Capabilities []string `json:"capabilities"`
	// Cores is a slice of ProcessorCore` struct pointers that are packed onto
	// this physical processor
	Cores []*ProcessorCore `json:"cores"`
}

// CoreByID returns the ProcessorCore having the supplied ID.
func (p *Processor) CoreByID(coreID int) *ProcessorCore {
	for _, core := range p.Cores {
		if core.ID == coreID {
			return core
		}
	}
	return nil
}

// HasCapability returns true if the Processor has the supplied cpuid
// capability, false otherwise. Example of cpuid capabilities would be 'vmx' or
// 'sse4_2'. To see a list of potential cpuid capabilitiies, see the section on
// CPUID feature bits in the following article:
//
// https://en.wikipedia.org/wiki/CPUID
func (p *Processor) HasCapability(find string) bool {
	for _, c := range p.Capabilities {
		if c == find {
			return true
		}
	}
	return false
}

// String returns a short string describing the Processor
func (p *Processor) String() string {
	ncs := "cores"
	if p.NumCores == 1 {
		ncs = "core"
	}
	nts := "threads"
	if p.NumThreads == 1 {
		nts = "thread"
	}
	return fmt.Sprintf(
		"physical package #%d (%d %s, %d hardware %s)",
		p.ID,
		p.NumCores,
		ncs,
		p.NumThreads,
		nts,
	)
}

// Info describes all central processing unit (CPU) functionality on a host.
// Returned by the `ghw.CPU()` function.
type Info struct {
	ctx *context.Context
	// TotalCores is the total number of physical cores the host system
	// contains
	TotalCores uint32 `json:"total_cores"`
	// TotalThreads is the total number of hardware threads the host system
	// contains
	TotalThreads uint32 `json:"total_threads"`
	// Processors is a slice of Processor struct pointers, one for each
	// physical processor package contained in the host
	Processors []*Processor `json:"processors"`
}

// New returns a pointer to an Info struct that contains information about the
// CPUs on the host system
func New(opts ...*option.Option) (*Info, error) {
	ctx := context.New(opts...)
	info := &Info{ctx: ctx}
	if err := ctx.Do(info.load); err != nil {
		return nil, err
	}
	return info, nil
}

// String returns a short string indicating a summary of CPU information
func (i *Info) String() string {
	nps := "packages"
	if len(i.Processors) == 1 {
		nps = "package"
	}
	ncs := "cores"
	if i.TotalCores == 1 {
		ncs = "core"
	}
	nts := "threads"
	if i.TotalThreads == 1 {
		nts = "thread"
	}
	return fmt.Sprintf(
		"cpu (%d physical %s, %d %s, %d hardware %s)",
		len(i.Processors),
		nps,
		i.TotalCores,
		ncs,
		i.TotalThreads,
		nts,
	)
}

// simple private struct used to encapsulate cpu information in a top-level
// "cpu" YAML/JSON map/object key
type cpuPrinter struct {
	Info *Info `json:"cpu"`
}

// YAMLString returns a string with the cpu information formatted as YAML
// under a top-level "cpu:" key
func (i *Info) YAMLString() string {
	return marshal.SafeYAML(i.ctx, cpuPrinter{i})
}

// JSONString returns a string with the cpu information formatted as JSON
// under a top-level "cpu:" key
func (i *Info) JSONString(indent bool) string {
	return marshal.SafeJSON(i.ctx, cpuPrinter{i}, indent)
}
