// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package procfs

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/prometheus/procfs/internal/util"
)

// ProcSnmp models the content of /proc/<pid>/net/snmp.
type ProcSnmp struct {
	// The process ID.
	PID int
	Ip
	Icmp
	IcmpMsg
	Tcp
	Udp
	UdpLite
}

type Ip struct { // nolint:revive
	Forwarding      float64
	DefaultTTL      float64
	InReceives      float64
	InHdrErrors     float64
	InAddrErrors    float64
	ForwDatagrams   float64
	InUnknownProtos float64
	InDiscards      float64
	InDelivers      float64
	OutRequests     float64
	OutDiscards     float64
	OutNoRoutes     float64
	ReasmTimeout    float64
	ReasmReqds      float64
	ReasmOKs        float64
	ReasmFails      float64
	FragOKs         float64
	FragFails       float64
	FragCreates     float64
}

type Icmp struct {
	InMsgs           float64
	InErrors         float64
	InCsumErrors     float64
	InDestUnreachs   float64
	InTimeExcds      float64
	InParmProbs      float64
	InSrcQuenchs     float64
	InRedirects      float64
	InEchos          float64
	InEchoReps       float64
	InTimestamps     float64
	InTimestampReps  float64
	InAddrMasks      float64
	InAddrMaskReps   float64
	OutMsgs          float64
	OutErrors        float64
	OutDestUnreachs  float64
	OutTimeExcds     float64
	OutParmProbs     float64
	OutSrcQuenchs    float64
	OutRedirects     float64
	OutEchos         float64
	OutEchoReps      float64
	OutTimestamps    float64
	OutTimestampReps float64
	OutAddrMasks     float64
	OutAddrMaskReps  float64
}

type IcmpMsg struct {
	InType3  float64
	OutType3 float64
}

type Tcp struct { // nolint:revive
	RtoAlgorithm float64
	RtoMin       float64
	RtoMax       float64
	MaxConn      float64
	ActiveOpens  float64
	PassiveOpens float64
	AttemptFails float64
	EstabResets  float64
	CurrEstab    float64
	InSegs       float64
	OutSegs      float64
	RetransSegs  float64
	InErrs       float64
	OutRsts      float64
	InCsumErrors float64
}

type Udp struct { // nolint:revive
	InDatagrams  float64
	NoPorts      float64
	InErrors     float64
	OutDatagrams float64
	RcvbufErrors float64
	SndbufErrors float64
	InCsumErrors float64
	IgnoredMulti float64
}

type UdpLite struct { // nolint:revive
	InDatagrams  float64
	NoPorts      float64
	InErrors     float64
	OutDatagrams float64
	RcvbufErrors float64
	SndbufErrors float64
	InCsumErrors float64
	IgnoredMulti float64
}

func (p Proc) Snmp() (ProcSnmp, error) {
	filename := p.path("net/snmp")
	data, err := util.ReadFileNoStat(filename)
	if err != nil {
		return ProcSnmp{PID: p.PID}, err
	}
	procSnmp, err := parseSnmp(bytes.NewReader(data), filename)
	procSnmp.PID = p.PID
	return procSnmp, err
}

// parseSnmp parses the metrics from proc/<pid>/net/snmp file
// and returns a map contains those metrics (e.g. {"Ip": {"Forwarding": 2}}).
func parseSnmp(r io.Reader, fileName string) (ProcSnmp, error) {
	var (
		scanner  = bufio.NewScanner(r)
		procSnmp = ProcSnmp{}
	)

	for scanner.Scan() {
		nameParts := strings.Split(scanner.Text(), " ")
		scanner.Scan()
		valueParts := strings.Split(scanner.Text(), " ")
		// Remove trailing :.
		protocol := strings.TrimSuffix(nameParts[0], ":")
		if len(nameParts) != len(valueParts) {
			return procSnmp, fmt.Errorf("mismatch field count mismatch in %s: %s",
				fileName, protocol)
		}
		for i := 1; i < len(nameParts); i++ {
			value, err := strconv.ParseFloat(valueParts[i], 64)
			if err != nil {
				return procSnmp, err
			}
			key := nameParts[i]

			switch protocol {
			case "Ip":
				switch key {
				case "Forwarding":
					procSnmp.Ip.Forwarding = value
				case "DefaultTTL":
					procSnmp.Ip.DefaultTTL = value
				case "InReceives":
					procSnmp.Ip.InReceives = value
				case "InHdrErrors":
					procSnmp.Ip.InHdrErrors = value
				case "InAddrErrors":
					procSnmp.Ip.InAddrErrors = value
				case "ForwDatagrams":
					procSnmp.Ip.ForwDatagrams = value
				case "InUnknownProtos":
					procSnmp.Ip.InUnknownProtos = value
				case "InDiscards":
					procSnmp.Ip.InDiscards = value
				case "InDelivers":
					procSnmp.Ip.InDelivers = value
				case "OutRequests":
					procSnmp.Ip.OutRequests = value
				case "OutDiscards":
					procSnmp.Ip.OutDiscards = value
				case "OutNoRoutes":
					procSnmp.Ip.OutNoRoutes = value
				case "ReasmTimeout":
					procSnmp.Ip.ReasmTimeout = value
				case "ReasmReqds":
					procSnmp.Ip.ReasmReqds = value
				case "ReasmOKs":
					procSnmp.Ip.ReasmOKs = value
				case "ReasmFails":
					procSnmp.Ip.ReasmFails = value
				case "FragOKs":
					procSnmp.Ip.FragOKs = value
				case "FragFails":
					procSnmp.Ip.FragFails = value
				case "FragCreates":
					procSnmp.Ip.FragCreates = value
				}
			case "Icmp":
				switch key {
				case "InMsgs":
					procSnmp.Icmp.InMsgs = value
				case "InErrors":
					procSnmp.Icmp.InErrors = value
				case "InCsumErrors":
					procSnmp.Icmp.InCsumErrors = value
				case "InDestUnreachs":
					procSnmp.Icmp.InDestUnreachs = value
				case "InTimeExcds":
					procSnmp.Icmp.InTimeExcds = value
				case "InParmProbs":
					procSnmp.Icmp.InParmProbs = value
				case "InSrcQuenchs":
					procSnmp.Icmp.InSrcQuenchs = value
				case "InRedirects":
					procSnmp.Icmp.InRedirects = value
				case "InEchos":
					procSnmp.Icmp.InEchos = value
				case "InEchoReps":
					procSnmp.Icmp.InEchoReps = value
				case "InTimestamps":
					procSnmp.Icmp.InTimestamps = value
				case "InTimestampReps":
					procSnmp.Icmp.InTimestampReps = value
				case "InAddrMasks":
					procSnmp.Icmp.InAddrMasks = value
				case "InAddrMaskReps":
					procSnmp.Icmp.InAddrMaskReps = value
				case "OutMsgs":
					procSnmp.Icmp.OutMsgs = value
				case "OutErrors":
					procSnmp.Icmp.OutErrors = value
				case "OutDestUnreachs":
					procSnmp.Icmp.OutDestUnreachs = value
				case "OutTimeExcds":
					procSnmp.Icmp.OutTimeExcds = value
				case "OutParmProbs":
					procSnmp.Icmp.OutParmProbs = value
				case "OutSrcQuenchs":
					procSnmp.Icmp.OutSrcQuenchs = value
				case "OutRedirects":
					procSnmp.Icmp.OutRedirects = value
				case "OutEchos":
					procSnmp.Icmp.OutEchos = value
				case "OutEchoReps":
					procSnmp.Icmp.OutEchoReps = value
				case "OutTimestamps":
					procSnmp.Icmp.OutTimestamps = value
				case "OutTimestampReps":
					procSnmp.Icmp.OutTimestampReps = value
				case "OutAddrMasks":
					procSnmp.Icmp.OutAddrMasks = value
				case "OutAddrMaskReps":
					procSnmp.Icmp.OutAddrMaskReps = value
				}
			case "IcmpMsg":
				switch key {
				case "InType3":
					procSnmp.IcmpMsg.InType3 = value
				case "OutType3":
					procSnmp.IcmpMsg.OutType3 = value
				}
			case "Tcp":
				switch key {
				case "RtoAlgorithm":
					procSnmp.Tcp.RtoAlgorithm = value
				case "RtoMin":
					procSnmp.Tcp.RtoMin = value
				case "RtoMax":
					procSnmp.Tcp.RtoMax = value
				case "MaxConn":
					procSnmp.Tcp.MaxConn = value
				case "ActiveOpens":
					procSnmp.Tcp.ActiveOpens = value
				case "PassiveOpens":
					procSnmp.Tcp.PassiveOpens = value
				case "AttemptFails":
					procSnmp.Tcp.AttemptFails = value
				case "EstabResets":
					procSnmp.Tcp.EstabResets = value
				case "CurrEstab":
					procSnmp.Tcp.CurrEstab = value
				case "InSegs":
					procSnmp.Tcp.InSegs = value
				case "OutSegs":
					procSnmp.Tcp.OutSegs = value
				case "RetransSegs":
					procSnmp.Tcp.RetransSegs = value
				case "InErrs":
					procSnmp.Tcp.InErrs = value
				case "OutRsts":
					procSnmp.Tcp.OutRsts = value
				case "InCsumErrors":
					procSnmp.Tcp.InCsumErrors = value
				}
			case "Udp":
				switch key {
				case "InDatagrams":
					procSnmp.Udp.InDatagrams = value
				case "NoPorts":
					procSnmp.Udp.NoPorts = value
				case "InErrors":
					procSnmp.Udp.InErrors = value
				case "OutDatagrams":
					procSnmp.Udp.OutDatagrams = value
				case "RcvbufErrors":
					procSnmp.Udp.RcvbufErrors = value
				case "SndbufErrors":
					procSnmp.Udp.SndbufErrors = value
				case "InCsumErrors":
					procSnmp.Udp.InCsumErrors = value
				case "IgnoredMulti":
					procSnmp.Udp.IgnoredMulti = value
				}
			case "UdpLite":
				switch key {
				case "InDatagrams":
					procSnmp.UdpLite.InDatagrams = value
				case "NoPorts":
					procSnmp.UdpLite.NoPorts = value
				case "InErrors":
					procSnmp.UdpLite.InErrors = value
				case "OutDatagrams":
					procSnmp.UdpLite.OutDatagrams = value
				case "RcvbufErrors":
					procSnmp.UdpLite.RcvbufErrors = value
				case "SndbufErrors":
					procSnmp.UdpLite.SndbufErrors = value
				case "InCsumErrors":
					procSnmp.UdpLite.InCsumErrors = value
				case "IgnoredMulti":
					procSnmp.UdpLite.IgnoredMulti = value
				}
			}
		}
	}
	return procSnmp, scanner.Err()
}
