// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package cgroups provides functions to parse cgroup information.
package cgroups

import (
	"io"
	"slices"
	"strings"

	"github.com/siderolabs/gen/maps"
)

// Tree represents a cgroup tree.
type Tree struct {
	Root *Node
}

// Find the node by directory path.
func (t *Tree) Find(directoryPath string) *Node {
	node := t.Root

	for component := range strings.SplitSeq(directoryPath, "/") {
		if component == "." || component == "" {
			return node
		}

		if node.Children == nil {
			node.Children = make(map[string]*Node)
		}

		child, ok := node.Children[component]
		if !ok {
			child = &Node{}
			node.Children[component] = child
		}

		node = child
	}

	return node
}

// ResolveNames resolves the names of the node and its children.
func (t *Tree) ResolveNames(nameMap map[string]string) {
	t.Root.ResolveNames(nameMap)
}

// Walk the tree.
func (t *Tree) Walk(fn func(*Node)) {
	t.Root.Walk(fn)
}

// SortedChildren returns the sorted children of the node.
func (n *Node) SortedChildren() []string {
	children := maps.Keys(n.Children)

	slices.Sort(children)

	return children
}

// ResolveNames resolves the names of the node and its children.
func (n *Node) ResolveNames(nameMap map[string]string) {
	for name, child := range n.Children {
		if resolvedName, ok := nameMap[name]; ok {
			delete(n.Children, name)
			n.Children[resolvedName] = child
		}

		child.ResolveNames(nameMap)
	}
}

// Walk the node.
func (n *Node) Walk(fn func(*Node)) {
	fn(n)

	for _, child := range n.Children {
		child.Walk(fn)
	}
}

// Node represents a cgroup node.
type Node struct {
	Children map[string]*Node

	CgroupEvents  FlatMap
	CgroupFreeze  Value
	CgroupProcs   Values
	CgroupStat    FlatMap
	CgroupThreads Values

	// Resolved externally into process names.
	CgroupProcsResolved []RawValue

	CPUIdle       Value
	CPUMax        Values
	CPUMaxBurst   Value
	CPUPressure   NestedKeyed
	CPUStat       FlatMap
	CPUStatLocal  FlatMap
	CPUWeight     Value
	CPUWeightNice Value

	CPUSetCPUs          RawValue
	CPUSetCPUsEffective RawValue
	CPUSetMems          RawValue
	CPUSetMemsEffective RawValue

	IOBFQWeight FlatMap
	IOMax       NestedKeyed
	IOPressure  NestedKeyed
	IOStat      NestedKeyed

	MemoryCurrent     Value
	MemoryEvents      FlatMap
	MemoryEventsLocal FlatMap
	MemoryHigh        Value
	MemoryLow         Value
	MemoryMax         Value
	MemoryMin         Value
	MemoryNUMAStat    NestedKeyed
	MemoryOOMGroup    Value
	MemoryPeak        Value
	MemoryPressure    NestedKeyed
	MemoryStat        FlatMap

	MemorySwapCurrent Value
	MemorySwapEvents  FlatMap
	MemorySwapHigh    Value
	MemorySwapMax     Value
	MemorySwapPeak    Value

	MemoryZswapCurrent   Value
	MemoryZswapMax       Value
	MemoryZswapWriteback Value

	PidsCurrent Value
	PidsEvents  FlatMap
	PidsMax     Value
	PidsPeak    Value
}

func parseSingleValue(parser func(r io.Reader) (Values, error), out *Value, r io.Reader) error {
	values, err := parser(r)
	if err != nil {
		return err
	}

	if len(values) > 0 {
		*out = values[0]
	}

	return nil
}

// Parse the cgroup information by filename from the reader.
//
//nolint:gocyclo,cyclop
func (n *Node) Parse(filename string, r io.Reader) error {
	var err error

	switch filename {
	case "cgroup.events":
		n.CgroupEvents, err = ParseFlatMapValues(r)

		return err
	case "cgroup.freeze":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.CgroupFreeze, r)
	case "cgroup.procs":
		n.CgroupProcs, err = ParseNewlineSeparatedValues(r)

		return err
	case "cgroup.stat":
		n.CgroupStat, err = ParseFlatMapValues(r)

		return err
	case "cgroup.threads":
		n.CgroupThreads, err = ParseNewlineSeparatedValues(r)

		return err
	case "cpu.idle":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.CPUIdle, r)
	case "cpu.max":
		n.CPUMax, err = ParseSpaceSeparatedValues(r)

		return err
	case "cpu.max.burst":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.CPUMaxBurst, r)
	case "cpu.pressure":
		n.CPUPressure, err = ParseNestedKeyedValues(r)

		return err
	case "cpu.stat":
		n.CPUStat, err = ParseFlatMapValues(r)

		return err
	case "cpu.stat.local":
		n.CPUStatLocal, err = ParseFlatMapValues(r)

		return err
	case "cpu.weight":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.CPUWeight, r)
	case "cpu.weight.nice":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.CPUWeightNice, r)
	case "cpuset.cpus":
		n.CPUSetCPUs, err = ParseRawValue(r)

		return err
	case "cpuset.cpus.effective":
		n.CPUSetCPUsEffective, err = ParseRawValue(r)

		return err
	case "cpuset.mems":
		n.CPUSetMems, err = ParseRawValue(r)

		return err
	case "cpuset.mems.effective":
		n.CPUSetMemsEffective, err = ParseRawValue(r)

		return err
	case "io.bfq.weight":
		n.IOBFQWeight, err = ParseFlatMapValues(r)

		return err
	case "io.max":
		n.IOMax, err = ParseNestedKeyedValues(r)

		return err
	case "io.pressure":
		n.IOPressure, err = ParseNestedKeyedValues(r)

		return err
	case "io.stat":
		n.IOStat, err = ParseNestedKeyedValues(r)

		return err
	case "memory.current":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryCurrent, r)
	case "memory.events":
		n.MemoryEvents, err = ParseFlatMapValues(r)

		return err
	case "memory.events.local":
		n.MemoryEventsLocal, err = ParseFlatMapValues(r)

		return err
	case "memory.high":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryHigh, r)
	case "memory.low":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryLow, r)
	case "memory.max":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryMax, r)
	case "memory.min":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryMin, r)
	case "memory.numa_stat":
		n.MemoryNUMAStat, err = ParseNestedKeyedValues(r)

		return err
	case "memory.oom.group":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryOOMGroup, r)
	case "memory.peak":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryPeak, r)
	case "memory.pressure":
		n.MemoryPressure, err = ParseNestedKeyedValues(r)

		return err
	case "memory.stat":
		n.MemoryStat, err = ParseFlatMapValues(r)

		return err
	case "memory.swap.current":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemorySwapCurrent, r)
	case "memory.swap.events":
		n.MemorySwapEvents, err = ParseFlatMapValues(r)

		return err
	case "memory.swap.high":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemorySwapHigh, r)
	case "memory.swap.max":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemorySwapMax, r)
	case "memory.swap.peak":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemorySwapPeak, r)
	case "memory.zswap.current":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapCurrent, r)
	case "memory.zswap.max":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapMax, r)
	case "memory.zswap.writeback":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapWriteback, r)
	case "pids.current":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.PidsCurrent, r)
	case "pids.events":
		n.PidsEvents, err = ParseFlatMapValues(r)

		return err
	case "pids.max":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.PidsMax, r)
	case "pids.peak":
		return parseSingleValue(ParseNewlineSeparatedValues, &n.PidsPeak, r)
	}

	return nil
}
