package main

import (
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

func readBoardInfo(dir NamedFS) map[string]string {
	result := make(map[string]string)
	c, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "board_info.txt"))
	if err != nil {
		return result
	}
	ls := strings.Split(string(c), "\n")
	for _, l := range ls {
		spl := strings.SplitN(l, ":", 2)
		if len(spl) != 2 {
			// This shouldn't ever happen, but let's try to
			// extract as much information from erroneous
			// board_info files (if they exist) as possible.
			continue
		}
		result[strings.TrimSpace(spl[0])] = strings.TrimSpace(spl[1])
	}
	return result
}

func fetchBoards(dirs chan<- NamedFS) {
	defer close(dirs)
	ds, err := fs.Glob(cbdirFS, filepath.Join("src", "mainboard", "*", "*"))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Could not find mainboard directories: %v\n", err)
		return
	}
	for _, d := range ds {
		if _, err := fs.ReadDir(cbdirFS, d); err != nil {
			continue
		}
		dirs <- NamedFS{
			FS:   cbdirFS,
			Name: d,
		}
	}
}

var niceVendors = make(map[string]string)

func getNiceVendor(dir string, vendor string) (string, error) {
	if _, exists := niceVendors[vendor]; !exists {
		c, err := fs.ReadFile(cbdirFS, filepath.Join(dir, "..", "Kconfig.name"))
		if err != nil {
			return "", err
		}
		re, err := regexp.Compile("(?i)config VENDOR_" + vendor)
		if err != nil {
			return "", err
		}
		ls := strings.Split(string(c), "\n")
		next := false
		for _, l := range ls {
			if next {
				niceVendors[vendor] = strings.Split(l, "\"")[1]
				break
			}
			if re.Match([]byte(l)) {
				next = true
			}
		}
	}
	return niceVendors[vendor], nil
}

func readKconfig(dir NamedFS) (string, string, string, string, string, error) {
	var north, south, superio, cpu, partnum string
	c, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "Kconfig"))
	if err != nil {
		return north, south, superio, cpu, partnum, err
	}
	ls := strings.Split(string(c), "\n")
	partoffset := 0
	for _, l := range ls {
		l = strings.TrimSpace(l)
		if len(l) < 7 {
			continue
		}
		// TODO: handling of MAINBOARD_PART_NUMBER is rather broken
		// and fragile. Doesn't help that many boards use different
		// part numbers for different models and this code can't
		// figure it out.
		if strings.Contains(strings.ToLower(l), "config mainboard_part_number") {
			partoffset = 2
			continue
		}
		if partoffset > 0 {
			partoffset--
			if strings.Contains(l, "default") {
				partnum = strings.Split(l, "\"")[1]
				continue
			}
		}
		if l[0:7] != "select " {
			continue
		}
		l = l[7:]
		if len(l) > 12 && l[0:12] == "NORTHBRIDGE_" {
			north = l[12:]
			continue
		}
		if len(l) > 12 && l[0:12] == "SOUTHBRIDGE_" {
			if strings.Contains(l, "SKIP_") ||
				strings.Contains(l, "DISABLE_") {
				continue
			}
			south = l[12:]
			continue
		}
		if len(l) > 8 && l[0:8] == "SUPERIO_" {
			superio = l[8:]
			continue
		}
		if len(l) > 4 && (l[0:4] == "CPU_" || l[0:4] == "SOC_") {
			if strings.Contains(l, "AMD_AGESA_FAMILY") ||
				strings.Contains(l, "AMD_COMMON_") ||
				strings.Contains(l, "INTEL_COMMON_") ||
				strings.Contains(l, "INTEL_DISABLE_") ||
				strings.Contains(l, "INTEL_CSE_") ||
				strings.Contains(l, "CPU_MICROCODE_CBFS_NONE") {
				continue
			}
			cpu = l[4:]
		}
	}
	return north, south, superio, cpu, partnum, nil
}

type reReplace struct {
	pattern *regexp.Regexp
	replace string
}

func prettify(input string, rules *[]reReplace) string {
	for _, rule := range *rules {
		input = rule.pattern.ReplaceAllString(input, rule.replace)
	}
	return input
}

var northbridgeRules = []reReplace{
	{
		pattern: regexp.MustCompile("AMD_AGESA_FAMILY([0-9a-fA-F]*)(.*)"),
		replace: "AMD Family ${1}h${2} (AGESA)",
	},
	{
		pattern: regexp.MustCompile("AMD_PI_(.*)"),
		replace: "AMD ${1} (PI)",
	},
	{
		pattern: regexp.MustCompile("INTEL_FSP_(.*)"),
		replace: "Intel® ${1} (FSP)",
	},
	{
		pattern: regexp.MustCompile("AMD_FAMILY([0-9a-fA-F]*)"),
		replace: "AMD Family ${1}h,",
	},
	{
		pattern: regexp.MustCompile("AMD_AMDFAM([0-9a-fA-F]*)"),
		replace: "AMD Family ${1}h",
	},
	{
		pattern: regexp.MustCompile("_"),
		replace: " ",
	},
	{
		pattern: regexp.MustCompile("INTEL"),
		replace: "Intel®",
	},
}

func prettifyNorthbridge(northbridge string) string {
	return prettify(northbridge, &northbridgeRules)
}

var southbridgeRules = []reReplace{
	{
		pattern: regexp.MustCompile("_"),
		replace: " ",
	},
	{
		pattern: regexp.MustCompile("INTEL"),
		replace: "Intel®",
	},
}

func prettifySouthbridge(southbridge string) string {
	return prettify(southbridge, &southbridgeRules)
}

var superIORules = []reReplace{
	{
		pattern: regexp.MustCompile("_"),
		replace: " ",
	},
	{
		pattern: regexp.MustCompile("WINBOND"),
		replace: "Winbond™,",
	},
	{
		pattern: regexp.MustCompile("ITE"),
		replace: "ITE™",
	},
	{
		pattern: regexp.MustCompile("SMSC"),
		replace: "SMSC®",
	},
	{
		pattern: regexp.MustCompile("NUVOTON"),
		replace: "Nuvoton ",
	},
}

func prettifySuperIO(superio string) string {
	return prettify(superio, &superIORules)
}

type cpuMapping struct {
	cpu    string
	socket string
}

var cpuMappings = map[string]cpuMapping{
	"ALLWINNER_A10": {
		cpu:    "Allwinner A10",
		socket: "?",
	},
	"AMD_GEODE_LX": {
		cpu:    "AMD Geode™ LX",
		socket: "—",
	},
	"AMD_SOCKET_754": {
		cpu:    "AMD Sempron™ / Athlon™ 64 / Turion™ 64",
		socket: "Socket 754",
	},
	"AMD_SOCKET_ASB2": {
		cpu:    "AMD Turion™ II Neo/Athlon™ II Neo",
		socket: "ASB2 (BGA812)",
	},
	"AMD_SOCKET_S1G1": {
		cpu:    "AMD Turion™ / X2  Sempron™",
		socket: "Socket S1G1",
	},
	"AMD_SOCKET_G34": {
		cpu:    "AMD Opteron™ Magny-Cours/Interlagos",
		socket: "Socket G34",
	},
	"AMD_SOCKET_G34_NON_AGESA": {
		cpu:    "AMD Opteron™ Magny-Cours/Interlagos",
		socket: "Socket G34",
	},
	"AMD_SOCKET_C32": {
		cpu:    "AMD Opteron™ Magny-Cours/Interlagos",
		socket: "Socket C32",
	},
	"AMD_SOCKET_C32_NON_AGESA": {
		cpu:    "AMD Opteron™ Magny-Cours/Interlagos",
		socket: "Socket C32",
	},
	"AMD_SOCKET_AM2": {
		cpu:    "?",
		socket: "Socket AM2",
	},
	"AMD_SOCKET_AM3": {
		cpu:    "AMD Athlon™ 64 / FX / X2",
		socket: "Socket AM3",
	},
	"AMD_SOCKET_AM2R2": {
		cpu:    "AMD Athlon™ 64 / X2 / FX, Sempron™",
		socket: "Socket AM2+",
	},
	"AMD_SOCKET_F": {
		cpu:    "AMD Opteron™",
		socket: "Socket F",
	},
	"AMD_SOCKET_F_1207": {
		cpu:    "AMD Opteron™",
		socket: "Socket F 1207",
	},
	"AMD_SOCKET_940": {
		cpu:    "AMD Opteron™",
		socket: "Socket 940",
	},
	"AMD_SOCKET_939": {
		cpu:    "AMD Athlon™ 64 / FX / X2",
		socket: "Socket 939",
	},
	"AMD_SC520": {
		cpu:    "AMD Élan™SC520",
		socket: "—",
	},
	"AMD_STONEYRIDGE_FP4": {
		cpu:    "AMD Stoney Ridge",
		socket: "FP4 BGA",
	},
	"ARMLTD_CORTEX_A9": {
		cpu:    "ARM Cortex A9",
		socket: "?",
	},
	"DMP_VORTEX86EX": {
		cpu:    "DMP VORTEX86EX",
		socket: "?",
	},
	"MEDIATEK_MT8173": {
		cpu:    "MediaTek MT8173",
		socket: "—",
	},
	"NVIDIA_TEGRA124": {
		cpu:    "NVIDIA Tegra 124",
		socket: "—",
	},
	"NVIDIA_TEGRA210": {
		cpu:    "NVIDIA Tegra 210",
		socket: "—",
	},
	"SAMSUNG_EXYNOS5420": {
		cpu:    "Samsung Exynos 5420",
		socket: "?",
	},
	"SAMSUNG_EXYNOS5250": {
		cpu:    "Samsung Exynos 5250",
		socket: "?",
	},
	"TI_AM335X": {
		cpu:    "TI AM335X",
		socket: "?",
	},
	"INTEL_APOLLOLAKE": {
		cpu:    "Intel® Apollo Lake",
		socket: "—",
	},
	"INTEL_BAYTRAIL": {
		cpu:    "Intel® Bay Trail",
		socket: "—",
	},
	"INTEL_BRASWELL": {
		cpu:    "Intel® Braswell",
		socket: "—",
	},
	"INTEL_BROADWELL": {
		cpu:    "Intel® Broadwell",
		socket: "—",
	},
	"INTEL_DENVERTON_NS": {
		cpu:    "Intel® Denverton-NS",
		socket: "—",
	},
	"INTEL_FSP_BROADWELL_DE": {
		cpu:    "Intel® Broadwell-DE",
		socket: "—",
	},
	"INTEL_GLK": {
		cpu:    "Intel® Gemini Lake",
		socket: "—",
	},
	"INTEL_GEMINILAKE": {
		cpu:    "Intel® Gemini Lake",
		socket: "—",
	},
	"INTEL_ICELAKE": {
		cpu:    "Intel® Ice Lake",
		socket: "—",
	},
	"INTEL_KABYLAKE": {
		cpu:    "Intel® Kaby Lake",
		socket: "—",
	},
	"INTEL_SANDYBRIDGE": {
		cpu:    "Intel® Sandy Bridge",
		socket: "—",
	},
	"INTEL_SKYLAKE": {
		cpu:    "Intel® Skylake",
		socket: "—",
	},
	"INTEL_SLOT_1": {
		cpu:    "Intel® Pentium® II/III, Celeron®",
		socket: "Slot 1",
	},
	"INTEL_SOCKET_MPGA604": {
		cpu:    "Intel® Xeon®",
		socket: "Socket 604",
	},
	"INTEL_SOCKET_M": {
		cpu:    "Intel® Core™ 2 Duo Mobile, Core™ Duo/Solo, Celeron® M",
		socket: "Socket M (mPGA478MT)",
	},
	"INTEL_SOCKET_LGA771": {
		cpu:    "Intel Xeon™ 5000 series",
		socket: "Socket LGA771",
	},
	"INTEL_SOCKET_LGA775": {
		cpu:    "Intel® Core 2, Pentium 4/D",
		socket: "Socket LGA775",
	},
	"INTEL_SOCKET_PGA370": {
		cpu:    "Intel® Pentium® III-800, Celeron®",
		socket: "Socket PGA370",
	},
	"INTEL_SOCKET_MPGA479M": {
		cpu:    "Intel® Mobile Celeron",
		socket: "Socket 479",
	},
	"INTEL_HASWELL": {
		cpu:    "Intel® 4th Gen (Haswell) Core i3/i5/i7",
		socket: "?",
	},
	"INTEL_FSP_RANGELEY": {
		cpu:    "Intel® Atom Rangeley (FSP)",
		socket: "?",
	},
	"INTEL_SOCKET_441": {
		cpu:    "Intel® Atom™ 230",
		socket: "Socket 441",
	},
	"INTEL_SOCKET_FC_PGA370": {
		cpu:    "Intel® Pentium® III, Celeron®",
		socket: "Socket PGA370",
	},
	"INTEL_EP80579": {
		cpu:    "Intel® EP80579",
		socket: "Intel® EP80579",
	},
	"INTEL_SOCKET_MFCBGA479": {
		cpu:    "Intel® Mobile Celeron",
		socket: "Socket 479",
	},
	"INTEL_WHISKEYLAKE": {
		cpu:    "Intel® Whiskey Lake",
		socket: "—",
	},
	"QC_IPQ806X": {
		cpu:    "Qualcomm IPQ806x",
		socket: "—",
	},
	"QUALCOMM_QCS405": {
		cpu:    "Qualcomm QCS405",
		socket: "—",
	},
	"ROCKCHIP_RK3288": {
		cpu:    "Rockchip RK3288",
		socket: "—",
	},
	"ROCKCHIP_RK3399": {
		cpu:    "Rockchip RK3399",
		socket: "—",
	},
	"VIA_C3": {
		cpu:    "VIA C3™",
		socket: "?",
	},
	"VIA_C7": {
		cpu:    "VIA C7™",
		socket: "?",
	},
	"VIA_NANO": {
		cpu:    "VIA NANO™",
		socket: "?",
	},
	"QEMU_X86": {
		cpu:    "QEMU x86",
		socket: "—",
	},
}

func prettifyCPU(cpu, north string, northNice string) (string, string) {
	if match, ok := cpuMappings[cpu]; ok {
		return match.cpu, match.socket
	}
	if cpu == "" {
		if match, ok := cpuMappings[north]; ok {
			return match.cpu, match.socket
		}
		if north == "INTEL_IRONLAKE" {
			return "Intel® 1st Gen (Westmere) Core i3/i5/i7", "?"
		}
		if north == "RDC_R8610" {
			return "RDC R8610", "—"
		}
		if (len(north) > 10 && north[0:10] == "AMD_AGESA_") || (len(north) > 7 && north[0:7] == "AMD_PI_") {
			return northNice, "?"
		}
		return north, north
	}
	if cpu == "INTEL_SOCKET_BGA956" {
		if north == "INTEL_GM45" {
			return "Intel® Core 2 Duo (Penryn)", "Socket P"
		}
		return "Intel® Pentium® M", "BGA956"
	}
	if cpu == "INTEL_SOCKET_RPGA989" || cpu == "INTEL_SOCKET_LGA1155" || cpu == "INTEL_SOCKET_RPGA988B" {
		socket := "Socket " + cpu[13:]
		if north == "INTEL_HASWELL" {
			return "Intel® 4th Gen (Haswell) Core i3/i5/i7", socket
		}
		if north == "INTEL_IVYBRIDGE" || north == "INTEL_FSP_IVYBRIDGE" {
			return "Intel® 3rd Gen (Ivybridge) Core i3/i5/i7", socket
		}
		if north == "INTEL_SANDYBRIDGE" {
			return "Intel® 2nd Gen (Sandybridge) Core i3/i5/i7", socket
		}
		return north, socket
	}
	return cpu, cpu
}

func collectBoards(dirs <-chan NamedFS) {
	for dir := range dirs {
		path := strings.Split(dir.Name, string(filepath.Separator))
		vendor, board := path[2], path[3]
		vendorNice, err := getNiceVendor(dir.Name, vendor)

		if err != nil {
			fmt.Fprintf(os.Stderr, "Could not find nice vendor name for %s: %v\n", dir.Name, err)
			continue
		}

		bi := readBoardInfo(dir)
		cat := Category(bi["Category"])
		if _, ok := data.CategoryNiceNames[cat]; !ok {
			cat = "unclass"
		}
		if bi["Vendor cooperation score"] == "" {
			bi["Vendor cooperation score"] = "—"
		}

		venboard := vendor + string(filepath.Separator) + board
		if bi["Clone of"] != "" {
			venboard = bi["Clone of"]
			venboard = strings.ReplaceAll(venboard, "/", string(filepath.Separator))
			newpath := filepath.Join(dir.Name, "..", "..", venboard)
			dir.Name = newpath
		}

		north, south, superio, cpu, partnum, err := readKconfig(dir)
		if err != nil {
			fmt.Fprintf(os.Stderr, "'%s' is not a mainboard directory: %v\n", dir.Name, err)
			// Continue with the path because that's what the
			// shell script did. We might want to change semantics
			// later.
		}
		northbridgeNice := prettifyNorthbridge(north)
		southbridgeNice := prettifySouthbridge(south)
		superIONice := prettifySuperIO(superio)
		cpuNice, socketNice := prettifyCPU(cpu, north, northbridgeNice)

		boardNice := bi["Board name"]
		if boardNice == "" {
			boardNice = partnum
		}
		if boardNice == "" {
			boardNice = strings.ReplaceAll(boardNice, "_", " ")
			boardNice = strings.ToUpper(boardNice)
		}

		b := Board{
			Vendor:                 vendor,
			Vendor2nd:              bi["Vendor name"],
			VendorNice:             vendorNice,
			VendorBoard:            vendor + "/" + board,
			Board:                  board,
			BoardNice:              boardNice,
			BoardURL:               bi["Board URL"],
			NorthbridgeNice:        northbridgeNice,
			SouthbridgeNice:        southbridgeNice,
			SuperIONice:            superIONice,
			CPUNice:                cpuNice,
			SocketNice:             socketNice,
			ROMPackage:             bi["ROM package"],
			ROMProtocol:            bi["ROM protocol"],
			ROMSocketed:            bi["ROM socketed"],
			FlashromSupport:        bi["Flashrom support"],
			VendorCooperationScore: bi["Vendor cooperation score"],
			VendorCooperationPage:  bi["Vendor cooperation page"],
		}
		if b.ROMPackage == "" {
			b.ROMPackage = "?"
		}
		if b.ROMProtocol == "" {
			b.ROMProtocol = "?"
		}

		if data.BoardsByCategory[cat] == nil {
			data.BoardsByCategory[cat] = []Board{}
		}
		data.BoardsByCategory[cat] = append(data.BoardsByCategory[cat], b)
	}
	for ci := range data.BoardsByCategory {
		cat := data.BoardsByCategory[ci]
		sort.Slice(data.BoardsByCategory[ci], func(i, j int) bool {
			if cat[i].Vendor == cat[j].Vendor {
				return cat[i].Board < cat[j].Board
			}
			return cat[i].Vendor < cat[j].Vendor
		})
	}
}