package menus

import (
	"fmt"
	"image/color"
	"io/ioutil"
	"log"
	"path/filepath"
	"strconv"
	"strings"

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

type MenuType int

const (
	TypeStatic          MenuType = 0
	TypeMenu            MenuType = 1
	TypeDragMenu        MenuType = 2 // Only seen in Configure_Vehicle_{Chaos,Ultra}
	TypeSimpleButton    MenuType = 3
	TypeDoorHotspot     MenuType = 30 // Like a button I guess? "FONTTYPE is animation speed"
	TypeDoorHotspot2    MenuType = 31 // Seems like a duplicate of the above? What's different?
	TypeLineKbd         MenuType = 40
	TypeThumb           MenuType = 45
	TypeLineBriefing    MenuType = 41
	TypeInvokeButton    MenuType = 50
	TypeDoorHotspot3    MenuType = 60 // Maybe? Appears in Arrange.mnu
	TypeOverlay         MenuType = 61
	TypeHypertext       MenuType = 70
	TypeCheckbox        MenuType = 91
	TypeEditBox         MenuType = 100
	TypeInventorySelect MenuType = 110
	TypeRadioButton     MenuType = 120
	TypeDropdownButton  MenuType = 200
	TypeComboBoxItem    MenuType = 205
	TypeAnimationSample MenuType = 220
	TypeAnimationHover  MenuType = 221 // FONTTYPE is animation speed. Only animate when hovered
	TypeMainButton      MenuType = 228
	TypeSlider          MenuType = 232
	TypeStatusBar       MenuType = 233
	TypeDialogue        MenuType = 300
)

// FIXME: certain elements - especially overlays - don't have a DESC specified
// in the .mnu file, but display text specified with a number in i18n. The only
// conclusion I can draw is that they're hardcoded in the binary and set from
// outside. So, do that here.
var DescOverrides = map[string]map[string]int{
	"main": {
		"2.6": 50992,
	},
	"newgame": {
		"2.5": 50993,
	},
	"levelply": {
		"2.6": 50996,
	},
}

// FIXME: Same idea with text overrides, only these aren't mentioned in the .dta
// file at all!
var TextOverrides = map[string]map[string]string{
	"main": {
		"2.7": "0.1-ordoor",
	},
}

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

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

	// From i18n
	Text string
	Help string

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

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

	BackgroundColor color.Color
	HypertextColor  color.Color

	// 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)
	name = strings.Replace(name, filepath.Ext(name), "", -1)
	name = strings.ToLower(name)

	// 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)
			vInt, err := strconv.Atoi(v) // FIXME:
			switch k {
			case "BACKGROUND COLOR 0..255..-1 trans":
				if err != nil {
					return nil, err
				}

				out.BackgroundColor = data.ColorPalette[vInt]
			case "HYPERTEXT COLOR 0..255":
				if err != nil {
					return nil, err
				}

				out.HypertextColor = data.ColorPalette[vInt]
			default:
				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(out, nil)
			case "SUBMENUID":
				record = newRecord(out, record.Toplevel())
			}
			setProperty(record, k, v)
		}
	}

	log.Printf("Menu properties: %#+v", out.Properties)

	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(menu *Menu, parent *Record) *Record {
	out := &Record{
		Menu:       menu,
		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 = MenuType(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 "FONTTYPE":
		r.FontType = vInt
	case "DRAW TYPE":
		r.DrawType = vInt
	case "SHARE":
		r.Share = vInt
	default:
		r.properties[k] = v
	}
}

type Replacer interface {
	ReplaceText(int, *string)
	ReplaceHelp(int, *string)
}

func (r *Record) Internationalize(replacer Replacer) {
	if overrides, ok := TextOverrides[r.Menu.Name]; ok {
		if override, ok := overrides[r.Path()]; ok {
			delete(r.properties, "DESC")
			r.Text = override
		}
	}

	if overrides, ok := DescOverrides[r.Menu.Name]; ok {
		if override, ok := overrides[r.Path()]; ok {
			r.properties["DESC"] = strconv.Itoa(override)
		}
	}

	id, err := strconv.Atoi(r.properties["DESC"])
	if err == nil {
		delete(r.properties, "DESC")
		replacer.ReplaceText(id, &r.Text)
		replacer.ReplaceHelp(id, &r.Help)
	}

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

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

func (r *Record) Path() string {
	var path []string

	for rec := r; rec != nil; rec = rec.Parent {
		path = append([]string{strconv.Itoa(rec.Id)}, path...)
	}

	return strings.Join(path, ".")
}
