package parser

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"review.coreboot.org/coreboot.git/util/intelp2m/config/p2m"
	"review.coreboot.org/coreboot.git/util/intelp2m/logs"
	"review.coreboot.org/coreboot.git/util/intelp2m/parser/template"
	"review.coreboot.org/coreboot.git/util/intelp2m/platforms"
	"review.coreboot.org/coreboot.git/util/intelp2m/platforms/common"
	"review.coreboot.org/coreboot.git/util/intelp2m/platforms/common/fields"
	"review.coreboot.org/coreboot.git/util/intelp2m/platforms/common/register/bits"
)

type EntryType int

const (
	EntryEmpty EntryType = iota
	EntryPad
	EntryGroup
	EntryReserved
)

// Parser entry
// ID        : pad id string
// Function  : the string that means the pad function
// DW0       : DW0 register struct
// DW1       : DW1 register struct
// 0wnership : host software ownership
type Entry struct {
	EType     EntryType
	ID        string
	Function  string
	DW0       uint32
	DW1       uint32
	Ownership bool
}

func (e *Entry) ToMacro() []string {
	constructor, err := platforms.GetConstructor()
	if err != nil {
		panic(err)
	}
	macro := common.CreateFrom(e.ID, e.Ownership, constructor(e.DW0, e.DW1), fields.Get())
	slices := strings.Split(macro.Generate(), "\n")
	return slices
}

// extractPad() extracts pad information from a string
func extractPad(line string) (Entry, error) {
	function, id, dw0, dw1, err := template.Apply(line)
	if err != nil {
		logs.Errorf("extraction error: %v", err)
		return Entry{EType: EntryEmpty}, err
	}

	pad := Entry{
		EType:     EntryPad,
		Function:  function,
		ID:        id,
		DW0:       dw0,
		DW1:       dw1,
		Ownership: common.Acpi,
	}

	if dw0 == bits.All32 {
		pad.EType = EntryReserved
	}

	return pad, nil
}

// extractGroup() extracts information about the pad group from the string
func extractGroup(line string) Entry {
	group := Entry{
		EType:    EntryGroup,
		Function: line,
	}
	return group
}

// checkGPPG() checks whether the desired group is present in the string
func checkKeywords(line string, slice []string) bool {
	for _, key := range slice {
		if strings.Contains(line, key) {
			return true
		}
	}
	return false
}

// Extract() extracts pad information from a string
func Extract(line string) Entry {
	if checkKeywords(line, []string{"GPIO Community", "GPIO Group"}) {
		return extractGroup(line)
	}
	if gppg, err := platforms.GetGPPGroups(); err != nil {
		logs.Errorf("extract error: %v: skip line <%s>", err, line)
		return Entry{EType: EntryEmpty}
	} else if checkKeywords(line, gppg) {
		pad, err := extractPad(line)
		if err != nil {
			logs.Errorf("extract pad info from %s: %v", line, err)
			return Entry{EType: EntryEmpty}
		}
		return pad
	}
	logs.Infof("skip line <%s>", line)
	return Entry{EType: EntryEmpty}
}

// Run() starts the file parsing process
func Run() ([]Entry, error) {
	entries := make([]Entry, 0)

	file, err := os.Open(p2m.Config.InputPath)
	if err != nil {
		err = fmt.Errorf("input file error: %v", err)
		logs.Errorf("%v", err)
		return nil, err
	}
	defer file.Close()

	logs.Infof("parse %s file", p2m.Config.InputPath)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		entry := Extract(line)
		if entry.EType != EntryEmpty {
			entries = append(entries, entry)
		}
	}

	logs.Infof("successfully completed: %d entries", len(entries))
	return entries, nil
}
