package procstat

import (
	"fmt"
	"log"

	"github.com/shirou/gopsutil/process"

	"github.com/influxdb/telegraf/plugins"
)

type SpecProcessor struct {
	Prefix string
	tags   map[string]string
	acc    plugins.Accumulator
	proc   *process.Process
}

func (p *SpecProcessor) add(metric string, value interface{}) {
	var mname string
	if p.Prefix == "" {
		mname = metric
	} else {
		mname = p.Prefix + "_" + metric
	}
	p.acc.Add(mname, value, p.tags)
}

func NewSpecProcessor(
	prefix string,
	acc plugins.Accumulator,
	p *process.Process,
) *SpecProcessor {
	tags := make(map[string]string)
	tags["pid"] = fmt.Sprintf("%v", p.Pid)
	if name, err := p.Name(); err == nil {
		tags["name"] = name
	}
	return &SpecProcessor{
		Prefix: prefix,
		tags:   tags,
		acc:    acc,
		proc:   p,
	}
}

func (p *SpecProcessor) pushMetrics() {
	if err := p.pushFDStats(); err != nil {
		log.Printf("procstat, fd stats not available: %s", err.Error())
	}
	if err := p.pushCtxStats(); err != nil {
		log.Printf("procstat, ctx stats not available: %s", err.Error())
	}
	if err := p.pushIOStats(); err != nil {
		log.Printf("procstat, io stats not available: %s", err.Error())
	}
	if err := p.pushCPUStats(); err != nil {
		log.Printf("procstat, cpu stats not available: %s", err.Error())
	}
	if err := p.pushMemoryStats(); err != nil {
		log.Printf("procstat, mem stats not available: %s", err.Error())
	}
}

func (p *SpecProcessor) pushFDStats() error {
	fds, err := p.proc.NumFDs()
	if err != nil {
		return fmt.Errorf("NumFD error: %s\n", err)
	}
	p.add("num_fds", fds)
	return nil
}

func (p *SpecProcessor) pushCtxStats() error {
	ctx, err := p.proc.NumCtxSwitches()
	if err != nil {
		return fmt.Errorf("ContextSwitch error: %s\n", err)
	}
	p.add("voluntary_context_switches", ctx.Voluntary)
	p.add("involuntary_context_switches", ctx.Involuntary)
	return nil
}

func (p *SpecProcessor) pushIOStats() error {
	io, err := p.proc.IOCounters()
	if err != nil {
		return fmt.Errorf("IOCounters error: %s\n", err)
	}
	p.add("read_count", io.ReadCount)
	p.add("write_count", io.WriteCount)
	p.add("read_bytes", io.ReadBytes)
	p.add("write_bytes", io.WriteCount)
	return nil
}

func (p *SpecProcessor) pushCPUStats() error {
	cpu, err := p.proc.CPUTimes()
	if err != nil {
		return err
	}
	p.add("cpu_user", cpu.User)
	p.add("cpu_system", cpu.System)
	p.add("cpu_idle", cpu.Idle)
	p.add("cpu_nice", cpu.Nice)
	p.add("cpu_iowait", cpu.Iowait)
	p.add("cpu_irq", cpu.Irq)
	p.add("cpu_soft_irq", cpu.Softirq)
	p.add("cpu_soft_steal", cpu.Steal)
	p.add("cpu_soft_stolen", cpu.Stolen)
	p.add("cpu_soft_guest", cpu.Guest)
	p.add("cpu_soft_guest_nice", cpu.GuestNice)
	return nil
}

func (p *SpecProcessor) pushMemoryStats() error {
	mem, err := p.proc.MemoryInfo()
	if err != nil {
		return err
	}
	p.add("memory_rss", mem.RSS)
	p.add("memory_vms", mem.VMS)
	p.add("memory_swap", mem.Swap)
	return nil
}