package sparsegrid

import (
	"fmt"
	"strings"

	"adventofcode2022/utils"
)

type SparseGrid[T comparable] struct {
	minX, maxX, minY, maxY int
	data                   map[string]T
	empty                  T
}

func NewGrid[T comparable](empty T) *SparseGrid[T] {
	return &SparseGrid[T]{
		minX:  utils.MaxInt,
		maxX:  utils.MinInt,
		minY:  utils.MaxInt,
		maxY:  utils.MinInt,
		data:  map[string]T{},
		empty: empty,
	}
}

func (g *SparseGrid[T]) SizeX() (int, int) {
	return g.minX, g.maxX
}

func (g *SparseGrid[T]) SizeY() (int, int) {
	return g.minY, g.maxY
}

func (g *SparseGrid[T]) Visited() int {
	return len(g.data)
}

func (g *SparseGrid[T]) Get(x, y int) T {
	k := key(x, y)
	v, ok := g.data[k]
	if !ok {
		return g.empty
	}
	return v
}

func (g *SparseGrid[T]) Set(x, y int, v T) {
	k := key(x, y)
	current, ok := g.data[k]
	if ok && v == current {
		return
	} else if !ok && v == g.empty {
		return
	} else if v == g.empty {
		delete(g.data, k)
	} else {
		g.data[k] = v
		g.minX = utils.Min(g.minX, x)
		g.maxX = utils.Max(g.maxX, x)
		g.minY = utils.Min(g.minY, y)
		g.maxY = utils.Max(g.maxY, y)
	}
}

func (g *SparseGrid[T]) StringWithFormatter(formatter func(T, int, int) string) string {
	var r strings.Builder
	for j := g.minY; j <= g.maxY; j++ {
		for i := g.minX; i <= g.maxX; i++ {
			_, err := r.WriteString(formatter(g.Get(i, j), i, j))
			utils.PanicOnErr(err)
		}
		_, err := r.WriteRune('\n')
		utils.PanicOnErr(err)
	}
	return r.String()
}

func key(x, y int) string {
	return fmt.Sprintf("%d:%d", x, y)
}
