package mapgens

import (
	"fmt"
	"lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
	"lab.zaar.be/thefish/alchemyst-go/engine/types"
	"lab.zaar.be/thefish/alchemyst-go/util"
	"lab.zaar.be/thefish/alchemyst-go/util/appctx"
)

//fixme move to config
var minRoomSize = 3
var maxRoomSize = 15
var maxrooms = 50

var fges = map[int]types.RectFill{
	1: types.RectFill{
		Top:         func() *gamemap.Tile { return gamemap.NewWall() },
		Bottom:      func() *gamemap.Tile { return gamemap.NewWall() },
		Left:        func() *gamemap.Tile { return gamemap.NewWall() },
		Right:       func() *gamemap.Tile { return gamemap.NewWall() },
		BottomLeft:  func() *gamemap.Tile { return gamemap.NewWall() },
		BottomRight: func() *gamemap.Tile { return gamemap.NewWall() },
		TopLeft:     func() *gamemap.Tile { return gamemap.NewWall() },
		TopRight:    func() *gamemap.Tile { return gamemap.NewWall() },
		Body:        func() *gamemap.Tile { return gamemap.NewFloor() },
	},

	2: types.RectFill{
		Top:         func() *gamemap.Tile { return gamemap.NewWaterTile() },
		Bottom:      func() *gamemap.Tile { return gamemap.NewWaterTile() },
		Left:        func() *gamemap.Tile { return gamemap.NewWaterTile() },
		Right:       func() *gamemap.Tile { return gamemap.NewWaterTile() },
		BottomLeft:  func() *gamemap.Tile { return gamemap.NewWaterTile() },
		BottomRight: func() *gamemap.Tile { return gamemap.NewWaterTile() },
		TopLeft:     func() *gamemap.Tile { return gamemap.NewWaterTile() },
		TopRight:    func() *gamemap.Tile { return gamemap.NewWaterTile() },
		Body:        func() *gamemap.Tile { return gamemap.NewDeepWaterTile() },
	},
}

func DefaultGen(ctx appctx.ClientCtx,l *gamemap.Level) (*gamemap.Level, []gamemap.Room) {

	rng := util.NewRNG()

	//load prefabs
	pfLoader := gamemap.NewPrefabLoader(ctx)
	pfRooms := pfLoader.PrefabRoomsList()


	//fill with walls
	for i := 0; i < l.W; i ++ {
		for j := 0; j < l.H; j++ {
			l.SetTileByXY(i, j, gamemap.NewWall())
		}
	}

	rooms := make([]gamemap.Room, 0)
	prefabUsed := false
	for i := 0; i < maxrooms; i++ {
		failed := false
		var fillage types.RectFill
		fillage = fges[rng.GetWeightedEntity(map[int]int{1: 10, 2: 1})]
		var newRoom gamemap.Room
		if !prefabUsed  || rng.Range(0,5) > 3 {
			//prefab
			prefabUsed = true
			r := pfRooms[rng.Range(0, len(pfRooms))] //copy to local scope
			newRoom = r
		} else {
			newRoom = gamemap.NewRandomRectRoom(
				rng,
				rng.Range(minRoomSize, maxRoomSize),
				rng.Range(minRoomSize, maxRoomSize),
				fillage,
			)
		}
		where := types.Coords{
			rng.Range(1, l.W -2 - newRoom.W),
			rng.Range(1, l.H - 2 - newRoom.H),
		}

		newRoom.MoveToCoords(where)

		for _, otherRoom := range rooms {
			if otherRoom.Intersects(newRoom.Rect) {
				failed = true
				break
			}
		}

		if !failed {
			rooms = append(rooms, newRoom)
		}

		//addStairs(rooms)
		//itemize(rooms)
	}

	//build delannay graph from room center

	//refine it to minimum spanning tree

	//connect accordingly
	for _, room := range rooms {
		err := room.BlitToLevel(l)
		if err != nil {
			fmt.Printf("err: %v", err)
		}
	}

	for idx, room := range rooms {
		if idx > 0 {
			connectRooms(l, room, rooms[idx-1], rng.Range(0, 1))
		}
	}

	return l, rooms
}

func connectRooms(l *gamemap.Level, room, otherRoom gamemap.Room, toss int) {
	if toss == 0 {
		digHTunnel(l, room.Center.X, otherRoom.Center.X, room.Center.Y)
		digVTunnel(l, room.Center.Y, otherRoom.Center.Y, otherRoom.Center.X)
	} else {
		digVTunnel(l, room.Center.Y, otherRoom.Center.Y, room.Center.Y)
		digHTunnel(l, room.Center.X, otherRoom.Center.X, otherRoom.Center.Y)
	}
}

func digHTunnel(l *gamemap.Level, x1, x2, y int) {
	var start, finish int
	if x1 < x2 {
		start = x1
		finish = x2
	} else {
		start = x2
		finish = x1
	}
	for i := start; i <= finish; i++ {
		if l.InBounds(types.Coords{i, y}) {
			l.SetTileByXY(i, y, gamemap.NewFloor())
			//l.Tiles[i][y] = gamemap.NewFloor()
		}
	}
}

func digVTunnel(l *gamemap.Level, y1, y2, x int) {
	var start, finish int
	if y1 < y2 {
		start = y1
		finish = y2
	} else {
		start = y2
		finish = y1
	}
	for i := start; i <= finish; i++ {
		if l.InBounds(types.Coords{x, i}) {
			l.SetTileByXY(x, i, gamemap.NewFloor())
		}
	}
}