package precomputed_permissive

import (
	"errors"
	"fmt"
	"lab.zaar.be/thefish/alchemyst-go/engine/fov/basic"
	"lab.zaar.be/thefish/alchemyst-go/engine/types"
	"math"
	"sort"
)

//Incomplete implementation of permissive algo with precomputation

var NotFoundCell = errors.New("Cell not found")

type Cell struct {
	types.Coords
	distance float64
	occluded []int //indexes of cells in CellList
}

type CellList []*Cell

type DistanceSorter CellList

func (a DistanceSorter) Len() int           { return len(a) }
func (a DistanceSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a DistanceSorter) Less(i, j int) bool { return a[i].distance < a[j].distance }

func (pp *precomputedPermissive) FindByCoords(c types.Coords) (int, *Cell, error) {
	for i := range pp.CellList {
		if pp.CellList[i].Coords == c {
			// Found!
			return i, pp.CellList[i], nil
		}
	}
	return 0, &Cell{}, NotFoundCell
}

type precomputedPermissive struct {
	MaxTorchRadius int
	CellList       CellList

	cosTable map[int]float64
	sinTable map[int]float64
}

func NewPrecomputedPermissive(maxTorchRadius int) *precomputedPermissive {
	result := &precomputedPermissive{MaxTorchRadius: maxTorchRadius}
	result.PrecomputeFovMap()
	return result
}

func (pp *precomputedPermissive) IsInFov(coords types.Coords) bool {
	return true
}

func (pp *precomputedPermissive) ComputeFov(coords types.Coords, radius int) {

}

func (pp *precomputedPermissive) PrecomputeFovMap() {
	max := pp.MaxTorchRadius
	minusMax := (-1) * max
	zeroCoords := types.Coords{X: 0, Y: 0}
	var x, y int
	//fill list
	for x = minusMax; x < max+1; x++ {
		for y = minusMax; y < max+1; y++ {
			if x == 0 && y == 0 {
				continue;
			}
			iterCoords := types.Coords{X: x, Y: y}
			distance := zeroCoords.DistanceTo(iterCoords)
			if distance <= float64(max) {
				pp.CellList = append(pp.CellList, &Cell{iterCoords, distance, nil})
			}
		}
	}
	//Do not change cell order after this!
	sort.Sort(DistanceSorter(pp.CellList))
	//debug
	//for _, cell := range pp.CellList {
	//	fmt.Printf("\n coords: %v, distance: %f, len_occl: %d", cell.Coords, cell.distance, len(cell.occluded))
	//}

	//Bresanham lines / Raycast
	var lineX, lineY float64
	for i := 0; i < 360; i++ {
		dx := math.Sin(float64(i) / (float64(180) / math.Pi))
		dy := math.Cos(float64(i) / (float64(180) / math.Pi))

		occlusion := make([]int, max)
		traversedCells := make([]*Cell, max)
		lineX = 0
		lineY = 0
		for j := 0; j < max; j++ {
			lineX -= dx
			lineY -= dy

			roundedX := int(basic.Round(lineX))
			roundedY := int(basic.Round(lineY))

			idx, cell, err := pp.FindByCoords(types.Coords{X: roundedX, Y: roundedY})
			if err != nil {
				//inexistent coord found
				break;
			}
			occlusion[j] = idx
			traversedCells[j] = cell
		}
		// -2 because we do not want to cell occlude itself
		for k := len(occlusion) - 2; k >= 0; k-- {
			if traversedCells[k] == nil {
				continue;
			}
			if traversedCells[k].occluded == nil {
				traversedCells[k].occluded = occlusion[k + 1:]
			}
			//Remove duplicates
			traversedCells[k].occluded = unique(append(traversedCells[k].occluded, occlusion[k + 1:]...))
		}
		fmt.Printf("\n next: %d", i)
	}

	fmt.Printf("before len: %d", len(pp.CellList))
	fmt.Printf("after len: %d", len(pp.CellList))

	for _, cell := range pp.CellList {
		fmt.Printf("\n coords: %v, distance: %f, len_occl: %d", cell.Coords, cell.distance, len(cell.occluded))
	}
}


func unique(intSlice []int) []int {
	keys := make(map[int]bool)
	list := []int{}
	for _, entry := range intSlice {
		if _, value := keys[entry]; !value {
			keys[entry] = true
			list = append(list, entry)
		}
	}
	return list
}
