package generator

import (
	"math"

	"github.com/df-mc/dragonfly/server/block"
	"github.com/df-mc/dragonfly/server/block/cube"
	"github.com/df-mc/dragonfly/server/world"
	"github.com/df-mc/dragonfly/server/world/biome"
	"github.com/df-mc/dragonfly/server/world/chunk"
)

// VanillaLightweight is a lightweight vanilla-like terrain generator optimised
// for low-end ARM boards (e.g. Armbian H680P). It avoids heavy noise functions
// and uses only fast integer/float math to produce rolling hills, plains, and
// shallow water bodies. Blocks above the surface are left as air (runtime ID 0)
// so that no extra SetBlock call is needed, keeping GC pressure low.
type VanillaLightweight struct {
	biome   uint32
	grass   uint32
	dirt    uint32
	stone   uint32
	sand    uint32
	water   uint32
	bedrock uint32
}

// NewVanillaLightweight creates a new VanillaLightweight generator.
func NewVanillaLightweight(br world.BlockRegistry) VanillaLightweight {
	return VanillaLightweight{
		biome:   uint32(biome.Plains{}.EncodeBiome()),
		grass:   br.BlockRuntimeID(block.Grass{}),
		dirt:    br.BlockRuntimeID(block.Dirt{}),
		stone:   br.BlockRuntimeID(block.Stone{}),
		sand:    br.BlockRuntimeID(block.Sand{}),
		water:   br.BlockRuntimeID(block.Water{Still: true, Depth: 8}),
		bedrock: br.BlockRuntimeID(block.Bedrock{}),
	}
}

const seaLevel int16 = 62

// heightAt returns the terrain height at a given world coordinate using cheap
// trigonometric functions. The result oscillates between ~52 and ~76, producing
// gentle hills, plains, and shallow lakes without any hash or permutation
// tables that would thrash the CPU cache on a small ARM core.
func heightAt(wx, wz float64) int16 {
	h := 64.0 +
		8.0*math.Sin(wx*0.0157)*math.Cos(wz*0.0157) +
		4.0*math.Sin(wx*0.0341+wz*0.0271) +
		2.0*math.Cos(wx*0.083)*math.Sin(wz*0.083)
	return int16(h)
}

// GenerateChunk generates a single chunk.
func (v VanillaLightweight) GenerateChunk(pos world.ChunkPos, c *chunk.Chunk) {
	min := int16(c.Range().Min())
	cx, cz := int32(pos.X())<<4, int32(pos.Z())<<4

	for x := uint8(0); x < 16; x++ {
		for z := uint8(0); z < 16; z++ {
			wx := float64(cx + int32(x))
			wz := float64(cz + int32(z))

			height := heightAt(wx, wz)

			// Fast path: only iterate up to max(height, seaLevel) instead of
			// going all the way to the chunk ceiling.
			top := height
			if seaLevel > top {
				top = seaLevel
			}

			for y := min; y <= top; y++ {
				c.SetBiome(x, y, z, v.biome)

				switch {
				case y == min:
					c.SetBlock(x, y, z, 0, v.bedrock)
				case y < height-3:
					c.SetBlock(x, y, z, 0, v.stone)
				case y < height:
					c.SetBlock(x, y, z, 0, v.dirt)
				case y == height:
					if height < seaLevel-1 {
						// Underwater floor → sand.
						c.SetBlock(x, y, z, 0, v.sand)
					} else {
						c.SetBlock(x, y, z, 0, v.grass)
					}
				default:
					// y > height && y <= seaLevel → water.
					if y <= seaLevel {
						c.SetBlock(x, y, z, 0, v.water)
					}
				}
			}

			// Set biome for a few sub-chunks above the terrain so the client
			// shows the correct biome colour for sky/fog without iterating the
			// entire column up to max.
			for y := top + 1; y <= top+16 && y <= int16(c.Range().Max()); y++ {
				c.SetBiome(x, y, z, v.biome)
			}
		}
	}
}

// DefaultSpawn returns a fixed, safe spawn position on solid ground at 0, 0.
func (v VanillaLightweight) DefaultSpawn(dim world.Dimension) cube.Pos {
	h := heightAt(0, 0)
	return cube.Pos{0, int(h) + 1, 0}
}
