package assets

import (
	"bufio"
	"fmt"
	_ "image/png"
	"io/fs"
	"log"
	"openquell/util"
	"os"
	"path/filepath"
	"strings"

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

var Levels = LoadLevels("levels")
var Tiles = InitTiles()

// Tile:  contains  image, identifier  (as  used  in level  data)  and
// additional properties
type Tile struct {
	Id          byte
	Sprite      *ebiten.Image
	Class       string
	Solid       bool
	Player      bool
	Renderable  bool
	Velocity    bool
	Collectible bool
	Particle    int // -1=unused, 0-3 = show image of slice
	Particles   []*ebiten.Image
}

func NewTilePlayer() *Tile {
	return &Tile{
		Id:         'S',
		Sprite:     Assets["sphere-blue"],
		Class:      "sphere",
		Renderable: true,
		Player:     true,
		Velocity:   true,
	}
}

func NewTileBlock(class string) *Tile {
	return &Tile{
		Id:         '#',
		Sprite:     Assets[class],
		Class:      class,
		Solid:      true,
		Renderable: true,
	}
}

func NewTileCollectible(class string) *Tile {
	return &Tile{
		Id:          'o',
		Sprite:      Assets[class],
		Class:       class,
		Solid:       false,
		Renderable:  true,
		Collectible: true,
	}
}

func NewTileParticle(class []string) *Tile {
	sprites := []*ebiten.Image{}

	for _, sprite := range class {
		sprites = append(sprites, Assets[sprite])
	}

	return &Tile{
		Id:         '*',
		Class:      "particle",
		Solid:      false,
		Renderable: false,
		Particle:   -1,
		Particles:  sprites,
	}
}

// used to map level data bytes to actual tiles
type TileRegistry map[byte]*Tile

// holds a raw level spec:
//
// Name:        the name of the level file w/o the .lvl extension
// Background:  an image name used as game background for this level
// Description: text to display on top
// Data:        a level spec consisting of chars of the above mapping and spaces, e.g.:
// ####
// #  #
// ####
//
// Each level data must be 20 chars wide (= 640 px width) and 15 chars
// high (=480 px height).
type RawLevel struct {
	Name        string
	Description string
	Background  *ebiten.Image
	Data        []byte
}

func InitTiles() TileRegistry {
	return TileRegistry{
		' ': {Id: ' ', Class: "floor", Renderable: false},
		'#': NewTileBlock("block-grey32"),
		'S': NewTilePlayer(),
		'o': NewTileCollectible("collectible-orange"),
		'*': NewTileParticle([]string{
			"particle-dust-0", "particle-dust-0", "particle-dust-1", "particle-dust-1",
			"particle-dust-0", "particle-dust-2", "particle-dust-3", "particle-dust-3",
		}),
	}
}

// load levels at compile time into ram, creates a slice of raw levels
func LoadLevels(dir string) []RawLevel {
	levels := []RawLevel{}

	// we use embed.FS to iterate over all files in ./levels/
	entries, err := assetfs.ReadDir(dir)
	if err != nil {
		log.Fatalf("failed to read level dir %s: %s", dir, err)
	}

	for _, levelfile := range entries {
		if levelfile.Type().IsRegular() && strings.Contains(levelfile.Name(), ".lvl") {
			path := filepath.Join("assets", dir)
			fmt.Printf("LOADING level %s/%s ... ", path, levelfile)
			level := ParseRawLevel(path, levelfile)
			fmt.Printf("done\n")
			levels = append(levels, level)
		}
	}

	return levels
}

func ParseRawLevel(dir string, levelfile fs.DirEntry) RawLevel {
	fd, err := os.Open(filepath.Join(dir, levelfile.Name()))
	if err != nil {
		log.Fatalf("failed to read level file %s: %s", levelfile.Name(), err)
	}
	defer fd.Close()

	name := strings.TrimSuffix(levelfile.Name(), ".lvl")
	des := ""
	background := &ebiten.Image{}
	data := []byte{}

	scanner := bufio.NewScanner(fd)
	for scanner.Scan() {
		// ignore any whitespace
		line := scanner.Text()

		// ignore empty lines
		if len(line) == 0 {
			continue
		}

		switch {
		case strings.Contains(line, "Background:"):
			haveit := strings.Split(line, ": ")
			if util.Exists(Assets, haveit[1]) {
				background = Assets[haveit[1]]
			}
		case strings.Contains(line, "Description:"):
			haveit := strings.Split(line, ": ")
			des = haveit[1]
		default:
			// all other  non-empty and  non-equalsign lines  are
			// level definition matrix data, merge thes into data
			data = append(data, line+"\n"...)
		}
	}

	return RawLevel{
		Name:        name,
		Data:        data,
		Background:  background,
		Description: des,
	}
}
