// Copyright 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.

//go:build linux

package sysfs

import (
	"os"
	"path/filepath"
	"regexp"
	"slices"
)

const sasHostClassPath = "class/sas_host"

type SASHost struct {
	Name     string   // /sys/class/sas_host/<Name>
	SASPhys  []string // /sys/class/sas_host/<Name>/device/phy-*
	SASPorts []string // /sys/class/sas_host/<Name>/device/ports-*
}

type SASHostClass map[string]*SASHost

var (
	sasPhyDeviceRegexp  = regexp.MustCompile(`^phy-[0-9:]+$`)
	sasPortDeviceRegexp = regexp.MustCompile(`^port-[0-9:]+$`)
)

// SASHostClass parses host[0-9]+ devices in /sys/class/sas_host.
// This generally only exists so that it can pull in SAS Port and SAS
// PHY entries.
//
// The sas_host class doesn't collect any obvious statistics.  Each
// sas_host contains a scsi_host, which seems to collect a couple
// minor stats (ioc_reset_count and reply_queue_count), but they're
// not worth collecting at this time.  There are more useful SAS stats
// in the sas_phy class.
func (fs FS) SASHostClass() (SASHostClass, error) {
	path := fs.sys.Path(sasHostClassPath)

	dirs, err := os.ReadDir(path)
	if err != nil {
		return nil, err
	}

	shc := make(SASHostClass, len(dirs))

	for _, d := range dirs {
		host, err := fs.parseSASHost(d.Name())
		if err != nil {
			return nil, err
		}

		shc[host.Name] = host
	}

	return shc, nil
}

// Parse a single sas_host.
func (fs FS) parseSASHost(name string) (*SASHost, error) {
	//path := fs.sys.Path(sasHostClassPath, name)
	host := SASHost{Name: name}

	devicepath := fs.sys.Path(filepath.Join(sasHostClassPath, name, "device"))

	dirs, err := os.ReadDir(devicepath)
	if err != nil {
		return nil, err
	}

	for _, d := range dirs {
		if sasPhyDeviceRegexp.MatchString(d.Name()) {
			host.SASPhys = append(host.SASPhys, d.Name())
		}
		if sasPortDeviceRegexp.MatchString(d.Name()) {
			host.SASPorts = append(host.SASPorts, d.Name())
		}
	}

	return &host, nil
}

// GetByName returns the SASHost with the provided name.
func (shc *SASHostClass) GetByName(hostName string) *SASHost {
	return (*shc)[hostName]
}

// GetByPhy finds the SASHost that contains the provided PHY name.
func (shc *SASHostClass) GetByPhy(phyName string) *SASHost {
	for _, h := range *shc {
		if slices.Contains(h.SASPhys, phyName) {
			return h
		}
	}
	return nil
}

// GetByPort finds the SASHost that contains the provided SAS Port name.
func (shc *SASHostClass) GetByPort(portName string) *SASHost {
	for _, h := range *shc {
		if slices.Contains(h.SASPorts, portName) {
			return h
		}
	}
	return nil
}
