package assetstore

import (
	"fmt"
	"image"
	"log"
	"path/filepath"
	"strings"

	"github.com/hajimehoshi/ebiten"

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

type Object struct {
	assets  *AssetStore
	sprites []*Sprite

	raw        *data.Object
	NumSprites int
	Name       string
}

type Sprite struct {
	obj *Object

	XOffset int // TODO: replace these everywhere with Rect
	YOffset int
	Width   int
	Height  int

	ID    string
	Rect  image.Rectangle
	Image *ebiten.Image
}

func (a *AssetStore) Object(name string) (*Object, error) {
	name = canonical(name)

	if obj, ok := a.objs[name]; ok {
		return obj, nil
	}
	log.Printf("Loading object %v", name)

	filename, err := a.lookup(name, "obj", "Obj")
	if err != nil {
		return nil, err
	}

	obj, err := a.ObjectByPath(filename)
	if err != nil {
		return nil, err
	}

	a.objs[name] = obj

	return obj, nil
}

// FIXME: Objects loaded by path are not cached
func (a *AssetStore) ObjectByPath(path string) (*Object, error) {
	name := filepath.Base(path)
	name = strings.Replace(name, filepath.Ext(name), "", -1)
	name = canonical(name)

	raw, err := data.LoadObjectLazily(path)
	if err != nil {
		return nil, err
	}

	obj := &Object{
		assets:     a,
		sprites:    make([]*Sprite, int(raw.NumSprites)),
		raw:        raw,
		NumSprites: int(raw.NumSprites),
		Name:       canonical(name),
	}

	return obj, nil
}

// Loads all sprites in the object eagerly
func (o *Object) LoadSprites() error {
	for i := 0; i < o.NumSprites; i++ {
		if _, err := o.Sprite(i); err != nil {
			return err
		}
	}

	return nil
}

func (o *Object) Sprites(start, count int) ([]*Sprite, error) {
	out := make([]*Sprite, count)

	for i := start; i < start+count; i++ {
		sprite, err := o.Sprite(i)
		if err != nil {
			return nil, err
		}

		out[i-start] = sprite
	}

	return out, nil
}

func (o *Object) Sprite(idx int) (*Sprite, error) {
	if sprite := o.sprites[idx]; sprite != nil {
		return sprite, nil
	}

	if o.raw.Sprites[idx] == nil {
		if err := o.raw.LoadSprite(idx); err != nil {
			return nil, err
		}
	}

	raw := o.raw.Sprites[idx]
	img, err := ebiten.NewImageFromImage(raw.ToImage(), ebiten.FilterDefault)
	if err != nil {
		return nil, err
	}

	rect := image.Rect(
		int(raw.XOffset),
		int(raw.YOffset),
		int(raw.XOffset+raw.Width),
		int(raw.YOffset+raw.Height),
	)

	sprite := &Sprite{
		ID:      fmt.Sprintf("%v:%v", o.raw.Name, idx),
		obj:     o,
		Width:   rect.Dx(),
		Height:  rect.Dy(),
		XOffset: rect.Min.X,
		YOffset: rect.Min.Y,
		Rect:    rect,
		Image:   img,
	}

	o.sprites[idx] = sprite

	return sprite, nil
}
