package util

import (
	"errors"
	"image"
	"image/color"
	"io"
	"log"
	"sort"

	"edgaru089.ink/go/gl01/internal/util/itype"
)

// The largest width a texture atlas can grow to.
const AtlasMaxWidth = 256

// Atlas stores a sequence of textures, binding them into one big texture.
type Atlas struct {
	Image     *image.RGBA
	ImageSize itype.Vec2i

	imgs []struct {
		name string
		img  image.Image
	}

	rects map[string]itype.Recti
}

// Add adds a new image.Image with name.
func (a *Atlas) Add(name string, img image.Image) {
	a.imgs = append(a.imgs, struct {
		name string
		img  image.Image
	}{name, img})
}

// AddFile adds a new image file with name.
//
// Returns error if image.Decode returns error.
func (a *Atlas) AddFile(name string, imgfile io.Reader) error {
	img, _, err := image.Decode(imgfile)
	if err != nil {
		return errors.New("TexAtlas: decoding file: " + err.Error())
	}

	a.imgs = append(a.imgs, struct {
		name string
		img  image.Image
	}{name, img})

	return nil
}

// Rect returns texture rect in pixel coordinates, with origin in the
// top-left corner.
func (a *Atlas) Rect(name string) itype.Recti {
	recti, ok := a.rects[name]
	if !ok {
		recti = a.rects["_dummy_nontransparent"]
	}
	return recti
}

// RectNormalized returns texture rect in normalized coordinates,
// i.e., are between [0,1) divided by texture size on each dimension.
func (a *Atlas) RectNormalized(name string) itype.Rectf {
	recti := a.Rect(name)
	return itype.Rectf{
		Left:   float32(recti.Left) / float32(a.ImageSize[0]),
		Top:    float32(recti.Top) / float32(a.ImageSize[1]),
		Width:  float32(recti.Width) / float32(a.ImageSize[0]),
		Height: float32(recti.Height) / float32(a.ImageSize[1]),
	}
}

// HasBuilt returns if the Atlas has been built.
func (a *Atlas) HasBuilt() bool {
	return a.rects != nil
}

// BuildTexture builds the texture atlas.
// /*** It can also swaps the texture upside-down to avoid OpenGL coordinate issues. ***/
func (a *Atlas) BuildTexture( /*upsideDown bool*/ ) {

	var offX, offY, sizeX, sizeY int
	var maxHeight int

	// HACK Add a dummy solid image to prevent skipping mask rendering
	// because of a transparent pixel
	dummy := image.NewRGBA(image.Rect(0, 0, 2, 2))
	dummy.Set(0, 0, color.Black)
	dummy.Set(0, 1, color.RGBA{255, 0, 255, 255})
	dummy.Set(1, 0, color.RGBA{255, 0, 255, 255})
	dummy.Set(1, 1, color.Black)
	a.imgs = append(a.imgs, struct {
		name string
		img  image.Image
	}{"_dummy_nontransparent", dummy})

	sort.Sort(SortFunc{
		Lenf:  func() int { return len(a.imgs) },
		Swapf: func(i, j int) { a.imgs[i], a.imgs[j] = a.imgs[j], a.imgs[i] },
		Lessf: func(i, j int) bool {
			if a.imgs[i].name == "_dummy_nontransparent" {
				return true
			} else if a.imgs[j].name == "_dummy_nontransparent" {
				return false
			} else if a.imgs[i].img.Bounds().Dy() < a.imgs[j].img.Bounds().Dy() {
				return true
			} else if a.imgs[i].img.Bounds().Dy() == a.imgs[j].img.Bounds().Dy() &&
				a.imgs[i].img.Bounds().Dx() < a.imgs[j].img.Bounds().Dx() {
				return true
			}
			return false
		},
	})

	a.rects = make(map[string]itype.Recti)

	for _, i := range a.imgs {

		if offX+i.img.Bounds().Dx() > AtlasMaxWidth {
			// New line
			offX = 0
			offY += maxHeight
			maxHeight = 0
		}

		a.rects[i.name] = itype.Recti{offX, offY, i.img.Bounds().Dx(), i.img.Bounds().Dy()}

		log.Printf("Atlas: texture %s at pos %v\n", i.name, a.rects[i.name])

		sizeX = Maxi(sizeX, offX+i.img.Bounds().Dx())
		sizeY = Maxi(sizeY, offY+i.img.Bounds().Dy())
		maxHeight = Maxi(maxHeight, i.img.Bounds().Dy())

		offX += i.img.Bounds().Dx()
	}

	a.Image = image.NewRGBA(image.Rect(0, 0, sizeX, sizeY))
	a.ImageSize = itype.Vec2i{sizeX, sizeY}

	for _, i := range a.imgs {
		rect := a.rects[i.name]
		for x := 0; x < rect.Width; x++ {
			for y := 0; y < rect.Height; y++ {
				a.Image.Set(
					x+rect.Left,
					y+rect.Top, /*i.img.Bounds().Dy()-1-(y+rect.Top),*/
					i.img.At(
						i.img.Bounds().Min.X+x,
						i.img.Bounds().Min.Y+y,
					),
				)
			}
		}
	}
}
