package d2mapgen

import (
	"log"
	"math/rand"
	"strings"

	"github.com/OpenDiablo2/OpenDiablo2/d2common"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen/d2wilderness"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp"
)

var wildernessGrass = d2ds1.FloorShadowRecord{Prop1: 1, Style: 0, Sequence: 0}

func loadPreset(mapEngine *d2mapengine.MapEngine, id, index int) *d2mapstamp.Stamp {
	for _, file := range d2datadict.LevelPreset(id).Files {
		mapEngine.AddDS1(file)
	}

	return d2mapstamp.LoadStamp(d2enum.RegionAct1Wilderness, id, index)
}

// GenerateAct1Overworld generates the map and entities for the first town and surrounding area.
func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
	rand.Seed(mapEngine.Seed())

	wilderness1Details := d2datadict.GetLevelDetails(2)

	mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150)
	mapWidth := mapEngine.Size().Width
	mapHeight := mapEngine.Size().Height

	townStamp := d2mapstamp.LoadStamp(d2enum.RegionAct1Town, 1, -1)
	townStamp.RegionPath()
	townSize := townStamp.Size()

	log.Printf("Region Path: %s", townStamp.RegionPath())
	if strings.Contains(townStamp.RegionPath(), "E1") {
		// East Exit
		mapEngine.PlaceStamp(townStamp, 0, 0)
		generateWilderness1TownEast(mapEngine, townSize.Width, 0)
	} else if strings.Contains(townStamp.RegionPath(), "S1") {
		// South Exit
		mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, 0)

		// Generate the river running along the edge of the map
		rightWaterBorderStamp := loadPreset(mapEngine, d2wilderness.WaterBorderEast, 0)
		rightWaterBorderStamp2 := loadPreset(mapEngine, d2wilderness.WaterBorderWest, 0)
		// Place the water on the right side of the map
		for y := townSize.Height; y < mapHeight-9; y += 9 {
			mapEngine.PlaceStamp(rightWaterBorderStamp, mapWidth-17, y)
			mapEngine.PlaceStamp(rightWaterBorderStamp2, mapWidth-9, y)
		}
		generateWilderness1TownSouth(mapEngine, mapWidth-wilderness1Details.SizeXNormal-14, townSize.Height)
	} else if strings.Contains(townStamp.RegionPath(), "W1") {
		// West Exit
		mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)

		generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width-wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal)
	} else {
		// North Exit
		mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
	}

	mapEngine.RegenerateWalkPaths()
}

func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, startY int) {
	levelDetails := d2datadict.GetLevelDetails(2)

	fenceNorthStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2),
	}

	fenceSouthStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2),
	}

	fenceWestStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2),
	}

	fenceEastStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderEast, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderEast, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderEast, 2),
	}

	fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0)
	fenceNorthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthEast, 0)
	fenceSouthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthEast, 0)
	fenceWestEdge := loadPreset(mapEngine, d2wilderness.TreeBoxNorthEast, 0)

	areaRect := d2common.Rectangle{
		Left:   startX,
		Top:    startY + 9,
		Width:  levelDetails.SizeXNormal,
		Height: levelDetails.SizeYNormal - 3,
	}
	generateWilderness1Contents(mapEngine, areaRect)

	// Draw the north and south fence
	for i := 0; i < 9; i++ {
		mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9), startY)
		mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(levelDetails.SizeYNormal+6))
	}

	// West fence
	for i := 1; i < 6; i++ {
		mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(levelDetails.SizeYNormal+6)-(i*9))
	}

	// East Fence
	for i := 1; i < 10; i++ {
		mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal, startY+(i*9))
	}

	mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal+6)
	mapEngine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45)
	mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY)
	mapEngine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6)
}

func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, startY int) {
	levelDetails := d2datadict.GetLevelDetails(2)

	fenceNorthStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2),
	}

	fenceWestStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2),
	}

	fenceSouthStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2),
	}

	fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0)
	fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0)
	fenceWaterBorderSouthEast := loadPreset(mapEngine, d2wilderness.WaterBorderEast, 1)

	areaRect := d2common.Rectangle{
		Left:   startX + 2,
		Top:    startY,
		Width:  levelDetails.SizeXNormal - 2,
		Height: levelDetails.SizeYNormal - 3,
	}
	generateWilderness1Contents(mapEngine, areaRect)

	// Draw the north fence
	for i := 0; i < 4; i++ {
		mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)+5, startY-6)
	}

	// Draw the west fence
	for i := 0; i < 8; i++ {
		mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)+3)
	}

	// Draw the south fence
	for i := 1; i < 9; i++ {
		mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(8*9)+3)
	}

	mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-6)
	mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+(8*9)+3)
	mapEngine.PlaceStamp(fenceWaterBorderSouthEast, startX+(9*9)-4, startY+(8*9)+1)
}

func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, startY int) {
	levelDetails := d2datadict.GetLevelDetails(2)

	fenceEastEdge := loadPreset(mapEngine, d2wilderness.TreeBoxSouthWest, 0)
	fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0)
	fenceNorthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthEast, 0)
	fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0)

	fenceSouthStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2),
	}

	fenceNorthStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2),
	}

	fenceEastStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderEast, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderEast, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderEast, 2),
	}

	fenceWestStamp := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0),
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1),
		loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2),
	}

	// Draw the north and south fences
	for i := 0; i < 9; i++ {
		if i > 0 && i < 8 {
			mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)-1, startY-15)
		}

		mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9)-1, startY+levelDetails.SizeYNormal-12)
	}

	// Draw the east fence
	for i := 0; i < 6; i++ {
		mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal-9, startY+(i*9)-6)
	}

	// Draw the west fence
	for i := 0; i < 9; i++ {
		mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)-6)
	}

	// Draw the west fence
	mapEngine.PlaceStamp(fenceEastEdge, startX+levelDetails.SizeXNormal-9, startY+39)
	mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-15)
	mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12)
	mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15)

	areaRect := d2common.Rectangle{
		Left:   startX + 9,
		Top:    startY - 10,
		Width:  levelDetails.SizeXNormal - 9,
		Height: levelDetails.SizeYNormal - 2,
	}
	generateWilderness1Contents(mapEngine, areaRect)
}

func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) {
	levelDetails := d2datadict.GetLevelDetails(2)

	denOfEvil := loadPreset(mapEngine, d2wilderness.DenOfEvilEntrance, 0)
	denOfEvilLoc := d2common.Point{
		X: rect.Left + (rect.Width / 2) + rand.Intn(10),
		Y: rect.Top + (rect.Height / 2) + rand.Intn(10),
	}

	// Fill in the grass
	for y := 0; y < rect.Height; y++ {
		for x := 0; x < rect.Width; x++ {
			tile := mapEngine.Tile(rect.Left+x, rect.Top+y)
			tile.RegionType = d2enum.RegionIdType(levelDetails.LevelType)
			tile.Floors = []d2ds1.FloorShadowRecord{wildernessGrass}
		}
	}

	stuff := []*d2mapstamp.Stamp{
		loadPreset(mapEngine, d2wilderness.StoneFill1, 0),
		loadPreset(mapEngine, d2wilderness.StoneFill1, 1),
		loadPreset(mapEngine, d2wilderness.StoneFill1, 2),
		loadPreset(mapEngine, d2wilderness.StoneFill2, 0),
		loadPreset(mapEngine, d2wilderness.StoneFill2, 1),
		loadPreset(mapEngine, d2wilderness.StoneFill2, 2),
		loadPreset(mapEngine, d2wilderness.Cottages1, 0),
		loadPreset(mapEngine, d2wilderness.Cottages1, 1),
		loadPreset(mapEngine, d2wilderness.Cottages1, 2),
		loadPreset(mapEngine, d2wilderness.Cottages1, 3),
		loadPreset(mapEngine, d2wilderness.Cottages1, 4),
		loadPreset(mapEngine, d2wilderness.Cottages1, 5),
		loadPreset(mapEngine, d2wilderness.FallenCamp1, 0),
		loadPreset(mapEngine, d2wilderness.FallenCamp1, 1),
		loadPreset(mapEngine, d2wilderness.FallenCamp1, 2),
		loadPreset(mapEngine, d2wilderness.FallenCamp1, 3),
		loadPreset(mapEngine, d2wilderness.Pond, 0),
		loadPreset(mapEngine, d2wilderness.SwampFill1, 0),
		loadPreset(mapEngine, d2wilderness.SwampFill2, 0),
	}

	mapEngine.PlaceStamp(denOfEvil, denOfEvilLoc.X, denOfEvilLoc.Y)

	numPlaced := 0
	for numPlaced < 25 {
		stamp := stuff[rand.Intn(len(stuff))]

		stampRect := d2common.Rectangle{
			Left:   rect.Left + rand.Intn(rect.Width) - stamp.Size().Width,
			Top:    rect.Top + rand.Intn(rect.Height) - stamp.Size().Height,
			Width:  stamp.Size().Width,
			Height: stamp.Size().Height,
		}

		if areaEmpty(mapEngine, stampRect) {
			mapEngine.PlaceStamp(stamp, stampRect.Left, stampRect.Top)
			numPlaced++
		}
	}
}

func areaEmpty(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) bool {
	mapHeight := mapEngine.Size().Height
	mapWidth := mapEngine.Size().Width

	if rect.Top < 0 || rect.Left < 0 || rect.Bottom() >= mapHeight || rect.Right() >= mapWidth {
		return false
	}

	for y := rect.Top; y <= rect.Bottom(); y++ {
		for x := rect.Left; x <= rect.Right(); x++ {
			if len(mapEngine.Tile(x, y).Floors) == 0 {
				continue
			}

			floor := mapEngine.Tile(x, y).Floors[0]

			if floor.Style != 0 || floor.Sequence != 0 || floor.Prop1 != 1 {
				return false
			}
		}
	}

	return true
}
