package menus

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strconv"
	"strings"

	"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
)

const (
	TypeStatic     = 0
	TypeMenu       = 1
	TypeOverlay    = 61
	TypeMainButton = 228
)

type Record struct {
	Parent   *Record
	Children []*Record

	Id       int
	Type     int
	DrawType int
	FontType int
	Active   bool
	SpriteId []int
	Share    int
	X        int
	Y        int
	Desc     string

	// FIXME: turn these into first-class data
	properties map[string]string
}

type Menu struct {
	Name string
	// TODO: load these
	ObjectFiles []string
	FontNames   []string

	// FIXME: turn these into first-class data
	Properties map[string]string

	// The actual menu records. There are multiple top-level items. Submenus are
	// only ever nested one deep.
	Records []*Record
}

func LoadMenu(filename string) (*Menu, error) {
	name := filepath.Base(filename)

	// FIXME: this needs turning into a real parser sometime
	scanner, err := asciiscan.New(filename)
	if err != nil {
		return nil, err
	}

	defer scanner.Close()

	var str string
	var record *Record

	section := 0
	isProp := false
	out := &Menu{
		Name:       name,
		Properties: map[string]string{},
	}

	for {
		str, err = scanner.ConsumeString()
		if err != nil {
			return nil, err
		}

		// Whether the lines are properties or not alternate with each section,
		// except the records use `*` as a separator
		if section < 3 && isProp != asciiscan.IsProperty(str) {
			section += 1
			isProp = !isProp
		}

		if str == "~" {
			break
		}

		switch section {
		case 0: // List of object files
			out.ObjectFiles = append(out.ObjectFiles, str)
		case 1: // List of properties
			k, v := asciiscan.ConsumeProperty(str)
			out.Properties[k] = v
		case 2: // list of fonts
			// FIXME: do we need to do something cleverer here?
			if str == "NULL" {
				continue
			}
			out.FontNames = append(out.FontNames, str)
		case 3: // Menu records
			if str == "*" { // NEXT RECORD
				out.Records = append(out.Records, record.Toplevel())
				continue
			}

			k, v := asciiscan.ConsumeProperty(str)
			switch k {
			case "MENUID":
				record = newRecord(nil)
			case "SUBMENUID":
				record = newRecord(record.Toplevel())
			}
			setProperty(record, k, v)
		}
	}

	return out, nil
}

func LoadMenus(dir string) (map[string]*Menu, error) {
	fis, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	out := make(map[string]*Menu, len(fis))

	for _, fi := range fis {
		relname := fi.Name()
		basename := filepath.Base(relname)
		extname := filepath.Ext(relname)

		// Skip anything that isn't a .mnu file
		if !strings.EqualFold(extname, ".mnu") {
			continue
		}

		built, err := LoadMenu(filepath.Join(dir, relname))
		if err != nil {
			return nil, fmt.Errorf("%s: %v", filepath.Join(dir, relname), err)
		}

		out[basename] = built
	}

	return out, nil
}

func newRecord(parent *Record) *Record {
	out := &Record{
		Parent:     parent,
		properties: map[string]string{},
	}

	if parent != nil {
		parent.Children = append(parent.Children, out)
	}

	return out
}

func (r *Record) Toplevel() *Record {
	if r.Parent != nil {
		return r.Parent.Toplevel()
	}

	return r
}

func setProperty(r *Record, k, v string) {
	vSplit := strings.Split(v, ",")
	vInt, _ := strconv.Atoi(v)
	vSplitInt := make([]int, len(vSplit))

	for i, subV := range vSplit {
		vSplitInt[i], _ = strconv.Atoi(subV)
	}

	switch k {
	case "MENUID", "SUBMENUID":
		r.Id = vInt
	case "MENUTYPE", "SUBMENUTYPE":
		r.Type = vInt
	case "ACTIVE":
		r.Active = (vInt != 0)
	case "SPRITEID":
		r.SpriteId = vSplitInt
	case "X-CORD":
		r.X = vInt
	case "Y-CORD":
		r.Y = vInt
	case "DESC":
		r.Desc = v
	case "FONTTYPE":
		r.FontType = vInt
	case "DRAW TYPE":
		r.DrawType = vInt
	case "SHARE":
		r.Share = vInt
	default:
		r.properties[k] = v
	}
}

type Replacer interface {
	Replace(int, *string)
}

func (r *Record) Internationalize(replacer Replacer) {
	id, err := strconv.Atoi(r.Desc)
	if err == nil {
		replacer.Replace(id, &r.Desc)
	}

	for _, child := range r.Children {
		child.Internationalize(replacer)
	}
}

func (m *Menu) Internationalize(replacer Replacer) {
	for _, record := range m.Records {
		record.Internationalize(replacer)
	}
}
