package world

import (
	"testing"

	"regionio/internal/worldgen"
)

// Ore-vein block states, from OreVeinifier.VeinType. Copper's ore is the plain
// stone variant and iron's is the deepslate one; neither switches with depth.
const (
	StateGranite          uint16 = 2
	StateDeepslateIronOre uint16 = 132
	StateTuff             uint16 = 23452
	StateCopperOre        uint16 = 25313
	StateRawIronBlock     uint16 = 29577
	StateRawCopperBlock   uint16 = 29578
)

// TestOreVeins checks the mega-veins the noise router has always described and
// nothing has ever read: copper in granite between y=0 and y=50, iron in tuff
// between y=-60 and y=-8, with a rare raw-ore block inside each.
//
// The Y windows are the sharpest assertion available. A vein's type comes from
// the sign of the veininess noise but its window comes from the type, so a
// single block outside a window means the guard is wrong.
func TestOreVeins(t *testing.T) {
	const seed = 12345
	od, err := worldgen.LoadOverworldFinalDensity(seed)
	if err != nil {
		t.Fatal(err)
	}
	picker := worldgen.OverworldFluidPicker(od.SeaLevel)
	veins := worldgen.NewOreVeinifier(od)
	carver, err := worldgen.NewCarver(od, seed)
	if err != nil {
		t.Fatal(err)
	}
	initCarverReplaceable(carver.ReplaceableBlocks())
	counts := map[uint16]int{}
	lowest := map[uint16]int{}
	highest := map[uint16]int{}
	interesting := []uint16{StateCopperOre, StateRawCopperBlock, StateGranite,
		StateDeepslateIronOre, StateRawIronBlock, StateTuff}
	isVein := map[uint16]bool{}
	for _, id := range interesting {
		isVein[id] = true
	}
	for cx := int32(-60); cx <= 60; cx += 20 {
		for cz := int32(-60); cz <= 60; cz += 20 {
			// Call the terrain/material pass directly and skip decorate. Ordinary
			// placed ores share several states with mega-veins and cannot be
			// distinguished by block ID after decoration.
			ch := generateVanillaWithoutDecoration(od, picker, veins, carver, seed, cx, cz)
			for wy := MinY; wy < 60; wy++ {
				for lx := 0; lx < 16; lx++ {
					for lz := 0; lz < 16; lz++ {
						b := ch.GetBlock(lx, wy, lz)
						if !isVein[b] {
							continue
						}
						if counts[b] == 0 {
							lowest[b], highest[b] = wy, wy
						}
						counts[b]++
						lowest[b] = min(lowest[b], wy)
						highest[b] = max(highest[b], wy)
					}
				}
			}
		}
	}

	for _, c := range []struct {
		state       uint16
		name        string
		minY, maxY  int
		wantAtLeast int
	}{
		{StateCopperOre, "copper ore", 0, 50, 50},
		{StateGranite, "copper vein filler", 0, 50, 100},
		{StateDeepslateIronOre, "deepslate iron ore", -60, -8, 50},
		{StateTuff, "iron vein filler", -60, -8, 100},
	} {
		if counts[c.state] < c.wantAtLeast {
			t.Errorf("%s: %d blocks, want at least %d", c.name, counts[c.state], c.wantAtLeast)
			continue
		}
		if lowest[c.state] < c.minY || highest[c.state] > c.maxY {
			t.Errorf("%s spans y %d..%d, outside its window %d..%d",
				c.name, lowest[c.state], highest[c.state], c.minY, c.maxY)
		}
	}
	t.Logf("copper %d (raw %d) in granite %d; iron %d (raw %d) in tuff %d",
		counts[StateCopperOre], counts[StateRawCopperBlock], counts[StateGranite],
		counts[StateDeepslateIronOre], counts[StateRawIronBlock], counts[StateTuff])

	// A raw-ore block replaces an ore block with probability 0.02, so a few
	// hundred ore blocks should turn up a handful. Zero means the third draw is
	// never reached.
	if counts[StateRawCopperBlock]+counts[StateRawIronBlock] == 0 {
		t.Error("no raw ore blocks anywhere; the raw-ore roll is unreachable")
	}
}
