package solver

import (
	"errors"
	"fmt"
	"maze-solver/maze"
	"maze-solver/utils"
	"strings"
)

type BFSSolver struct {
	solved_chan chan<- *maze.SolvedMaze
	queue       *Queue
}

type Queue struct {
	head, tail *Element
}

type Element struct {
	prev, next *Element
	value      []*maze.Node
}

func (q *Queue) enqueue(v []*maze.Node) {
	prev_last := q.tail
	new_elem := &Element{
		prev:  prev_last,
		next:  nil,
		value: v,
	}
	if prev_last != nil {
		prev_last.next = new_elem
	}

	q.tail = new_elem

	if q.head == nil {
		q.head = new_elem
	}
}

func (q *Queue) dequeue() ([]*maze.Node, error) {
	if q.head == nil {
		return nil, errors.New("Can't dequeue and empty queue")
	}
	ret := q.head.value
	q.head = q.head.next
	if q.head != nil {
		q.head.prev = nil
	} else {
		q.tail = nil
	}

	return ret, nil
}

func (q Queue) String() string {
	var ret strings.Builder
	i := 0
	for history := q.head; history != nil; history = history.next {
		ret.WriteString(fmt.Sprintf("%v: %v\n", i, history_str(history.value)))
		i++
	}
	return ret.String()
}

func history_str(history []*maze.Node) string {
	var ret strings.Builder
	for _, node := range history {
		ret.WriteString(fmt.Sprintf("%v ", node.Coords))
	}
	return ret.String()
}

func (s *BFSSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
	defer utils.Timer("BFS algorithm", 2)()

	current, end := m.Nodes[0], m.Nodes[len(m.Nodes)-1]
	s.queue = &Queue{
		head: nil,
		tail: nil,
	}

	current_history := make([]*maze.Node, 0, len(m.Nodes))
	current_history = append(current_history, current)

	var err error
	for current != end {
		current.Visited = true
		if s.solved_chan != nil {
			s.solved_chan <- &maze.SolvedMaze{
				Maze:     m,
				Solution: current_history,
			}
		}

		s.addIfNotVisited(current.Down, current_history)
		s.addIfNotVisited(current.Left, current_history)
		s.addIfNotVisited(current.Right, current_history)
		s.addIfNotVisited(current.Up, current_history)

		current_history, err = s.queue.dequeue()
		if err != nil {
			panic(err)
		}
		current = current_history[len(current_history)-1]
	}
	if s.solved_chan != nil {
		s.solved_chan <- &maze.SolvedMaze{
			Maze:     m,
			Solution: current_history,
		}
	}

	return &maze.SolvedMaze{
		Maze:     m,
		Solution: current_history,
	}
}

func (s *BFSSolver) addIfNotVisited(node *maze.Node, current_history []*maze.Node) {
	if !visited(node) {
		new_history := make([]*maze.Node, len(current_history)+1)
		copy(new_history, current_history)
		new_history[len(current_history)] = node

		s.queue.enqueue(new_history)
	}
}
