package world

import (
	"github.com/Tnze/go-mc/bot/world/entity"
	"github.com/Tnze/go-mc/data/block"
	pk "github.com/Tnze/go-mc/net/packet"
)

// World record all of the things in the world where player at
type World struct {
	Entities map[int32]entity.Entity
	Chunks   map[ChunkLoc]*Chunk
}

// Chunk store a 256*16*16 column blocks
type Chunk struct {
	Sections [16]Section
}

// Section store a 16*16*16 cube blocks
type Section interface {
	// GetBlock return block status, offset can be calculate by SectionOffset.
	GetBlock(offset uint) BlockStatus
	// SetBlock is the reverse operation of GetBlock.
	SetBlock(offset uint, s BlockStatus)
}

func SectionIdx(x, y, z int) uint {
	// According to wiki.vg: Data Array is given for each block with increasing x coordinates,
	// within rows of increasing z coordinates, within layers of increasing y coordinates.
	// So offset equals to ( x*16^0 + z*16^1 + y*16^2 )*(bits per block).
	return uint(((y & 15) << 8) | (z << 4) | x)
}

type BlockStatus uint32

type ChunkLoc struct {
	X, Z int
}

// //Entity 表示一个实体
// type Entity interface {
// 	EntityID() int32
// }

// //Face is a face of a block
// type Face byte

// // All six faces in a block
// const (
// 	Bottom Face = iota
// 	Top
// 	North
// 	South
// 	West
// 	East
// )

// getBlock return the block in the position (x, y, z)
func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
	// Use n>>4 rather then n/16. It acts wrong if n<0.
	c := w.Chunks[ChunkLoc{x >> 4, z >> 4}]
	if c != nil {
		if sec := c.Sections[y>>4]; sec != nil {
			return sec.GetBlock(SectionIdx(x&15, y&15, z&15))
		}
	}
	return 0
}

func (w *World) UnaryBlockUpdate(pos pk.Position, bStateID BlockStatus) bool {
	c := w.Chunks[ChunkLoc{X: pos.X >> 4, Z: pos.Z >> 4}]
	if c == nil {
		return false
	}
	sIdx, bIdx := pos.Y>>4, SectionIdx(pos.X&15, pos.Y&15, pos.Z&15)

	if sec := c.Sections[sIdx]; sec == nil {
		sec = newSectionWithSize(uint(block.BitsPerBlock))
		sec.SetBlock(bIdx, bStateID)
		c.Sections[sIdx] = sec
	} else {
		sec.SetBlock(bIdx, bStateID)
	}
	return true
}

func (w *World) MultiBlockUpdate(loc ChunkLoc, sectionY int, blocks []pk.VarLong) bool {
	c := w.Chunks[loc]
	if c == nil {
		return false // not loaded
	}

	sec := c.Sections[sectionY]
	if sec == nil {
		sec = newSectionWithSize(uint(block.BitsPerBlock))
		c.Sections[sectionY] = sec
	}

	for _, b := range blocks {
		bStateID := b >> 12
		x, z, y := (b>>8)&0xf, (b>>4)&0xf, b&0xf
		sec.SetBlock(SectionIdx(int(x&15), int(y&15), int(z&15)), BlockStatus(bStateID))
	}

	return true
}

// func (b Block) String() string {
// 	return blockNameByID[b.id]
// }

//LoadChunk load chunk at (x, z)
func (w *World) LoadChunk(x, z int, c *Chunk) {
	w.Chunks[ChunkLoc{X: x, Z: z}] = c
}
