// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build linux

package cgroups

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/DataDog/datadog-agent/pkg/util/pointer"
)

func (c *cgroupV2) GetIOStats(stats *IOStats) error {
	if stats == nil {
		return &InvalidInputError{Desc: "input stats cannot be nil"}
	}

	if !c.controllerActivated("io") {
		return &ControllerNotFoundError{Controller: "io"}
	}

	stats.Devices = make(map[string]DeviceIOStats)
	stats.ReadBytes = pointer.Ptr(uint64(0))
	stats.WriteBytes = pointer.Ptr(uint64(0))
	stats.ReadOperations = pointer.Ptr(uint64(0))
	stats.WriteOperations = pointer.Ptr(uint64(0))

	if err := parseColumnStats(c.fr, c.pathFor("io.stat"), parseV2IOFn(stats)); err != nil {
		reportError(err)
	}

	if err := parseColumnStats(c.fr, c.pathFor("io.max"), parseV2IOFn(stats)); err != nil {
		reportError(err)
	}

	if err := parsePSI(c.fr, c.pathFor("io.pressure"), &stats.PSISome, &stats.PSIFull); err != nil {
		reportError(err)
	}

	// In case we did not get any device info, clearing everything
	if len(stats.Devices) == 0 {
		stats.Devices = nil
		stats.ReadBytes = nil
		stats.WriteBytes = nil
		stats.ReadOperations = nil
		stats.WriteOperations = nil
	}

	return nil
}

// format for io.stat "259:0 rbytes=278528 wbytes=9700089856 rios=6 wios=2289428 dbytes=0 dios=0"
// format for io.max "8:16 rbps=2097152 wbps=max riops=max wiops=120"
func parseV2IOFn(stats *IOStats) func([]string) error {
	return func(fields []string) error {
		if len(fields) < 2 {
			reportError(newValueError("", fmt.Errorf("malformed line fields: '%v'", fields)))
		}

		written := false
		device := stats.Devices[fields[0]]

		for i := 1; i < len(fields); i++ {
			parts := strings.Split(fields[i], "=")
			if len(parts) != 2 {
				reportError(newValueError("", fmt.Errorf("malformed line fields: '%v'", fields)))
				continue
			}

			// max appears in io.max, it means no limit, not reporting in this case
			if parts[1] == "max" {
				continue
			}

			val, err := strconv.ParseUint(parts[1], 10, 64)
			if err != nil {
				reportError(newValueError("", fmt.Errorf("unexpected format in io file, part: %d, content: %v", i, fields[i])))
				continue
			}

			switch parts[0] {
			case "rbytes":
				written = true
				stats.ReadBytes = pointer.Ptr(*stats.ReadBytes + val)
				device.ReadBytes = &val
			case "wbytes":
				written = true
				stats.WriteBytes = pointer.Ptr(*stats.WriteBytes + val)
				device.WriteBytes = &val
			case "rios":
				written = true
				stats.ReadOperations = pointer.Ptr(*stats.ReadOperations + val)
				device.ReadOperations = &val
			case "wios":
				written = true
				stats.WriteOperations = pointer.Ptr(*stats.WriteOperations + val)
				device.WriteOperations = &val
			case "rbps":
				written = true
				device.ReadBytesLimit = &val
			case "wbps":
				written = true
				device.WriteBytesLimit = &val
			case "riops":
				written = true
				device.ReadOperationsLimit = &val
			case "wiops":
				written = true
				device.WriteOperationsLimit = &val
			}
		}

		if written {
			stats.Devices[fields[0]] = device
		}

		return nil
	}
}
