//go:build linux
// +build linux

package state

import (
	"encoding/binary"
	"fmt"
	"os"
	"runtime"
	"syscall"
	"time"

	"nms_nbi/lib/log"

	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/mem"
	"github.com/shirou/gopsutil/v3/process"
)

type SysInfo struct {
	SysCpuPercent uint16 // x%
	MyCpuPercent  uint16 // x%, CPU percent of current proccess

	SysTotalRam       uint32 // KB
	MyUsedRam         uint32 // RAM usage of current proccess, KB
	SysRamUsedPercent uint16 // x%

	PartitionNum  byte
	PartitionInfo []PartitionInfo // usage of each partition
}

const MAX_PARTITION_NUM byte = 32

func GetSysStat() ([]byte, int) {
	// Get sys info
	var sysInfo SysInfo
	err := GetSysInfo(&sysInfo)
	if err != nil {
		return nil, 0
	}

	//log.Tracef("current sys info: %v", sysInfo)

	// build ems buffer
	var data []byte = make([]byte, 1024)
	var len int
	var i byte
	binary.BigEndian.PutUint16(data[0:], sysInfo.MyCpuPercent)       //x% * 100
	binary.BigEndian.PutUint16(data[2:], sysInfo.SysCpuPercent)      //x% * 100
	binary.BigEndian.PutUint32(data[4:], sysInfo.SysTotalRam)        // KB
	binary.BigEndian.PutUint32(data[8:], sysInfo.MyUsedRam)          // KB
	binary.BigEndian.PutUint16(data[12:], sysInfo.SysRamUsedPercent) //x% * 100
	data[14] = sysInfo.PartitionNum
	for i = 0; i < sysInfo.PartitionNum; i++ {
		binary.BigEndian.PutUint32(data[15+8*(i):], sysInfo.PartitionInfo[i].Total)  // MB
		binary.BigEndian.PutUint32(data[15+8*(i)+4:], sysInfo.PartitionInfo[i].Used) // MB
	}
	len = int(15 + 8*sysInfo.PartitionNum)

	//log.Tracef("current sys stat buf: %v, len: %d", data, len)

	return data, len
}

var pProc *process.Process = nil

func GetSYsCpuPercent() float64 {
	totalPercent, err := cpu.Percent(0, false) //(2*time.Second, false)
	if err != nil {
		return 0.0
	} else {
		return totalPercent[0]
	}
}

func GetSysInfo(sysInfo *SysInfo) error {
	// sys cpu percent
	totalPercent, err := cpu.Percent(0, false) //(2*time.Second, false)
	if err != nil {
		sysInfo.SysCpuPercent = 0
	} else {
		sysInfo.SysCpuPercent = uint16(totalPercent[0] * 100)
	}

	if pProc == nil {
		checkPid := os.Getpid()
		pProc, err = process.NewProcess(int32(checkPid))
		if err != nil {
			log.Tracef("get process info error %v", err)
			return err
		}
	}

	// self cpu percent
	percent, err := pProc.Percent(0) //(2*time.Second)
	if err != nil {
		log.Tracef("get process cpu percent error %v", err)
		sysInfo.MyCpuPercent = 0
	} else {
		sysInfo.MyCpuPercent = uint16(percent * 100)
	}

	// self RAM(KB)
	myRam, err := pProc.MemoryInfo()
	if err != nil {
		log.Tracef("get self memory info error %v", err)
		sysInfo.MyUsedRam = 0
	} else {
		sysInfo.MyUsedRam = uint32(myRam.RSS / 1024)
	}

	// system RAM(KB)
	sysRam, err := mem.VirtualMemory()
	if err != nil {
		log.Tracef("gett sys memory info error %v", err)
		sysInfo.SysTotalRam = 0
		sysInfo.SysRamUsedPercent = 0
	} else {
		sysInfo.SysTotalRam = uint32(sysRam.Total / 1024)
		sysInfo.SysRamUsedPercent = uint16(sysRam.UsedPercent * 100)
	}

	// partition usage
	GetPartitions(sysInfo)
	return nil
}

func getProcess() process.Process {
	checkPid := os.Getpid()
	ret, _ := process.NewProcess(int32(checkPid))
	return *ret
}

func GetSystemCpuInfo() {
	physicalCnt, _ := cpu.Counts(false)
	logicalCnt, _ := cpu.Counts(true)
	log.Tracef("physical count:%d logical count:%d", physicalCnt, logicalCnt)

	totalPercent, _ := cpu.Percent(3*time.Second, false) // per cpu is false
	perPercents, _ := cpu.Percent(3*time.Second, true)   // per cpu is true
	log.Tracef("total percent:%v per percents:%v", totalPercent, perPercents)
}

func GetProcessCpuPercent() {
	p := getProcess()
	percent, err := p.Percent(0)
	if err != nil {
		log.Tracef("error %v", err)
	}

	numcpu := runtime.NumCPU()
	//	if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO
	if percent < 0.0 {
		log.Tracef("Err CPU Percent of Process: %f, CPU num: %d", percent, numcpu)
	} else {
		log.Tracef("get process CPU percent: %f, CPU num: %d", percent, numcpu)
	}
}

func GetProcessMemoryInfo() {
	p := getProcess()

	v, err := p.MemoryInfo()
	if err != nil {
		log.Tracef("getting memory info error %v", err)
	}

	log.Tracef("get process memory info %v", v)

	info, _ := mem.VirtualMemory()
	fmt.Println(info)
}

func GetPartitions(sysInfo *SysInfo) {
	sysInfo.PartitionNum = 0
	//sysInfo.PartitionInfo = make([]PartitionInfo, MAX_PARTITION_NUM, MAX_PARTITION_NUM)
	infos, _ := disk.Partitions(true)
	for _, info := range infos {
		GetOnePartitionUsage(info.Mountpoint, sysInfo)
		if sysInfo.PartitionNum >= MAX_PARTITION_NUM {
			break
		}
	}
}

func GetOnePartitionUsage(path string, sysInfo *SysInfo) int {
	info, err := disk.Usage(path)
	if err != nil {
		return -1
	}

	if info.Total <= 0 { // info.Used/(1024 * 1024)MB
		return 0
	}
	var partition PartitionInfo
	partition.Total = uint32(info.Total / 1024 / 1024)
	partition.Used = uint32(info.Used / 1024 / 1024)
	sysInfo.PartitionInfo = append(sysInfo.PartitionInfo, partition)
	sysInfo.PartitionNum++

	/*data, err := json.MarshalIndent(info, "", "  ")
	if err != nil {
		return -1
	}

	fmt.Println(string(data))*/
	return 1
}

func getOS() string {
	var osname string
	if runtime.GOOS == "linux" {
		osname = "GNU/Linux"
	}
	return osname
}

func utsnameToString(unameArray [65]int8) string {
	var byteString [65]byte
	var indexLength int
	for ; unameArray[indexLength] != 0 && indexLength < 65; indexLength++ {
		byteString[indexLength] = uint8(unameArray[indexLength])
	}
	return string(byteString[:indexLength])
}

func getUnameStr() string {
	var utsname = syscall.Utsname{}
	err := syscall.Uname(&utsname)
	if err == nil {
		name := utsnameToString(utsname.Sysname)
		node := utsnameToString(utsname.Nodename)
		release := utsnameToString(utsname.Release)
		version := utsnameToString(utsname.Version)
		machine := utsnameToString(utsname.Machine)
		//domain:=    utsnameToString(utsname.Domainname)
		osName := getOS()
		return fmt.Sprintf("%s %s %s %s %s %s", name, node,
			release, version, machine, osName)
	}
	return ""
}
