/*
** Zabbix
** Copyright (C) 2001-2022 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

package cpu

import (
	"encoding/json"
	"errors"
	"strconv"

	"zabbix.com/pkg/zbxerr"
)

const pluginName = "Cpu"

var impl Plugin

const (
	maxHistory = 60*15 + 2
)

const (
	cpuStatusOffline = 1 << iota
	cpuStatusOnline
)

var cpuStatuses [3]string = [3]string{"", "offline", "online"}

type cpuCounter int
type historyIndex int

func (h historyIndex) inc() historyIndex {
	h++
	if h == maxHistory {
		h = 0
	}
	return h
}

func (h historyIndex) dec() historyIndex {
	h--
	if h < 0 {
		h = maxHistory - 1
	}
	return h
}

func (h historyIndex) sub(value historyIndex) historyIndex {
	h -= value
	for h < 0 {
		h += maxHistory
	}
	return h
}

type cpuUnit struct {
	index      int
	head, tail historyIndex
	history    [maxHistory]cpuCounters
	status     int
}

type cpuDiscovery struct {
	Number int    `json:"{#CPU.NUMBER}"`
	Status string `json:"{#CPU.STATUS}"`
}

func (p *Plugin) Period() int {
	return 1
}

func (p *Plugin) getCpuDiscovery(params []string) (result interface{}, err error) {
	if len(params) > 0 {
		return nil, errors.New("Too many parameters.")
	}
	cpus := make([]*cpuDiscovery, 0, len(p.cpus))
	// 0 index is for the overall stats, skip for discovery
	for i := 1; i < len(p.cpus); i++ {
		cpu := p.cpus[i]
		cpus = append(cpus, &cpuDiscovery{Number: cpu.index, Status: cpuStatuses[cpu.status]})
	}
	var b []byte
	if b, err = json.Marshal(&cpus); err != nil {
		return
	}
	return string(b), nil
}

func (p *Plugin) getCpuNum(params []string) (result interface{}, err error) {
	switch len(params) {
	case 1:
		switch params[0] {
		case "", "online":
			return numCPUOnline(), nil
		case "max":
			return numCPUConf(), nil
		default:
			return nil, errors.New("Invalid first parameter.")
		}
	case 0:
		return numCPUOnline(), nil
	default:
		return nil, zbxerr.ErrorTooManyParameters
	}
}

func periodByMode(mode string) (period historyIndex) {
	switch mode {
	case "", "avg1":
		return 60
	case "avg5":
		return 60 * 5
	case "avg15":
		return 60 * 15
	default:
		return -1
	}
}

func indexByCpu(cpu string) (index int) {
	if cpu == "" || cpu == "all" {
		return 0
	}
	if i, err := strconv.ParseInt(cpu, 10, 32); err != nil {
		return -1
	} else {
		return int(i) + 1
	}
}

func (p *Plugin) getCpuUtil(params []string) (result interface{}, err error) {
	var index int
	var counter cpuCounter
	period := historyIndex(60)
	switch len(params) {
	case 3: // mode parameter
		if period = periodByMode(params[2]); period < 0 {
			return nil, errors.New("Invalid third parameter.")
		}
		fallthrough
	case 2: // type parameter
		if counter = counterByType(params[1]); counter == counterUnknown {
			return nil, errors.New("Invalid second parameter.")
		}
		fallthrough
	case 1: // cpu number or all;
		if index = indexByCpu(params[0]); index < 0 || index >= len(p.cpus) {
			return nil, errors.New("Invalid first parameter.")
		}
	case 0:
	default:
		return nil, errors.New("Too many parameters.")
	}

	cpu := p.cpus[index]
	if cpu.status == cpuStatusOffline {
		return nil, errors.New("CPU is offline.")
	}

	return cpu.counterAverage(counter, period, 1), nil
}

func (p *Plugin) newCpus(num int) (cpus []*cpuUnit) {
	cpus = make([]*cpuUnit, num+1)
	for i := 0; i < len(cpus); i++ {
		cpus[i] = &cpuUnit{
			index:  i - 1,
			status: cpuStatusOffline,
		}
	}
	return
}
