package dijkstra

import (
	"fmt"
	"math"
	"sync"
)

type ItemGraph struct {
	Nodes []*Node
	Edges map[Node][]*Edge
	lock  sync.RWMutex
}

// AddNode adds a node to the graph
func (g *ItemGraph) AddNode(n *Node) {
	g.lock.Lock()
	g.Nodes = append(g.Nodes, n)
	g.lock.Unlock()
}

// AddEdge adds an edge to the graph
func (g *ItemGraph) AddEdge(n1, n2 *Node, weight int) {
	g.lock.Lock()
	if g.Edges == nil {
		g.Edges = make(map[Node][]*Edge)
	}
	ed1 := Edge{
		Node:   n2,
		Weight: weight,
	}

	ed2 := Edge{
		Node:   n1,
		Weight: weight,
	}
	g.Edges[*n1] = append(g.Edges[*n1], &ed1)
	g.Edges[*n2] = append(g.Edges[*n2], &ed2)
	g.lock.Unlock()
}

// dijkstra implement
func getShortestPath(startNode *Node, endNode *Node, g *ItemGraph) ([]Point, int) {
	visited := make(map[Point]bool)
	dist := make(map[Point]int)
	prev := make(map[Point]Point)
	//pq := make(PriorityQueue, 1)
	//heap.Init(&pq)
	q := NodeQueue{}
	pq := q.NewQ()
	start := Vertex{
		Node:     startNode,
		Distance: 0,
	}
	for _, nval := range g.Nodes {
		dist[nval.Value] = math.MaxInt64
	}
	dist[startNode.Value] = start.Distance
	pq.Enqueue(start)
	//im := 0
	for !pq.IsEmpty() {
		v := pq.Dequeue()
		if visited[v.Node.Value] {
			continue
		}
		visited[v.Node.Value] = true
		near := g.Edges[*v.Node]

		for _, val := range near {
			if !visited[val.Node.Value] {
				if dist[v.Node.Value]+val.Weight < dist[val.Node.Value] {
					store := Vertex{
						Node:     val.Node,
						Distance: dist[v.Node.Value] + val.Weight,
					}
					dist[val.Node.Value] = dist[v.Node.Value] + val.Weight
					//prev[val.Node.Value] = fmt.Sprintf("->%s", v.Node.Value)
					prev[val.Node.Value] = v.Node.Value
					pq.Enqueue(store)
				}
				//visited[val.Node.value] = true
			}
		}
	}
	//	fmt.Println(dist)
	//	fmt.Println(prev)
	pathval := prev[endNode.Value]
	var finalArr []Point
	finalArr = append(finalArr, endNode.Value)
	for pathval != startNode.Value {
		finalArr = append(finalArr, pathval)
		pathval = prev[pathval]
	}
	finalArr = append(finalArr, pathval)
	fmt.Println(finalArr)
	for i, j := 0, len(finalArr)-1; i < j; i, j = i+1, j-1 {
		finalArr[i], finalArr[j] = finalArr[j], finalArr[i]
	}
	return finalArr, dist[endNode.Value]

}

func getAllShortestPaths(startNode *Node, endNode *Node, g *ItemGraph) ([][]Point, int) {
	visited := make(map[Point]bool)
	dist := make(map[Point]int)
	prev := make(map[Point][]Point) // Store multiple predecessors for each node
	q := NodeQueue{}
	pq := q.NewQ()

	start := Vertex{
		Node:     startNode,
		Distance: 0,
	}

	for _, n := range g.Nodes {
		dist[n.Value] = math.MaxInt64
	}
	dist[startNode.Value] = start.Distance
	pq.Enqueue(start)

	// Perform Dijkstra's algorithm
	for !pq.IsEmpty() {
		v := pq.Dequeue()
		if visited[v.Node.Value] {
			continue
		}
		visited[v.Node.Value] = true
		near := g.Edges[*v.Node]

		for _, edge := range near {
			alt := dist[v.Node.Value] + edge.Weight

			// Case 1: Found a shorter path
			if alt < dist[edge.Node.Value] {
				dist[edge.Node.Value] = alt
				prev[edge.Node.Value] = []Point{v.Node.Value} // Reset predecessors
				pq.Enqueue(Vertex{
					Node:     edge.Node,
					Distance: alt,
				})
			}

			// Case 2: Found an equally short path
			if alt == dist[edge.Node.Value] {
				// Add the current node as a valid predecessor if not already present
				found := false
				for _, p := range prev[edge.Node.Value] {
					if p == v.Node.Value {
						found = true
						break
					}
				}
				if !found {
					prev[edge.Node.Value] = append(prev[edge.Node.Value], v.Node.Value)
				}
			}
		}
	}

	// Iteratively reconstruct all paths
	var paths [][]Point
	stack := []struct {
		node Point
		path []Point
	}{
		{node: endNode.Value, path: []Point{}},
	}

	for len(stack) > 0 {
		// Pop the top item from the stack
		current := stack[len(stack)-1]
		stack = stack[:len(stack)-1]

		// Prepend the current node to the path
		newPath := append([]Point{current.node}, current.path...)

		// If we've reached the start node, save the path
		if current.node.X == startNode.Value.X && current.node.Y == startNode.Value.Y {
			paths = append(paths, newPath)
			continue
		}

		// Push all predecessors onto the stack
		for _, predecessor := range prev[current.node] {
			if !Contains(newPath, predecessor) {
				stack = append(stack, struct {
					node Point
					path []Point
				}{
					node: predecessor,
					path: newPath,
				})
			}
		}
	}

	return paths, dist[endNode.Value]
}

func Contains(slice []Point, value Point) bool {
	for _, v := range slice {
		if v == value { // Compare values
			return true
		}
	}
	return false
}

func CreateGraph(data InputGraph) *ItemGraph {
	var g ItemGraph
	nodes := make(map[Point]*Node)
	for _, v := range data.Graph {
		if _, found := nodes[v.Source]; !found {
			nA := Node{v.Source}
			nodes[v.Source] = &nA
			g.AddNode(&nA)
		}
		if _, found := nodes[v.Destination]; !found {
			nA := Node{v.Destination}
			nodes[v.Destination] = &nA
			g.AddNode(&nA)
		}
		g.AddEdge(nodes[v.Source], nodes[v.Destination], v.Weight)
	}
	return &g
}

func GetShortestPath(from, to Point, g *ItemGraph) ([]Point, int) {
	nA := &Node{from}
	nB := &Node{to}

	path, distance := getShortestPath(nA, nB, g)
	return path, distance
}

func GetAllShortestPaths(from, to Point, g *ItemGraph) ([][]Point, int) {
	nA := &Node{from}
	nB := &Node{to}

	paths, distance := getAllShortestPaths(nA, nB, g)
	return paths, distance
}
