package delaunay

//Original algo courtesy of github.com/esimov/triangle

import "lab.zaar.be/thefish/alchemyst-go/engine/types"

var nodeId = 0
var nodeList = make(map[types.Coords]Node, 0)

// Node defines a struct having as components the node X and Y coordinate position.
type Node struct {
	Id int
	types.Coords
}

// Struct which defines a circle geometry element.
type circle struct {
	types.Coords
	Radius int
}

// newNode creates a new node.
func newNode(coords types.Coords) Node {
	if n, ok := nodeList[coords]; ok {
		return n
	}
	neue := Node{Id: nodeId, Coords: coords}
	nodeList[coords] = neue
	nodeId++
	return neue
}

// isEq check if two Nodes are approximately equals.
func (this Node) isEq(other Node) bool {
	dx := this.X - other.X
	dy := this.Y - other.Y

	if dx < 0 {
		dx = -dx
	}
	if dy < 0 {
		dy = -dy
	}
	if float64(dx) < 0.0001 && float64(dy) < 0.0001 {
		return true
	}
	return false
}

// Edge struct having as component the node list.
type Edge struct {
	Nodes []Node
}

// newEdge creates a new Edge.
func newEdge(this, next Node) []Node {
	nodes := []Node{this, next}
	return nodes
}

// isEq check if two Edge are approximately equals.
func (e Edge) isEq(edge Edge) bool {
	na := e.Nodes
	nb := edge.Nodes
	na0, na1 := na[0], na[1]
	nb0, nb1 := nb[0], nb[1]

	if (na0.isEq(nb0) && na1.isEq(nb1)) ||
		(na0.isEq(nb1) && na1.isEq(nb0)) {
		return true
	}
	return false
}

// Triangle struct defines the basic components of a triangle.
// It's constructed by Nodes, it's Edges and the circumcircle which describes the triangle circumference.
type Triangle struct {
	Nodes  []Node
	Edges  []Edge
	circle circle
}

var t = Triangle{}

// newTriangle creates a new triangle which circumcircle encloses the point to be added.
func (t Triangle) newTriangle(p0, p1, p2 Node) Triangle {
	t.Nodes = []Node{p0, p1, p2}
	t.Edges = []Edge{{newEdge(p0, p1)}, {newEdge(p1, p2)}, {newEdge(p2, p0)}}

	// Create a circumscribed circle of this triangle.
	// The circumcircle of a triangle is the circle which has the three vertices of the triangle lying on its circumference.
	circle := t.circle
	ax, ay := p1.X-p0.X, p1.Y-p0.Y
	bx, by := p2.X-p0.X, p2.Y-p0.Y

	m := p1.X*p1.X - p0.X*p0.X + p1.Y*p1.Y - p0.Y*p0.Y
	u := p2.X*p2.X - p0.X*p0.X + p2.Y*p2.Y - p0.Y*p0.Y
	s := 1.0 / (2.0 * (float64(ax*by) - float64(ay*bx)))

	circle.Coords.X = int(float64((p2.Y-p0.Y)*m+(p0.Y-p1.Y)*u) * s)
	circle.Coords.Y = int(float64((p0.X-p2.X)*m+(p1.X-p0.X)*u) * s)

	// Calculate the distance between the node points and the triangle circumcircle.
	dx := p0.X - circle.Coords.X
	dy := p0.Y - circle.Coords.Y

	// Calculate the circle radius.
	circle.Radius = dx*dx + dy*dy
	t.circle = circle

	return t
}

// Delaunay defines the main components for the triangulation.
type Delaunay struct {
	width     int
	height    int
	triangles []Triangle
}

// Init initialize the delaunay structure.
func (d *Delaunay) Init(width, height int) *Delaunay {
	d.width = width
	d.height = height

	d.triangles = nil
	d.clear()

	return d
}

var supertriangle1, supertriangle2 Triangle

// clear method clears the delaunay triangles slice.
func (d *Delaunay) clear() {
	p0 := newNode(types.Coords{X: 0, Y: 0})
	p1 := newNode(types.Coords{X: d.width, Y: 0})
	p2 := func() Node {
		var coords types.Coords = types.Coords{X: d.width, Y: d.height}
		if n, ok := nodeList[coords]; ok {
			return n
		}
		neue := Node{Id: nodeId, Coords: coords}
		nodeList[coords] = neue
		nodeId++
		return neue
	}()
	p3 := newNode(types.Coords{X: 0, Y: d.height})

	// Create the supertriangle, an artificial triangle which encompasses all the points.
	// At the end of the triangulation process any triangles which share Edges with the supertriangle are deleted from the triangle list.
	supertriangle1 = t.newTriangle(p0, p1, p2)
	supertriangle2 = t.newTriangle(p0, p2, p3)
	d.triangles = []Triangle{supertriangle1, supertriangle2}
}

// Insert will insert new triangles into the triangles slice.
func (d *Delaunay) Insert(points []types.Coords) *Delaunay {
	var (
		i, j, k      int
		x, y, dx, dy int
		distSq       int
		polygon      []Edge
		edges        []Edge
		temps        []Triangle
	)

	for k = 0; k < len(points); k++ {
		x = points[k].X
		y = points[k].Y

		triangles := d.triangles
		edges = nil
		temps = nil

		for i = 0; i < len(d.triangles); i++ {
			t := triangles[i]

			//Check whether the points are inside the triangle circumcircle.
			circle := t.circle
			dx = circle.Coords.X - x
			dy = circle.Coords.Y - y
			distSq = dx*dx + dy*dy

			if distSq < circle.Radius {
				// Save triangle Edges in case they are included.
				edges = append(edges, t.Edges[0], t.Edges[1], t.Edges[2])
			} else {
				// If not included carry over.
				temps = append(temps, t)
			}
		}

		polygon = nil
		// Check duplication of Edges, delete if duplicates.
	edgesLoop:
		for i = 0; i < len(edges); i++ {
			edge := edges[i]
			for j = 0; j < len(polygon); j++ {
				// Remove identical Edges.
				if edge.isEq(polygon[j]) {
					// Remove polygon from the polygon slice.
					polygon = append(polygon[:j], polygon[j+1:]...)
					continue edgesLoop
				}
			}
			// Insert new Edge into the polygon slice.
			polygon = append(polygon, edge)

		}
		for i = 0; i < len(polygon); i++ {
			edge := polygon[i]
			temps = append(temps, t.newTriangle(edge.Nodes[0], edge.Nodes[1], newNode(types.Coords{X: x, Y: y})))
		}
		d.triangles = temps
	}

	//Clean up supertriangles
	z := 0 // output index
	var valid bool
	cleanList := make([]Triangle, 0)
	for _, triangle := range d.triangles {
		valid = true
		for _, checkedNode := range triangle.Nodes {
			for _, superNode := range supertriangle1.Nodes {
				if checkedNode == superNode {
					valid = false
					break
				}
			}
			for _, superNode := range supertriangle2.Nodes {
				if checkedNode == superNode {
					valid = false
					break
				}
			}
			if !valid {
				break
			}
		}
		if !valid {
			continue
		}
		cleanList = append(cleanList, triangle)
		z++
	}
	d.triangles = cleanList

	//update node indices
	return d
}

// GetTriangles return the generated triangles.
func (d *Delaunay) GetTriangles() []Triangle {
	return d.triangles
}

func (d *Delaunay) GetEdges() []Edge {
    edges := make([]Edge, 0)
    for _, trs := range d.triangles {
        edges = append(edges, trs.Edges...)
    }
    return edges
}
