package metrics

import (
	"context"

	"github.com/prometheus/procfs"
	"go.opencensus.io/stats"
	"go.opencensus.io/stats/view"
)

// A ProcessCollector collects stats about a process.
type ProcessCollector struct {
	cpuTotal  *stats.Float64Measure
	openFDs   *stats.Int64Measure
	maxFDs    *stats.Int64Measure
	vsize     *stats.Int64Measure
	maxVsize  *stats.Int64Measure
	rss       *stats.Int64Measure
	startTime *stats.Float64Measure
	views     []*view.View
}

// NewProcessCollector creates a new ProcessCollector.
func NewProcessCollector(name string) *ProcessCollector {
	pc := &ProcessCollector{
		cpuTotal: stats.Float64(
			name+"_process_cpu_seconds_total",
			"Total user and system CPU time spent in seconds.",
			stats.UnitSeconds,
		),
		openFDs: stats.Int64(
			name+"_process_open_fds",
			"Number of open file descriptors.",
			"{file_descriptor}",
		),
		maxFDs: stats.Int64(
			name+"_process_max_fds",
			"Maximum number of open file descriptors.",
			"{file_descriptor}",
		),
		vsize: stats.Int64(
			name+"_process_virtual_memory_bytes",
			"Virtual memory size in bytes.",
			stats.UnitBytes,
		),
		maxVsize: stats.Int64(
			name+"_process_virtual_memory_max_bytes",
			"Maximum amount of virtual memory available in bytes.",
			stats.UnitBytes,
		),
		rss: stats.Int64(
			name+"_process_resident_memory_bytes",
			"Resident memory size in bytes.",
			stats.UnitBytes,
		),
		startTime: stats.Float64(
			name+"_process_start_time_seconds",
			"Start time of the process since unix epoch in seconds.",
			stats.UnitSeconds,
		),
	}
	pc.views = []*view.View{
		{
			Name:        pc.cpuTotal.Name(),
			Description: pc.cpuTotal.Description(),
			Measure:     pc.cpuTotal,
			Aggregation: view.LastValue(),
		},
		{
			Name:        pc.openFDs.Name(),
			Description: pc.openFDs.Description(),
			Measure:     pc.openFDs,
			Aggregation: view.LastValue(),
		},
		{
			Name:        pc.maxFDs.Name(),
			Description: pc.maxFDs.Description(),
			Measure:     pc.maxFDs,
			Aggregation: view.LastValue(),
		},
		{
			Name:        pc.vsize.Name(),
			Description: pc.vsize.Description(),
			Measure:     pc.vsize,
			Aggregation: view.LastValue(),
		},
		{
			Name:        pc.maxVsize.Name(),
			Description: pc.maxVsize.Description(),
			Measure:     pc.maxVsize,
			Aggregation: view.LastValue(),
		},
		{
			Name:        pc.rss.Name(),
			Description: pc.rss.Description(),
			Measure:     pc.rss,
			Aggregation: view.LastValue(),
		},
		{
			Name:        pc.startTime.Name(),
			Description: pc.startTime.Description(),
			Measure:     pc.startTime,
			Aggregation: view.LastValue(),
		},
	}
	return pc
}

// Views returns the views for the process collector.
func (pc *ProcessCollector) Views() []*view.View {
	return pc.views
}

// Measure measures the stats for a process.
func (pc *ProcessCollector) Measure(ctx context.Context, pid int) error {
	proc, err := procfs.NewProc(pid)
	if err != nil {
		return err
	}

	procStat, err := proc.Stat()
	if err != nil {
		return err
	}

	procStartTime, err := procStat.StartTime()
	if err != nil {
		return err
	}

	procFDLen, err := proc.FileDescriptorsLen()
	if err != nil {
		return err
	}

	procLimits, err := proc.Limits()
	if err != nil {
		return err
	}

	stats.Record(ctx,
		pc.cpuTotal.M(procStat.CPUTime()),
		pc.openFDs.M(int64(procFDLen)),
		pc.maxFDs.M(int64(procLimits.OpenFiles)),
		pc.vsize.M(int64(procStat.VSize)),
		pc.maxVsize.M(int64(procLimits.AddressSpace)),
		pc.rss.M(int64(procStat.RSS)),
		pc.startTime.M(procStartTime),
	)
	return nil
}