package main

import (
	"errors"
	"io"
	"strconv"
	"strings"

	"opslag.de/schobers/geom"
)

type sprite struct {
	texture string
	parts   []spritePart
}

func (s sprite) FindPartByName(name string) spritePart {
	for _, part := range s.parts {
		if part.name == name {
			return part
		}
	}
	return spritePart{}
}

type spritePart struct {
	name   string
	sub    geom.Rectangle
	anchor geom.Point
	scale  float32
}

func loadSpriteAsset(r io.Reader) (sprite, error) {
	var l sprite
	ctx := spriteContext{&l, nil}
	err := parseLines(r, ctx.parse)
	if err != nil {
		return sprite{}, err
	}
	return l, nil
}

type spriteContext struct {
	sprite *sprite
	part   *spritePart
}

func (c *spriteContext) parse(p *lineParser) parseLineFn {
	if p.skipSpaceEOF() {
		return nil
	}
	line := p.peek()
	switch {
	case strings.HasPrefix(line, "sprite:"):
		p.next()
		return c.parseContent
	default:
		return nil
	}
}

func (c *spriteContext) parseContent(p *lineParser) parseLineFn {
	const partTag = "part:"
	const textureTag = "texture:"
	const spriteEndTag = ":sprite"
	if p.skipSpaceEOF() {
		return p.emitErr(errUnexpectedEnd)
	}
	line := p.peek()
	switch {
	case strings.HasPrefix(line, textureTag):
		c.sprite.texture = strings.TrimSpace(line[len(textureTag):])
		p.next()
		return c.parseContent
	case line == partTag:
		p.next()
		c.part = &spritePart{}
		return c.parsePart
	case line == spriteEndTag:
		return nil
	}
	return p.emitErr(errors.New("unexpected content of sprite"))
}

func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
	mustAtois := func(s ...string) []int {
		res := make([]int, len(s))
		var err error
		for i, s := range s {
			res[i], err = strconv.Atoi(s)
			if err != nil {
				panic("string does not represent an integer number")
			}
		}
		return res
	}
	mustCoords := func(s string) []int {
		coords := strings.Split(strings.TrimSpace(s), ",")
		return mustAtois(coords...)
	}
	mustAtof := func(s string) float32 {
		f, err := strconv.ParseFloat(strings.TrimSpace(s), 32)
		if err != nil {
			panic("string does not represent an floating point number")
		}
		return float32(f)
	}
	const nameTag = "name:"
	const subTextureTag = "sub_texture:"
	const anchorTag = "anchor:"
	const scaleTag = "scale:"
	const partEndTag = ":part"
	if p.skipSpaceEOF() {
		return p.emitErr(errUnexpectedEnd)
	}
	line := p.peek()
	switch {
	case strings.HasPrefix(line, nameTag):
		c.part.name = strings.TrimSpace(line[len(nameTag):])
		p.next()
		return c.parsePart
	case strings.HasPrefix(line, subTextureTag):
		var coords = mustCoords(line[len(subTextureTag):])
		if len(coords) != 4 {
			panic("expected four coordinates (min x, min y, size x, size y)")
		}
		c.part.sub = geom.Rect(coords[0], coords[1], coords[0]+coords[2], coords[1]+coords[3])
		p.next()
		return c.parsePart
	case strings.HasPrefix(line, anchorTag):
		var coords = mustCoords(line[len(anchorTag):])
		if len(coords) != 2 {
			panic("expected two coordinates (min x, min y)")
		}
		c.part.anchor = geom.Pt(coords[0], coords[1])
		p.next()
		return c.parsePart
	case strings.HasPrefix(line, scaleTag):
		c.part.scale = mustAtof(line[len(scaleTag):])
		p.next()
		return c.parsePart
	case line == partEndTag:
		c.sprite.parts = append(c.sprite.parts, *c.part)
		p.next()
		return c.parseContent
	}
	return p.emitErr(errors.New("unexpected content of part"))
}
