// Host detection.

package nembd

import (
	"fmt"
	"io/ioutil"
	"os/exec"
	"strconv"
	"strings"
)

// The Host type represents all the supported host types.
type Host string

const (
	// HostNull reprents a null host.
	HostNull Host = ""

	// HostRPi represents the RaspberryPi.
	HostRPi = "Raspberry Pi"

	// HostBBB represents the BeagleBone Black.
	HostBBB = "BeagleBone Black"

	// HostGalileo represents the Intel Galileo board.
	HostGalileo = "Intel Galileo"

	// HostCubieTruck represents the Cubie Truck.
	HostCubieTruck = "CubieTruck"

	// HostRadxa represents the Radxa board.
	HostRadxa = "Radxa"

	// HostCHIP represents the NextThing C.H.I.P.
	HostCHIP = "CHIP"

	// Host R16 Netease voice box
	HostIHW1 = "IHW1"
)

func execOutput(name string, arg ...string) (output string, err error) {
	var out []byte
	if out, err = exec.Command(name, arg...).Output(); err != nil {
		return
	}
	output = strings.TrimSpace(string(out))
	return
}
func ExecOutput(name string, arg ...string) (output string, err error) {
	return execOutput(name, arg...)
}

func parseVersion(str string) (major, minor, patch int, err error) {
	versionNumber := strings.Split(str, "-")
	parts := strings.Split(versionNumber[0], ".")
	len := len(parts)

	if major, err = strconv.Atoi(parts[0]); err != nil {
		return 0, 0, 0, err
	}
	if minor, err = strconv.Atoi(parts[1]); err != nil {
		return 0, 0, 0, err
	}
	if len > 2 {
		part := parts[2]
		part = strings.TrimSuffix(part, "+")
		if patch, err = strconv.Atoi(part); err != nil {
			return 0, 0, 0, err
		}
	}

	return major, minor, patch, err
}

func kernelVersion() (major, minor, patch int, err error) {
	output, err := execOutput("uname", "-r")
	if err != nil {
		return 0, 0, 0, err
	}

	return parseVersion(output)
}

func cpuInfo() (model, hardware string, revision int, err error) {
	output, err := ioutil.ReadFile("/proc/cpuinfo")
	if err != nil {
		return "", "", 0, err
	}
	for _, line := range strings.Split(string(output), "\n") {
		fields := strings.Split(line, ":")
		if len(fields) < 1 {
			continue
		}
		switch {
		case strings.HasPrefix(fields[0], "Revision"):
			revs := strings.TrimSpace(fields[1])
			rev, err := strconv.ParseInt(revs, 16, 32)
			if err != nil {
				continue
			}
			revision = int(rev)
		case strings.HasPrefix(fields[0], "Hardware"):
			hardware = strings.TrimSpace(fields[1])
		case strings.HasPrefix(fields[0], "model name"):
			model = fields[1]
		}
	}
	return model, hardware, revision, nil
}

func BoardInfo() (model, hardware, Serial, Chipid string, revision int, err error) {
	output, err := ioutil.ReadFile("/proc/cpuinfo")
	if err != nil {
		return "", "", "", "", 0, err
	}
	for _, line := range strings.Split(string(output), "\n") {
		fields := strings.Split(line, ":")
		if len(fields) < 1 {
			continue
		}
		switch {
		case strings.HasPrefix(fields[0], "Revision"):
			revs := strings.TrimSpace(fields[1])
			rev, err := strconv.ParseInt(revs, 16, 32)
			if err != nil {
				continue
			}
			revision = int(rev)
		case strings.HasPrefix(fields[0], "Hardware"):
			hardware = strings.TrimSpace(fields[1])
		case strings.HasPrefix(fields[0], "model name"):
			model = fields[1]
		case strings.HasPrefix(fields[0], "Serial"):
			Serial = strings.TrimSpace(fields[1])
		case strings.HasPrefix(fields[0], "Chipid"):
			Chipid = strings.TrimSpace(fields[1])
		}
	}
	return model, hardware, Serial, Chipid, revision, nil
}

// DetectHost returns the detected host and its revision number.
func DetectHost() (host Host, rev int, err error) {
	major, minor, patch, err := kernelVersion()
	if err != nil {
		return HostNull, 0, err
	}

	if major < 3 || (major == 3 && minor < 0) {
		return HostNull, 0, fmt.Errorf(
			"nembd: linux kernel versions lower than 3.0 are not supported, "+
				"you have %v.%v.%v", major, minor, patch)
	}

	model, hardware, rev, err := cpuInfo()
	if err != nil {
		return HostNull, 0, err
	}

	switch {
	case strings.Contains(hardware, "sun8i"):
		return HostIHW1, rev, nil
	case strings.Contains(model, "ARMv7") && (strings.Contains(hardware, "AM33XX") || strings.Contains(hardware, "AM335X")):
		return HostBBB, rev, nil
	case strings.Contains(hardware, "BCM2708") || strings.Contains(hardware, "BCM2709") || strings.Contains(hardware, "BCM2835"):
		return HostRPi, rev, nil
	case hardware == "Allwinner sun4i/sun5i Families":
		if major < 4 || (major == 4 && minor < 4) {
			return HostNull, 0, fmt.Errorf(
				"nembd: linux kernel version 4.4+ required, you have %v.%v",
				major, minor)
		}
		return HostCHIP, rev, nil
	default:
		return HostNull, 0, fmt.Errorf(`nembd: your host "%v:%v" is not supported at this moment. request support at https://github.com/kidoman/nembd/issues`, host, model)
	}
}
