package system

import (
	"fmt"

	"github.com/influxdb/telegraf/plugins"
	"github.com/influxdb/telegraf/plugins/system/ps/cpu"
)

type CPUStats struct {
	ps        PS
	lastStats []cpu.CPUTimesStat

	PerCPU   bool `toml:"percpu"`
	TotalCPU bool `toml:"totalcpu"`
}

func NewCPUStats(ps PS) *CPUStats {
	return &CPUStats{
		ps: ps,
	}
}

func (_ *CPUStats) Description() string {
	return "Read metrics about cpu usage"
}

var sampleConfig = `
	# Whether to report per-cpu stats or not
	percpu = true
	# Whether to report total system cpu stats or not
	totalcpu = true
`

func (_ *CPUStats) SampleConfig() string {
	return sampleConfig
}

func (s *CPUStats) Gather(acc plugins.Accumulator) error {
	times, err := s.ps.CPUTimes(s.PerCPU, s.TotalCPU)
	if err != nil {
		return fmt.Errorf("error getting CPU info: %s", err)
	}

	for i, cts := range times {
		tags := map[string]string{
			"cpu": cts.CPU,
		}

		busy, total := busyAndTotalCpuTime(cts)

		// Add total cpu numbers
		add(acc, "user", cts.User, tags)
		add(acc, "system", cts.System, tags)
		add(acc, "idle", cts.Idle, tags)
		add(acc, "nice", cts.Nice, tags)
		add(acc, "iowait", cts.Iowait, tags)
		add(acc, "irq", cts.Irq, tags)
		add(acc, "softirq", cts.Softirq, tags)
		add(acc, "steal", cts.Steal, tags)
		add(acc, "guest", cts.Guest, tags)
		add(acc, "guestNice", cts.GuestNice, tags)
		add(acc, "stolen", cts.Stolen, tags)
		add(acc, "busy", busy, tags)

		// Add in percentage
		if len(s.lastStats) == 0 {
			// If it's the 1st gather, can't get CPU stats yet
			continue
		}
		lastCts := s.lastStats[i]
		lastBusy, lastTotal := busyAndTotalCpuTime(lastCts)
		busyDelta := busy - lastBusy
		totalDelta := total - lastTotal

		if totalDelta < 0 {
			return fmt.Errorf("Error: current total CPU time is less than previous total CPU time")
		}

		if totalDelta == 0 {
			continue
		}

		add(acc, "percentageUser", 100*(cts.User-lastCts.User)/totalDelta, tags)
		add(acc, "percentageSystem", 100*(cts.System-lastCts.System)/totalDelta, tags)
		add(acc, "percentageIdle", 100*(cts.Idle-lastCts.Idle)/totalDelta, tags)
		add(acc, "percentageNice", 100*(cts.Nice-lastCts.Nice)/totalDelta, tags)
		add(acc, "percentageIowait", 100*(cts.Iowait-lastCts.Iowait)/totalDelta, tags)
		add(acc, "percentageIrq", 100*(cts.Irq-lastCts.Irq)/totalDelta, tags)
		add(acc, "percentageSoftirq", 100*(cts.Softirq-lastCts.Softirq)/totalDelta, tags)
		add(acc, "percentageSteal", 100*(cts.Steal-lastCts.Steal)/totalDelta, tags)
		add(acc, "percentageGuest", 100*(cts.Guest-lastCts.Guest)/totalDelta, tags)
		add(acc, "percentageGuestNice", 100*(cts.GuestNice-lastCts.GuestNice)/totalDelta, tags)
		add(acc, "percentageStolen", 100*(cts.Stolen-lastCts.Stolen)/totalDelta, tags)

		add(acc, "percentageBusy", 100*busyDelta/totalDelta, tags)

	}

	s.lastStats = times

	return nil
}

func busyAndTotalCpuTime(t cpu.CPUTimesStat) (float64, float64) {
	busy := t.User + t.System + t.Nice + t.Iowait + t.Irq + t.Softirq + t.Steal +
		t.Guest + t.GuestNice + t.Stolen

	return busy, busy + t.Idle
}

func init() {
	plugins.Add("cpu", func() plugins.Plugin {
		return &CPUStats{ps: &systemPS{}}
	})
}