package ui

import (
	"image"
	"log"

	"github.com/hajimehoshi/ebiten"

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

func init() {
	registerBuilder(menus.TypeStatic, registerStatic)
	registerBuilder(menus.TypeHypertext, registerHypertext)
	registerBuilder(menus.TypeOverlay, registerOverlay)
	registerBuilder(menus.TypeAnimationSample, registerAnimation)
	registerBuilder(menus.TypeAnimationHover, registerAnimationHover)
}

// A non-interactive element is not a widget; it merely displays some pixels and
// may optionally have a tooltip for display within bounds.
//
// For non-animated non-interactive elements, just give them a single frame.
type noninteractive struct {
	path   string
	frames animation
	rect   image.Rectangle

	// Some non-interactives, e.g., overlays, are an image + text to be shown
	textImg    *ebiten.Image
	textOffset image.Point

	clickImpl // Alright, alright, it turns out the bridge mission briefing is clickable
	hoverImpl
}

// This particular animation has entry and exit sequences, which are invoked
// when entering and leaving hover, respectively. Example: bridge doors
type animationHover struct {
	noninteractive           // Use the frames in here for the "enter hover" animation
	exitFrames     animation // and here the "exit hover" animation

	atTick  int // Tracks progress through the frames
	opening bool
	closing bool
}

func registerStatic(d *Driver, r *menus.Record) error {
	// FIXME: SpriteID takes precedence over SHARE if present, but is that right?
	spriteId := r.Share
	if len(r.SpriteId) > 0 && r.SpriteId[0] != -1 {
		spriteId = r.SpriteId[0]
	}

	sprite, err := d.menu.Sprite(spriteId)
	if err != nil {
		return err
	}

	ni := &noninteractive{
		path:      r.Path(),
		frames:    animation{sprite.Image},
		hoverImpl: hoverImpl{text: r.Text},
		rect:      sprite.Rect,
	}

	d.hoverables = append(d.hoverables, ni)
	d.paintables = append(d.paintables, ni)

	return nil
}

func registerHypertext(d *Driver, r *menus.Record) error {
	sprite, err := d.menu.Sprite(r.Share)
	if err != nil {
		return err
	}

	ni := &noninteractive{
		path:      r.Path(),
		hoverImpl: hoverImpl{text: r.Text},
		rect:      sprite.Rect,
	}

	d.clickables = append(d.clickables, ni)
	d.hoverables = append(d.hoverables, ni)

	return nil
}

// An overlay is a static image + some text that needs to be rendered
func registerOverlay(d *Driver, r *menus.Record) error {
	sprite, err := d.menu.Sprite(r.Share)
	if err != nil {
		return err
	}

	ni := &noninteractive{
		path:   r.Path(),
		frames: animation{sprite.Image},
		rect:   sprite.Rect,
	}

	if r.Text != "" {
		// FIXME: is this always right? Seems to make sense for Main.mnu
		fnt := d.menu.Font(r.FontType/10 - 1)

		textImg, err := fnt.DrawLine(r.Text)
		if err != nil {
			return err
		}

		ni.textImg = textImg

		// Centre the image
		xSlack := ni.rect.Dx() - textImg.Bounds().Dx()
		if xSlack > 0 {
			ni.textOffset.X = xSlack / 2
		}

		ySlack := ni.rect.Dy() - textImg.Bounds().Dy()
		if ySlack > 0 {
			ni.textOffset.Y = ySlack / 2
		}

	} else {
		log.Printf("Overlay without text detected: %#+v", r)
	}

	d.paintables = append(d.paintables, ni)

	return nil
}

// An animation is a non-interactive element that displays something in a loop
func registerAnimation(d *Driver, r *menus.Record) error {
	sprite, err := d.menu.Sprite(r.SpriteId[0])
	if err != nil {
		return err
	}

	frames, err := d.menu.Images(r.SpriteId[0], r.DrawType)
	if err != nil {
		return err
	}

	ani := &noninteractive{
		path:      r.Path(),
		frames:    animation(frames),
		hoverImpl: hoverImpl{text: r.Text},
		rect:      sprite.Rect,
	}

	d.hoverables = append(d.hoverables, ani)
	d.paintables = append(d.paintables, ani)

	return nil
}

func registerAnimationHover(d *Driver, r *menus.Record) error {
	sprite, err := d.menu.Sprite(r.SpriteId[0])
	if err != nil {
		return err
	}

	enterFrames, err := d.menu.Images(r.SpriteId[0], r.DrawType)
	if err != nil {
		return err
	}

	exitFrames, err := d.menu.Images(r.SpriteId[0]+r.DrawType, r.DrawType)
	if err != nil {
		return err
	}

	ani := &animationHover{
		noninteractive: noninteractive{
			path:      r.Path(),
			frames:    animation(enterFrames),
			hoverImpl: hoverImpl{text: r.Text},
			rect:      sprite.Rect,
		},

		exitFrames: animation(exitFrames),
	}

	d.hoverables = append(d.hoverables, ani)
	d.paintables = append(d.paintables, ani)

	return nil
}

func (n *noninteractive) id() string {
	return n.path
}

func (n *noninteractive) bounds() image.Rectangle {
	return n.rect
}

func (n *noninteractive) regions(tick int) []region {
	out := oneRegion(n.bounds().Min, n.frames.image(tick))

	if n.textImg != nil {
		out = append(out, oneRegion(n.textPos(), n.textImg)...)
	}

	return out
}

func (n *noninteractive) textPos() image.Point {
	return image.Pt(n.rect.Min.X+n.textOffset.X, n.rect.Min.Y+n.textOffset.Y)
}

func (a *animationHover) regions(tick int) []region {
	if a.opening || a.closing {
		var anim animation
		if a.opening {
			anim = a.frames
		} else {
			anim = a.exitFrames
		}

		out := oneRegion(a.bounds().Min, anim[a.atTick])

		if a.atTick < len(anim)-1 {
			a.atTick += 1
		} else if !a.hoverState() {
			a.closing = false
		}

		return out
	}

	// Nothing doing, show a closed door
	return oneRegion(a.bounds().Min, a.frames.image(0))
}

func (a *animationHover) setHoverState(value bool) {
	a.atTick = 0
	a.opening = value
	a.closing = !value

	a.hoverImpl.setHoverState(value)
}
