package world

import (
	"fmt"
	"io"

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

// World holds a number of Chunks and Entities.
type World struct {
	Chunks map[itype.Vec2i]*Chunk
	Seed   int32
}

// NewWorld creates a new, empty, world with no chunks.
func NewWorld() *World {
	var w World
	w.Chunks = make(map[itype.Vec2i]*Chunk)

	return &w
}

// The default World.
var DefaultWorld World

func init() {
	DefaultWorld.Chunks = make(map[itype.Vec2i]*Chunk)
}

// Chunk returns the chunk at [x, z], or nil if nonexistent.
func (w *World) Chunk(x, z int) *Chunk {
	return w.Chunks[itype.Vec2i{x, z}]
}

// SetChunk sets the chunk [x, z] to c.
func (w *World) SetChunk(x, z int, c *Chunk) {
	if old, ok := w.Chunks[itype.Vec2i{x, z}]; ok {
		old.FreeRender()
	}

	c.X = x
	c.Z = z
	c.renderChanged = true
	c.world = w
	w.Chunks[itype.Vec2i{x, z}] = c
	c.InitRender()
}

func BlockPosToChunk(blockpos itype.Vec3i) (x, z int) {
	if blockpos[0] >= 0 {
		x = blockpos[0] / ChunkSizeX
	} else {
		x = (blockpos[0]+1)/ChunkSizeX - 1
	}

	if blockpos[2] >= 0 {
		z = blockpos[2] / ChunkSizeZ
	} else {
		z = (blockpos[2]+1)/ChunkSizeZ - 1
	}

	return
}

func BlockPosToInChunk(blockpos itype.Vec3i) (x, y, z int) {
	if blockpos[0] >= 0 {
		x = blockpos[0] % ChunkSizeX
	} else {
		x = ChunkSizeX + (blockpos[0] % ChunkSizeX)
		if x == ChunkSizeX {
			x = 0
		}
	}
	y = blockpos[1]
	if blockpos[2] >= 0 {
		z = blockpos[2] % ChunkSizeZ
	} else {
		z = ChunkSizeZ + (blockpos[2] % ChunkSizeZ)
		if z == ChunkSizeZ {
			z = 0
		}
	}
	return
}

// Break breaks the block.
func (w *World) Break(pos itype.Vec3i) {
	cx, cz := BlockPosToChunk(pos)
	cix, ciy, ciz := BlockPosToInChunk(pos)
	c, ok := w.Chunks[itype.Vec2i{cx, cz}]
	if !ok || ciy < 0 || ciy >= ChunkSizeY {
		return
	}

	c.SetBlock(cix, ciy, ciz, 0, 0)
}

// Block returns the block at the position [pos], or an empty Block{} if it does not exist.
func (w *World) Block(pos itype.Vec3i) Block {
	cx, cz := BlockPosToChunk(pos)
	cix, ciy, ciz := BlockPosToInChunk(pos)
	c, ok := w.Chunks[itype.Vec2i{cx, cz}]
	if !ok || ciy < 0 || ciy >= ChunkSizeY {
		return Block{}
	}
	return Block{
		Id:        int(c.Id[cix][ciy][ciz]),
		Aux:       int(c.Aux[cix][ciy][ciz]),
		Dataset:   nil,
		Behaviour: GetBlockBehaviour(int(c.Id[cix][ciy][ciz])),
	}
}

// SetBlock sets the block at the given position, returning its Block.
//
// It fails if the chunk is not loaded, and an empty Block{} is returned.
func (w *World) SetBlock(pos itype.Vec3i, id, aux int, dataset itype.Dataset) Block {
	cx, cz := BlockPosToChunk(pos)
	cix, ciy, ciz := BlockPosToInChunk(pos)
	c, ok := w.Chunks[itype.Vec2i{cx, cz}]
	if !ok || ciy < 0 || ciy >= ChunkSizeY {
		return Block{}
	}

	c.SetBlock(cix, ciy, ciz, id, aux)
	return Block{
		Id:        int(c.Id[cix][ciy][ciz]),
		Aux:       int(c.Aux[cix][ciy][ciz]),
		Dataset:   nil,
		Behaviour: GetBlockBehaviour(int(c.Id[cix][ciy][ciz])),
	}
}

// LoadChunkFromGob loads (or overwrites) chunk [id.x, id.z] from the Gob-encoding file.
func (w *World) LoadChunkFromGob(id itype.Vec2i, file io.Reader) (err error) {
	c := &Chunk{}
	if err != nil {
		return fmt.Errorf("World: load chunk (%d,%d) from gob file: %s", id[0], id[1], err)
	}

	if old, ok := w.Chunks[id]; ok {
		old.FreeRender()
	}

	c.LoadFromGobIndexed(file, id[0], id[1])
	w.Chunks[id] = c
	c.InitRender()

	return nil
}

// Render should be called from WorldRenderer, setting shader and texture context
// for the Render function, which merely calls BindVertexArray and DrawArrays.
func (w *World) Render() {
	for _, c := range w.Chunks {
		c.Render()
	}
}

// RenderWater calls DrawArrays drawing the semi-transparant layer of the world.
func (w *World) RenderWater() {
	for _, c := range w.Chunks {
		c.RenderWater()
	}

}