package d2maprenderer

import (
	"log"

	"github.com/OpenDiablo2/OpenDiablo2/d2common"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)

func (mr *MapRenderer) generateTileCache() {
	mr.palette, _ = loadPaletteForAct(d2enum.RegionIdType(mr.mapEngine.LevelType().ID))
	mapEngineSize := mr.mapEngine.Size()

	for idx, tile := range *mr.mapEngine.Tiles() {
		tileX := idx % mapEngineSize.Width
		tileY := (idx - tileX) / mapEngineSize.Width

		for i := range tile.Floors {
			if !tile.Floors[i].Hidden && tile.Floors[i].Prop1 != 0 {
				mr.generateFloorCache(&tile.Floors[i], tileX, tileY)
			}
		}

		for i := range tile.Shadows {
			if !tile.Shadows[i].Hidden && tile.Shadows[i].Prop1 != 0 {
				mr.generateShadowCache(&tile.Shadows[i], tileX, tileY)
			}
		}

		for i := range tile.Walls {
			if !tile.Walls[i].Hidden && tile.Walls[i].Prop1 != 0 {
				mr.generateWallCache(&tile.Walls[i], tileX, tileY)
			}
		}
	}
}

func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
	tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), 0)

	var tileData []*d2dt1.Tile

	var tileIndex byte

	if tileOptions == nil {
		log.Printf("Could not locate tile Style:%d, Seq: %d, Type: %d\n", tile.Style, tile.Sequence, 0)

		tileData = append(tileData, &d2dt1.Tile{})
		tileData[0].Width = 10
		tileData[0].Height = 10
	} else {
		if !tileOptions[0].MaterialFlags.Lava {
			tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed())
			tileData = append(tileData, &tileOptions[tileIndex])
		} else {
			tile.Animated = true
			for i := range tileOptions {
				tileData = append(tileData, &tileOptions[i])
			}
		}
	}

	for i := range tileData {
		if !tileData[i].MaterialFlags.Lava {
			tile.RandomIndex = tileIndex
		} else {
			tileIndex = byte(tileData[i].RarityFrameIndex)
		}

		cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex)

		if cachedImage != nil {
			return
		}

		tileYMinimum := int32(0)

		for _, block := range tileData[i].Blocks {
			tileYMinimum = d2common.MinInt32(tileYMinimum, int32(block.Y))
		}

		tileYOffset := d2common.AbsInt32(tileYMinimum)
		tileHeight := d2common.AbsInt32(tileData[i].Height)
		image, _ := mr.renderer.NewSurface(int(tileData[i].Width), int(tileHeight), d2enum.FilterNearest)
		indexData := make([]byte, tileData[i].Width*tileHeight)
		mr.decodeTileGfxData(tileData[i].Blocks, &indexData, tileYOffset, tileData[i].Width)
		pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette)

		_ = image.ReplacePixels(pixels)
		mr.setImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex, image)
	}
}

func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
	tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), 13)

	var tileIndex byte

	var tileData *d2dt1.Tile

	if tileOptions == nil {
		return
	} else {
		tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed())
		tileData = &tileOptions[tileIndex]
	}

	if tileData.Width == 0 || tileData.Height == 0 {
		return
	}

	tile.RandomIndex = tileIndex
	tileMinY := int32(0)
	tileMaxY := int32(0)

	for _, block := range tileData.Blocks {
		tileMinY = d2common.MinInt32(tileMinY, int32(block.Y))
		tileMaxY = d2common.MaxInt32(tileMaxY, int32(block.Y+32))
	}

	tileYOffset := -tileMinY
	tileHeight := int(tileMaxY - tileMinY)
	tile.YAdjust = int(tileMinY + 80)

	cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex)
	if cachedImage != nil {
		return
	}

	image, _ := mr.renderer.NewSurface(int(tileData.Width), tileHeight, d2enum.FilterNearest)
	indexData := make([]byte, tileData.Width*int32(tileHeight))
	mr.decodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, tileData.Width)
	pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette)
	_ = image.ReplacePixels(pixels)
	mr.setImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex, image)
}

func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY int) {
	tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), int32(tile.Type))

	var tileIndex byte

	var tileData *d2dt1.Tile

	if tileOptions == nil {
		return
	} else {
		tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed())
		tileData = &tileOptions[tileIndex]
	}

	tile.RandomIndex = tileIndex

	var newTileData *d2dt1.Tile = nil

	if tile.Type == 3 {
		newTileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), int32(4))
		newTileIndex := mr.getRandomTile(newTileOptions, tileX, tileY, mr.mapEngine.Seed())
		newTileData = &newTileOptions[newTileIndex]
	}

	tileMinY := int32(0)
	tileMaxY := int32(0)

	target := tileData

	if newTileData != nil && newTileData.Height < tileData.Height {
		target = newTileData
	}

	for _, block := range target.Blocks {
		tileMinY = d2common.MinInt32(tileMinY, int32(block.Y))
		tileMaxY = d2common.MaxInt32(tileMaxY, int32(block.Y+32))
	}

	realHeight := d2common.MaxInt32(d2common.AbsInt32(tileData.Height), tileMaxY-tileMinY)
	tileYOffset := -tileMinY
	/*tileHeight := int(tileMaxY - tileMinY)*/

	if tile.Type == 15 {
		tile.YAdjust = -int(tileData.RoofHeight)
	} else {
		tile.YAdjust = int(tileMinY) + 80
	}

	cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tileIndex)
	if cachedImage != nil {
		return
	}

	if realHeight == 0 {
		log.Printf("Invalid 0 height for wall tile")
		return
	}

	image, _ := mr.renderer.NewSurface(160, int(realHeight), d2enum.FilterNearest)
	indexData := make([]byte, 160*realHeight)

	mr.decodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, 160)

	if newTileData != nil {
		mr.decodeTileGfxData(newTileData.Blocks, &indexData, tileYOffset, 160)
	}

	pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette)

	if err := image.ReplacePixels(pixels); err != nil {
		log.Panicf(err.Error())
	}

	mr.setImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tileIndex, image)
}

func (mr *MapRenderer) getRandomTile(tiles []d2dt1.Tile, x, y int, seed int64) byte {
	/* Walker's Alias Method for weighted random selection
	 * with xorshifting for random numbers */

	var tileSeed uint64
	tileSeed = uint64(seed) + uint64(x)
	tileSeed *= uint64(y) + uint64(mr.mapEngine.LevelType().ID)

	tileSeed ^= tileSeed << 13
	tileSeed ^= tileSeed >> 17
	tileSeed ^= tileSeed << 5

	weightSum := 0

	for _, tile := range tiles {
		weightSum += int(tile.RarityFrameIndex)
	}

	if weightSum == 0 {
		return 0
	}

	random := tileSeed % uint64(weightSum)

	sum := 0

	for i, tile := range tiles {
		sum += int(tile.RarityFrameIndex)
		if sum >= int(random) {
			return byte(i)
		}
	}

	// This return shouldn't be hit
	return 0
}
