package parser

import (
	"bufio"
	"fmt"
	"strings"
	"strconv"
)

import "../platforms/common"
import "../platforms/snr"
import "../platforms/lbg"
import "../platforms/apl"
import "../platforms/cnl"
import "../config"

// PlatformSpecific - platform-specific interface
type PlatformSpecific interface {
	GenMacro(id string, dw0 uint32, dw1 uint32, ownership uint8) string
	GroupNameExtract(line string) (bool, string)
	KeywordCheck(line string) bool
}

// padInfo - information about pad
// id        : pad id string
// offset    : the offset of the register address relative to the base
// function  : the string that means the pad function
// dw0       : DW0 register value
// dw1       : DW1 register value
// ownership : host software ownership
type padInfo struct {
	id        string
	offset    uint16
	function  string
	dw0       uint32
	dw1       uint32
	ownership uint8
}

// generate - wrapper for Fprintf(). Writes text to the file specified
// in config.OutputGenFile
func (info *padInfo) generate(lvl int, line string, a ...interface{}) {
	if config.InfoLevelGet() >= lvl {
		fmt.Fprintf(config.OutputGenFile, line, a...)
	}
}

// titleFprint - print GPIO group title to file
// /* ------- GPIO Group GPP_L ------- */
func (info *padInfo) titleFprint() {
	info.generate(0, "\n\t/* %s */\n", info.function)
}

// reservedFprint - print reserved GPIO to file as comment
// /* GPP_H17 - RESERVED */
func (info *padInfo) reservedFprint() {
	info.generate(2, "\n")
	// small comment about reserved port
	info.generate(0, "\t/* %s - %s */\n", info.id, info.function)
}

// padInfoMacroFprint - print information about current pad to file using
// special macros:
// PAD_CFG_NF(GPP_F1, 20K_PU, PLTRST, NF1), /* SATAXPCIE4 */
// gpio  : gpio.c file descriptor
// macro : string of the generated macro
func (info *padInfo) padInfoMacroFprint(macro string) {
	info.generate(2,
		"\n\t/* %s - %s */\n\t/* DW0: 0x%0.8x, DW1: 0x%0.8x */\n",
		info.id,
		info.function,
		info.dw0,
		info.dw1)
	info.generate(0, "\t%s", macro)
	if config.InfoLevelGet() == 1 {
		info.generate(1, "\t/* %s */", info.function)
	}
	info.generate(0, "\n")
}

// ParserData - global data
// line       : string from the configuration file
// padmap     : pad info map
// RawFmt     : flag for generating pads config file with DW0/1 reg raw values
// Template   : structure template type of ConfigFile
type ParserData struct {
	platform   PlatformSpecific
	line       string
	padmap     []padInfo
	ownership  map[string]uint32
}

// hostOwnershipGet - get the host software ownership value for the corresponding
// pad ID
// id : pad ID string
// return the host software ownership form the parser struct
func (parser *ParserData) hostOwnershipGet(id string) uint8 {
	var ownership uint8 = 0
	status, group := parser.platform.GroupNameExtract(id)
	if config.TemplateGet() == config.TempInteltool && status {
		numder, _ := strconv.Atoi(strings.TrimLeft(id, group))
		if (parser.ownership[group] & (1 << uint8(numder))) != 0 {
			ownership = 1
		}
	}
	return ownership
}

// padInfoExtract - adds a new entry to pad info map
// return error status
func (parser *ParserData) padInfoExtract() int {
	var function, id string
	var dw0, dw1 uint32
	var template = map[int]template{
		config.TempInteltool: useInteltoolLogTemplate,
		config.TempGpioh    : useGpioHTemplate,
		config.TempSpec     : useYourTemplate,
	}
	if template[config.TemplateGet()](parser.line, &function, &id, &dw0, &dw1) == 0 {
		pad := padInfo{id: id,
			function: function,
			dw0: dw0,
			dw1: dw1,
			ownership: parser.hostOwnershipGet(id)}
		parser.padmap = append(parser.padmap, pad)
		return 0
	}
	fmt.Printf("This template (%d) does not match!\n", config.TemplateGet())
	return -1
}

// communityGroupExtract
func (parser *ParserData) communityGroupExtract() {
	pad := padInfo{function: parser.line}
	parser.padmap = append(parser.padmap, pad)
}

// PlatformSpecificInterfaceSet - specific interface for the platform selected
// in the configuration
func (parser *ParserData) PlatformSpecificInterfaceSet() {
	var platform = map[uint8]PlatformSpecific {
		config.SunriseType   : snr.PlatformSpecific{},
		// See platforms/lbg/macro.go
		config.LewisburgType : lbg.PlatformSpecific{
			InheritanceTemplate : snr.PlatformSpecific{},
		},
		config.ApolloType    : apl.PlatformSpecific{},
		config.CannonType    : cnl.PlatformSpecific{
			InheritanceTemplate : snr.PlatformSpecific{},
		},
	}
	parser.platform = platform[config.PlatformGet()]
}

// PadMapFprint - print pad info map to file
func (parser *ParserData) PadMapFprint() {
	for _, pad := range parser.padmap {
		switch pad.dw0 {
		case 0:
			pad.titleFprint()
		case 0xffffffff:
			pad.reservedFprint()
		default:
			str := parser.platform.GenMacro(pad.id, pad.dw0, pad.dw1, pad.ownership)
			pad.padInfoMacroFprint(str)
		}
	}
}

// Register - read specific platform registers (32 bits)
// line         : string from file with pad config map
// nameTemplate : register name femplate to filter parsed lines
// return
//     valid  : true if the dump of the register in intertool.log is set in accordance
//              with the template
//     name   : full register name
//     offset : register offset relative to the base address
//     value  : register value
func (parser *ParserData) Register(nameTemplate string) (
		valid bool, name string, offset uint32, value uint32) {
	if strings.Contains(parser.line, nameTemplate) &&
		config.TemplateGet() == config.TempInteltool {
		if registerInfoTemplate(parser.line, &name, &offset, &value) == 0 {
			fmt.Printf("\n\t/* %s : 0x%x : 0x%x */\n", name, offset, value)
			return true, name, offset, value
		}
	}
	return false, "ERROR", 0, 0
}

// padOwnershipExtract - extract Host Software Pad Ownership from inteltool dump
//                       return true if success
func (parser *ParserData) padOwnershipExtract() bool {
	var group string
	status, name, offset, value := parser.Register("HOSTSW_OWN_GPP_")
	if status {
		_, group = parser.platform.GroupNameExtract(parser.line)
		parser.ownership[group] = value
		fmt.Printf("\n\t/* padOwnershipExtract: [offset 0x%x] %s = 0x%x */\n",
				offset, name, parser.ownership[group])
	}
	return status
}

// padConfigurationExtract - reads GPIO configuration registers and returns true if the
//                           information from the inteltool log was successfully parsed.
func (parser *ParserData) padConfigurationExtract() bool {
	// Only for Sunrise or CannonLake, and only for inteltool.log file template
	if config.TemplateGet() != config.TempInteltool || config.IsPlatformApollo() {
		return false
	}
	return parser.padOwnershipExtract()
}

// Parse pads groupe information in the inteltool log file
// ConfigFile : name of inteltool log file
func (parser *ParserData) Parse() {
	// Read all lines from inteltool log file
	fmt.Println("Parse IntelTool Log File...")

	// determine the platform type and set the interface for it
	parser.PlatformSpecificInterfaceSet()

	// map of thepad ownership registers for the GPIO controller
	parser.ownership = make(map[string]uint32)

	scanner := bufio.NewScanner(config.InputRegDumpFile)
	for scanner.Scan() {
		parser.line = scanner.Text()
		isIncluded, _ := common.KeywordsCheck(parser.line, "GPIO Community", "GPIO Group");
		if isIncluded {
			parser.communityGroupExtract()
		} else if !parser.padConfigurationExtract() && parser.platform.KeywordCheck(parser.line) {
			if parser.padInfoExtract() != 0 {
				fmt.Println("...error!")
			}
		}
	}
	fmt.Println("...done!")
}