package ui

import (
	"fmt"
	"image"
	"reflect" // For DeepEqual

	"github.com/hajimehoshi/ebiten"

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

// type Interface encapsulates a user interface, providing a means to track UI
// state, draw the interface, and execute code when the widgets are interacted
// with.
//
// The graphics for UI elements were all created with a 640x480 resolution in
// mind. The interface transparently scales them all to the current screen size
// to compensate.
type Interface struct {
	menu    *assetstore.Menu
	static  []*assetstore.Sprite // Static elements in the interface
	ticks   int
	widgets []*Widget
}

func NewInterface(menu *assetstore.Menu) (*Interface, error) {
	iface := &Interface{
		menu: menu,
	}

	for _, record := range menu.Records() {
		if err := iface.addRecord(record); err != nil {
			return nil, err
		}
	}

	return iface, nil
}

// Find a widget by its hierarchical ID path
func (i *Interface) Widget(path ...int) (*Widget, error) {
	for _, widget := range i.widgets {
		if reflect.DeepEqual(path, widget.path) {
			return widget, nil
		}
	}

	return nil, fmt.Errorf("Couldn't find widget %#+v", path)
}

func (i *Interface) Update(screenX, screenY int) error {
	// Used in animation effects
	i.ticks += 1

	mousePos := i.getMousePos(screenX, screenY)

	// Iterate through all widgets, update mouse state
	for _, widget := range i.widgets {
		mouseIsOver := mousePos.In(widget.Bounds)
		widget.hovering(mouseIsOver)
		widget.mouseButton(mouseIsOver && ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft))
	}

	return nil
}

func (i *Interface) Draw(screen *ebiten.Image) error {
	geo := i.scale(screen.Size())
	do := &ebiten.DrawImageOptions{GeoM: geo}

	for _, sprite := range i.static {
		do.GeoM.Translate(geo.Apply(float64(sprite.XOffset), float64(sprite.YOffset)))
		if err := screen.DrawImage(sprite.Image, do); err != nil {
			return err
		}
		do.GeoM = geo
	}

	for _, widget := range i.widgets {
		img, err := widget.Image(i.ticks / 2)
		if err != nil {
			return err
		}

		if img == nil {
			continue
		}

		do.GeoM.Translate(geo.Apply(float64(widget.Bounds.Min.X), float64(widget.Bounds.Min.Y)))
		if err := screen.DrawImage(img, do); err != nil {
			return err
		}
		do.GeoM = geo
	}

	return nil
}

func (i *Interface) addRecord(record *menus.Record) error {
	switch record.Type {
	case menus.TypeStatic: // These are static
		if sprite, err := i.menu.Sprite(record.SpriteId[0]); err != nil {
			return err
		} else {
			i.static = append(i.static, sprite)
		}
	case menus.TypeMenu: // These aren't drawable and can be ignored
	case menus.TypeOverlay, menus.TypeMainButton: // Widgets \o/
		if widget, err := i.widgetFromRecord(record); err != nil {
			return err
		} else {
			i.widgets = append(i.widgets, widget)
		}

	default:
		return fmt.Errorf("ui.interface: encountered unknown menu record: %#+v", record)
	}

	// Recursively add all children
	for _, record := range record.Children {
		if err := i.addRecord(record); err != nil {
			return err
		}
	}

	return nil
}

// Works out how much we have to scale the current screen by to draw correctly
func (i *Interface) scale(w, h int) ebiten.GeoM {
	var geo ebiten.GeoM
	geo.Scale(float64(w)/640.0, float64(h)/480.0)

	return geo
}

func (i *Interface) unscale(w, h int) ebiten.GeoM {
	geo := i.scale(w, h)
	geo.Invert()

	return geo
}

// Returns the current position of the mouse in 640x480 coordinates. Needs the
// actual size of the screen to do so.
func (i *Interface) getMousePos(w, h int) image.Point {
	cX, cY := ebiten.CursorPosition()
	geo := i.unscale(w, h)

	sX, sY := geo.Apply(float64(cX), float64(cY))

	return image.Pt(int(sX), int(sY))
}

func (i *Interface) widgetFromRecord(record *menus.Record) (*Widget, error) {
	// FIXME: we assume that all widgets have a share sprite, but is that true?
	sprite, err := i.menu.Sprite(record.Share)
	if err != nil {
		return nil, err
	}

	var path []int
	for r := record; r != nil; r = r.Parent {
		path = append([]int{r.Id}, path...)
	}

	widget := &Widget{
		Bounds: sprite.Rect,
		path:   path,
		record: record,
		sprite: sprite,
	}

	switch record.Type {
	case menus.TypeMainButton:
		hovers, err := i.menu.Images(record.SpriteId[0], record.DrawType)
		if err != nil {
			return nil, err
		}
		widget.hoverAnimation = hovers

		sprite, err := i.menu.Sprite(record.Share + 1)
		if err != nil {
			return nil, err
		}
		widget.mouseButtonDownImage = sprite.Image
	}

	return widget, nil
}
