package fonts

import (
	"fmt"
	"path/filepath"
	"strconv"
	"strings"

	"code.ur.gs/lupine/ordoor/internal/util"
	"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
)

type Range struct {
	Min Point
	Max Point
}

type Point struct {
	Rune   rune
	Sprite int
}

type Font struct {
	Name string
	// Contains the sprite data for the font
	ObjectFile string

	// Maps ASCII bytes to a sprite offset in the ObjectFile
	Ranges  []Range
	Mapping map[rune]int
}

func (f *Font) Entries() int {
	return len(f.Mapping)
}

// Returns the offsets required to display a given string, returning an error if
// some of the runes in the string are unknown to the font
func (f *Font) Indices(s string) ([]int, error) {
	out := make([]int, 0, len(s))

	for i, b := range []byte(s) {

		offset, ok := f.Mapping[rune(b)]
		if !ok {
			return nil, fmt.Errorf("Unknown codepoint %v at offset %v in string %s", b, i, s)
		}

		out = append(out, offset)
	}

	return out, nil
}

func LoadFont(filename string) (*Font, error) {
	scanner, err := asciiscan.New(filename)
	if err != nil {
		return nil, err
	}

	defer scanner.Close()

	// First, load the object file name
	objFile, err := scanner.ConsumeString()
	if err != nil {
		return nil, err
	}

	out := &Font{
		Name:       filepath.Base(filename),
		ObjectFile: objFile,
		Mapping:    make(map[rune]int),
	}

	for {
		str, err := scanner.ConsumeString()
		if err != nil {
			return nil, err
		}

		parseErr := fmt.Errorf("Invalid entry in %v: %q", filename, str)
		fields := strings.Fields(str)

		switch fields[0] {
		case "done":
			goto out
		case "r": // A range of codepoints
			if len(fields) < 5 {
				return nil, parseErr
			}

			cpStart, _ := strconv.Atoi(fields[1])
			cpEnd, _ := strconv.Atoi(fields[2])
			idxStart, _ := strconv.Atoi(fields[3])
			idxEnd, _ := strconv.Atoi(fields[4])
			cpSize := cpEnd - cpStart
			idxSize := idxEnd - idxStart

			// FIXME: I'd love this to be an error but several .fnt files do it
			// Take the smallest range
			if cpSize != idxSize {
				fmt.Printf("WARNING: %v has mismatched codepoints (sz=%v) and indices (sz=%v): %q\n", filename, cpSize, idxSize, str)
				if cpSize < idxSize {
					idxEnd = idxStart + cpSize
					idxSize = cpSize
				} else {
					cpEnd = cpStart + idxSize
					cpSize = idxSize
				}

			}

			r := Range{
				Min: Point{Rune: rune(cpStart), Sprite: idxStart},
				Max: Point{Rune: rune(cpEnd), Sprite: idxEnd},
			}

			out.Ranges = append(out.Ranges, r)

			for offset := 0; offset <= cpSize; offset++ {
				out.Mapping[rune(cpStart+offset)] = idxStart + offset
			}
		case "v": // A single codepoint, 4 fields
			if len(fields) < 3 {
				return nil, parseErr
			}

			cp, _ := strconv.Atoi(fields[1])
			idx, _ := strconv.Atoi(fields[2])
			pt := Point{Rune: rune(cp), Sprite: idx}

			out.Ranges = append(out.Ranges, Range{Min: pt, Max: pt})

			out.Mapping[rune(cp)] = idx
		default:
			return nil, parseErr
		}
	}

out:
	return out, nil
}

func LoadFonts(dir string) (map[string]*Font, error) {
	files, err := util.DirByExt(dir, ".fnt")
	if err != nil {
		return nil, err
	}

	out := make(map[string]*Font, len(files))

	for _, file := range files {
		abs := filepath.Join(dir, file)
		base := filepath.Base(file)
		font, err := LoadFont(abs)
		if err != nil {
			return nil, fmt.Errorf("%s: %v", abs, err)
		}

		out[base] = font
	}

	return out, nil
}
