package hero

import (
	"fmt"
	"image"
	_ "image/png"
	"os"
	"path/filepath"
	"sort"

	"github.com/hajimehoshi/ebiten/v2"
)

// Asset paths
const (
	heroDir = "assets/hero"
)

// Animation state
type animState int

const (
	animIdle animState = iota
	animMove
)

type animationKey struct {
	direction Direction
	state     animState
}

// Sprite cache
var (
	knightAnimations map[animationKey][]*ebiten.Image
)

func init() {
	knightAnimations = make(map[animationKey][]*ebiten.Image)

	if err := loadKnightAnimations(); err != nil {
		panic(err)
	}
}

func getKnightSprite(direction Direction, moving bool, frameIndex int) *ebiten.Image {
	state := animIdle
	if moving {
		state = animMove
	}
	return frameFromSet(direction, state, frameIndex)
}

// Asset loading

func loadKnightAnimations() error {
	frames, err := loadAnimationFrames(heroDir)
	if err != nil {
		return err
	}

	knightAnimations[animationKey{DirLeft, animMove}] = frames
	knightAnimations[animationKey{DirLeft, animIdle}] = frames

	flippedFrames := make([]*ebiten.Image, len(frames))
	for i, frame := range frames {
		flippedFrames[i] = flipImageHorizontally(frame)
	}

	knightAnimations[animationKey{DirRight, animMove}] = flippedFrames
	knightAnimations[animationKey{DirRight, animIdle}] = flippedFrames

	return nil
}

func loadAnimationFrames(dir string) ([]*ebiten.Image, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	var files []string
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		name := entry.Name()
		if filepath.Ext(name) == ".png" {
			files = append(files, filepath.Join(dir, name))
		}
	}

	sort.Strings(files)

	var frames []*ebiten.Image
	for _, file := range files {
		img, err := loadImage(file)
		if err != nil {
			return nil, fmt.Errorf("load frame %s: %w", file, err)
		}
		frames = append(frames, img)
	}

	return frames, nil
}

func loadImage(path string) (*ebiten.Image, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	img, _, err := image.Decode(f)
	if err != nil {
		return nil, err
	}

	return ebiten.NewImageFromImage(img), nil
}

// Image manipulation

func flipImageHorizontally(img *ebiten.Image) *ebiten.Image {
	bounds := img.Bounds()
	w, h := bounds.Dx(), bounds.Dy()

	flipped := ebiten.NewImage(w, h)

	op := &ebiten.DrawImageOptions{}
	op.GeoM.Scale(-1, 1)
	op.GeoM.Translate(float64(w), 0)

	flipped.DrawImage(img, op)

	return flipped
}

func frameFromSet(direction Direction, state animState, frameIndex int) *ebiten.Image {
	if frameIndex < 0 {
		frameIndex = 0
	}

	if frames := knightAnimations[animationKey{direction, state}]; len(frames) > 0 {
		return frames[frameIndex%len(frames)]
	}

	if state == animMove {
		if frames := knightAnimations[animationKey{direction, animIdle}]; len(frames) > 0 {
			return frames[frameIndex%len(frames)]
		}
	} else {
		if frames := knightAnimations[animationKey{direction, animMove}]; len(frames) > 0 {
			return frames[frameIndex%len(frames)]
		}
	}

	return nil
}
