package main

import (
	"fmt"
	"image/png"
	"math"
	"os"
	"time"

	"golang.org/x/image/colornames"

	"github.com/faiface/pixel"
	"github.com/faiface/pixel/pixelgl"
	"github.com/salviati/go-tmx/tmx"
)

var clearColor = colornames.Skyblue

func gameloop(win *pixelgl.Window, tilemap *tmx.Map) {
	batches := make(map[string]*pixel.Batch)

	// Load the sprites
	sprites := make(map[string]*pixel.Sprite)
	for _, tileset := range tilemap.Tilesets {
		if _, alreadyLoaded := sprites[tileset.Image.Source]; !alreadyLoaded {
			sprite, pictureData := loadSprite(tileset.Image.Source)
			sprites[tileset.Image.Source] = sprite
			batches[tileset.Image.Source] = pixel.NewBatch(&pixel.TrianglesData{}, pictureData)
		}
	}

	var (
		camPos       = pixel.ZV
		camSpeed     = 1000.0
		camZoom      = 0.2
		camZoomSpeed = 1.2
	)

	last := time.Now()
	for !win.Closed() {
		dt := time.Since(last).Seconds()
		last = time.Now()

		// Camera movement
		cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center().Sub(camPos))
		win.SetMatrix(cam)
		if win.Pressed(pixelgl.KeyLeft) {
			camPos.X -= camSpeed * dt
		}
		if win.Pressed(pixelgl.KeyRight) {
			camPos.X += camSpeed * dt
		}
		if win.Pressed(pixelgl.KeyDown) {
			camPos.Y -= camSpeed * dt
		}
		if win.Pressed(pixelgl.KeyUp) {
			camPos.Y += camSpeed * dt
		}
		camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y)

		win.Clear(clearColor)

		// Draw tiles
		for _, batch := range batches {
			batch.Clear()
		}

		for _, layer := range tilemap.Layers {
			for tileIndex, tile := range layer.DecodedTiles {
				ts := layer.Tileset
				tID := int(tile.ID)

				if tID == 0 {
					// Tile ID 0 means blank, skip it.
					continue
				}

				// Calculate the framing for the tile within its tileset's source image
				numRows := ts.Tilecount / ts.Columns
				x, y := tileIDToCoord(tID, ts.Columns, numRows)
				gamePos := indexToGamePos(tileIndex, tilemap.Width, tilemap.Height)

				iX := float64(x) * float64(ts.TileWidth)
				fX := iX + float64(ts.TileWidth)
				iY := float64(y) * float64(ts.TileHeight)
				fY := iY + float64(ts.TileHeight)

				sprite := sprites[ts.Image.Source]
				sprite.Set(sprite.Picture(), pixel.R(iX, iY, fX, fY))
				pos := gamePos.ScaledXY(pixel.V(float64(ts.TileWidth), float64(ts.TileHeight)))
				sprite.Draw(batches[ts.Image.Source], pixel.IM.Moved(pos))
			}
		}

		for _, batch := range batches {
			batch.Draw(win)
		}
		win.Update()
	}
}

func tileIDToCoord(tID int, numColumns int, numRows int) (x int, y int) {
	x = tID % numColumns
	y = numRows - (tID / numColumns) - 1
	return
}

func indexToGamePos(idx int, width int, height int) pixel.Vec {
	gamePos := pixel.V(
		float64(idx%width)-1,
		float64(height)-float64(idx/width),
	)
	return gamePos
}

func run() {
	// Create the window with OpenGL
	cfg := pixelgl.WindowConfig{
		Title:  "Pixel Tilemaps",
		Bounds: pixel.R(0, 0, 800, 600),
		VSync:  true,
	}

	win, err := pixelgl.NewWindow(cfg)
	panicIfErr(err)

	// Initialize art assets (i.e. the tilemap)
	tilemap, err := tmx.ReadFile("gameart2d-desert.tmx")
	panicIfErr(err)

	fmt.Println("use WASD to move camera around")
	gameloop(win, tilemap)
}

func loadSprite(path string) (*pixel.Sprite, *pixel.PictureData) {
	f, err := os.Open(path)
	panicIfErr(err)

	img, err := png.Decode(f)
	panicIfErr(err)

	pd := pixel.PictureDataFromImage(img)
	return pixel.NewSprite(pd, pd.Bounds()), pd
}

func main() {
	pixelgl.Run(run)
}

func panicIfErr(err error) {
	if err != nil {
		panic(err)
	}
}
