package fonts

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

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

type Font struct {
	Name string
	// Contains the sprite data for the font. FIXME: load this?
	ObjectFile string

	// Maps ASCII bytes to a sprite offset in the ObjectFile
	mapping map[int]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[int(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[int]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])
			size := idxEnd - idxStart

			// FIXME: I'd love this to be an error but several .fnt files do it
			if cpEnd-cpStart != size {
				fmt.Printf("WARNING: %v has mismatched codepoints and indices: %q\n", filename, str)
			}

			for offset := 0; offset < size; offset++ {
				out.mapping[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])

			out.mapping[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
}
