package ui

import (
	"image"
	"math"
	"strconv"

	"code.ur.gs/lupine/ordoor/internal/assetstore"
	"code.ur.gs/lupine/ordoor/internal/menus"
)

func init() {
	registerBuilder(menus.TypeCheckbox, registerCheckbox)
	registerBuilder(menus.TypeSlider, registerSlider)
	registerBuilder(menus.TypeInventorySelect, registerInventorySelect)
}

// A checkbox can be a fancy button
type checkbox struct {
	button

	valueImpl
}

// A slider is harder. Two separate elements to render
type slider struct {
	path string

	baseSpr   *assetstore.Sprite
	clickSpr  *assetstore.Sprite
	sliderSpr *assetstore.Sprite

	hv    bool        // horizontal (false) or vertical (true) slider
	steps map[int]int // A list of valid steps. value:offset

	clickImpl
	mouseImpl
	valueImpl
}

// An inventory select is a sort of radio button. If 2 share the same menu,
// selecting one deselects the other. Otherwise, they act like checkboxes
type inventorySelect struct {
	checkbox

	parentPath string
	others     []*inventorySelect
}

// A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled.
func registerCheckbox(d *Driver, r *menus.Record) error {
	sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, disabled, checked
	if err != nil {
		return err
	}

	checkbox := &checkbox{
		button: button{
			path:      r.Path(),
			baseSpr:   sprites[0], // unchecked
			clickSpr:  sprites[2], // checked
			frozenSpr: sprites[1],
			hoverImpl: hoverImpl{text: r.Text},
		},
		valueImpl: valueImpl{str: "0"},
	}

	d.clickables = append(d.clickables, checkbox)
	d.freezables = append(d.freezables, checkbox)
	d.hoverables = append(d.hoverables, checkbox)
	d.paintables = append(d.paintables, checkbox)
	d.valueables = append(d.valueables, checkbox)

	return nil
}

func registerSlider(d *Driver, r *menus.Record) error {
	sprites, err := d.menu.Sprites(r.Share, 3) // base, clicked, slider element
	if err != nil {
		return err
	}

	slider := &slider{
		path:      r.Path(),
		baseSpr:   sprites[0],
		clickSpr:  sprites[1],
		sliderSpr: sprites[2],
		hv:        sprites[0].Rect.Dy() > sprites[0].Rect.Dx(), // A best guess
	}

	d.clickables = append(d.clickables, slider)
	d.mouseables = append(d.mouseables, slider)
	d.paintables = append(d.paintables, slider)
	d.valueables = append(d.valueables, slider)

	return nil
}

func registerInventorySelect(d *Driver, r *menus.Record) error {
	sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, checked, disabled
	if err != nil {
		return err
	}

	element := &inventorySelect{
		checkbox: checkbox{
			button: button{
				path:      r.Path(),
				baseSpr:   sprites[0], // unchecked
				clickSpr:  sprites[1], // checked
				frozenSpr: sprites[2], // disabled
				hoverImpl: hoverImpl{text: r.Text},
			},

			valueImpl: valueImpl{str: "0"},
		},
	}

	d.clickables = append(d.clickables, element)
	d.freezables = append(d.freezables, element)
	d.hoverables = append(d.hoverables, element)
	d.paintables = append(d.paintables, element)
	d.valueables = append(d.valueables, element)

	if r.Parent == nil {
		return nil
	}

	element.parentPath = r.Parent.Path()

	// Now update all inventory selects belonging to the same menu so they share
	// a list of all inventory selects. This will be replaced several times as
	// the menu is built.
	var inventorySelects []*inventorySelect
	for _, valueable := range d.valueables {
		if is, ok := valueable.(*inventorySelect); ok && is.parentPath == element.parentPath {
			inventorySelects = append(inventorySelects, is)
		}
	}

	for _, is := range inventorySelects {
		is.others = inventorySelects
	}

	// Select the first in the list
	if len(inventorySelects) == 1 {
		element.setValue("1")
	}

	return nil
}

func (c *checkbox) registerMouseClick() {
	if c.value() == "1" { // Click disables
		c.setValue("0")
	} else { // Click enables
		c.setValue("1")
	}
}

func (c *checkbox) regions(tick int) []region {
	if c.isFrozen() {
		return oneRegion(c.bounds().Min, c.frozenSpr.Image)
	}

	if c.value() == "1" {
		return oneRegion(c.bounds().Min, c.clickSpr.Image)
	}

	return oneRegion(c.bounds().Min, c.baseSpr.Image)
}

func (s *slider) id() string {
	return s.path
}

// The bounds of the slider are the whole thing
func (s *slider) bounds() image.Rectangle {
	return s.baseSpr.Rect
}

func (s *slider) registerMouseClick() {
	var value int
	if s.hv {
		value = s.valueFromPix(s.bounds().Min.Y, s.sliderPos().Y)
	} else {
		value = s.valueFromPix(s.bounds().Min.X, s.sliderPos().X)
	}

	s.valueImpl.str = strconv.Itoa(value)

	s.clickImpl.registerMouseClick()
}

func (s *slider) regions(tick int) []region {
	var out []region

	if s.mouseDownState() {
		out = append(out, oneRegion(s.bounds().Min, s.clickSpr.Image)...)
	} else {
		out = append(out, oneRegion(s.bounds().Min, s.baseSpr.Image)...)
	}

	out = append(out, oneRegion(s.sliderPos(), s.sliderSpr.Image)...)

	return out
}

func (s *slider) sliderPos() image.Point {
	if s.hv {
		return s.sliderPosVertical()
	}

	return s.sliderPosHorizontal()
}

func (s *slider) sliderPosHorizontal() image.Point {
	pos := s.bounds().Min

	if s.mouseDownState() {
		pos.X = s.constrainPix(s.bounds().Min.X, s.mouseImpl.pos.X)
	} else {
		pos.X = s.bounds().Min.X + s.offsetFromValue(s.valueInt())
	}

	return pos
}

func (s *slider) sliderPosVertical() image.Point {
	pos := s.bounds().Min

	if s.mouseDownState() {
		pos.Y = s.constrainPix(s.bounds().Min.Y, s.mouseImpl.pos.Y)
	} else {
		pos.Y = s.bounds().Min.Y + s.offsetFromValue(s.valueInt())
	}

	return pos
}

func (s *slider) valueFromPix(start, actual int) int {
	if len(s.steps) == 0 {
		return actual - start
	}

	minDistance := 9999
	var out int

	for value, offset := range s.steps {
		pix := start + offset
		distance := int(math.Abs(float64(actual - pix)))

		if distance < minDistance {
			minDistance = distance
			out = value
		}
	}

	return out
}

func (s *slider) offsetFromValue(value int) int {
	if len(s.steps) == 0 {
		return value
	}

	value = s.constrainValue(value)

	return s.steps[value]
}

func (s *slider) constrainPix(start, actual int) int {
	if len(s.steps) == 0 {
		return actual
	}

	minDistance := 9999
	out := actual

	for _, offset := range s.steps {
		pix := start + offset
		distance := int(math.Abs(float64(actual - pix)))

		if distance < minDistance {
			minDistance = distance
			out = pix
		}
	}

	return out
}
func (s *slider) constrainValue(actual int) int {
	if len(s.steps) == 0 {
		return actual
	}

	minDistance := 9999
	out := actual

	for value, _ := range s.steps {
		distance := int(math.Abs(float64(value - actual)))

		if distance < minDistance {
			minDistance = distance
			out = value
		}
	}

	return out
}

func (s *slider) valueInt() int {
	v, _ := strconv.Atoi(s.valueImpl.value())

	return s.constrainValue(v)
}

func (s *slider) value() string {
	return strconv.Itoa(s.valueInt())
}

func (i *inventorySelect) registerMouseClick() {
	// Do nothing if we're already selected
	if i.value() == "1" {
		return
	}

	// Turn us on, turn everyone else off
	for _, other := range i.others {
		other.setValue("0")
	}

	i.setValue("1")
}
