package main

import (
	"errors"
	"fmt"
	"io"

	"opslag.de/schobers/geom"
)

type entityType byte
type tile byte

const (
	entityTypeInvalid   entityType = entityType(0)
	entityTypeNone                 = '_'
	entityTypeCharacter            = '@'
	entityTypeEgg                  = 'X'
	entityTypeBrick                = 'B'
)

func (e entityType) IsValid() bool {
	switch e {
	case entityTypeNone:
	case entityTypeCharacter:
	case entityTypeEgg:
	case entityTypeBrick:
	default:
		return false
	}
	return true
}

const (
	tileInvalid tile = tile(0)
	tileNothing      = '.'
	tileBasic        = '#'
	tileMagma        = '~'
)

func (t tile) IsValid() bool {
	switch t {
	case tileNothing:
	case tileBasic:
	case tileMagma:
	default:
		return false
	}
	return true
}

type level struct {
	width    int
	height   int
	tiles    []tile
	entities []entityType
}

func (l level) idxToPos(i int) geom.Point { return geom.Pt(i%l.width, i/l.width) }

func (l level) posToIdx(p geom.Point) int {
	if p.X < 0 || p.Y < 0 || p.X >= l.width || p.Y >= l.height {
		return -1
	}
	return p.Y*l.width + p.X
}

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

type levelContext struct {
	level *level
}

func (c *levelContext) parse(p *lineParser) parseLineFn {
	if p.eof() {
		return nil
	}
	switch p.peek() {
	case "level:":
		return c.parseContent
	case "":
		p.next() // skip
		return c.parse
	default:
		return nil
	}
}

func (c *levelContext) parseContent(p *lineParser) parseLineFn {
	if p.next() != "level:" {
		return p.emitErr(errors.New("expected level start"))
	}
	return c.parseRow
}

func (c *levelContext) parseRow(p *lineParser) parseLineFn {
	if p.eof() {
		return p.emitErr(errors.New("unexpected end of file"))
	}
	line := p.next()
	if line == ":level" {
		return c.parse
	}
	if c.level.height == 0 {
		c.level.width = len(line) / 2
	}
	return c.addRow(p, line)
}

func (c *levelContext) addRow(p *lineParser, line string) parseLineFn {
	var tiles []tile
	var entities []entityType
	for i := 0; i < len(line); i += 2 {
		tiles = append(tiles, tile(line[i]))
		entities = append(entities, entityType(line[i+1]))
	}

	for i, t := range tiles {
		if !t.IsValid() {
			return p.emitErr(fmt.Errorf("level contains invalid tile at (%d, %d)", i, c.level.height))
		}
	}
	for i, e := range entities {
		if !e.IsValid() {
			return p.emitErr(fmt.Errorf("level contains invalid entity type at (%d, %d)", i, c.level.height))
		}
	}

	c.level.height++
	c.level.tiles = append(c.level.tiles, tiles...)
	c.level.entities = append(c.level.entities, entities...)

	return c.parseRow
}
