// Copyright ©2015 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dynamic

import (
	"bytes"
	"cmp"
	"fmt"
	"io"
	"slices"
	"text/tabwriter"

	"gonum.org/v1/gonum/graph/path/internal/testgraphs"
	"gonum.org/v1/gonum/graph/simple"
)

// dumper implements a grid D* Lite statistics dump.
type dumper struct {
	step int

	dStarLite *DStarLite
	grid      *testgraphs.LimitedVisionGrid

	w io.Writer
}

// dump writes a single step of a D* Lite path search to the dumper's io.Writer.
func (d *dumper) dump(withpath bool) {
	if d == nil {
		return
	}
	var pathStep map[int64]int
	if withpath {
		pathStep = make(map[int64]int)
		path, _ := d.dStarLite.Path()
		for i, n := range path {
			pathStep[n.ID()] = i
		}
	}
	fmt.Fprintf(d.w, "Step:%d kₘ=%v\n", d.step, d.dStarLite.keyModifier)
	d.step++
	w := tabwriter.NewWriter(d.w, 0, 0, 0, ' ', tabwriter.Debug)
	rows, cols := d.grid.Grid.Dims()
	for r := 0; r < rows; r++ {
		if r == 0 {
			for c := 0; c < cols; c++ {
				if c != 0 {
					fmt.Fprint(w, "\t")
				}
				fmt.Fprint(w, "-------------------")
			}
			fmt.Fprintln(w)
		}
		for ln := 0; ln < 6; ln++ {
			for c := 0; c < cols; c++ {
				if c != 0 {
					fmt.Fprint(w, "\t")
				}
				n := d.dStarLite.model.Node(d.grid.NodeAt(r, c).ID()).(*dStarLiteNode)
				switch ln {
				case 0:
					if n.ID() == d.grid.Location.ID() {
						if d.grid.Grid.HasOpen(n.ID()) {
							fmt.Fprintf(w, "id:%2d  >@<", n.ID())
						} else {
							// Mark location as illegal.
							fmt.Fprintf(w, "id:%2d  >!<", n.ID())
						}
					} else if n.ID() == d.dStarLite.t.ID() {
						fmt.Fprintf(w, "id:%2d   G", n.ID())
						// Mark goal cell as illegal.
						if !d.grid.Grid.HasOpen(n.ID()) {
							fmt.Fprint(w, "!")
						}
					} else if pathStep[n.ID()] > 0 {
						fmt.Fprintf(w, "id:%2d  %2d", n.ID(), pathStep[n.ID()])
						// Mark path cells with an obstruction.
						if !d.grid.Grid.HasOpen(n.ID()) {
							fmt.Fprint(w, "!")
						}
					} else {
						fmt.Fprintf(w, "id:%2d", n.ID())
						// Mark cells with an obstruction.
						if !d.grid.Grid.HasOpen(n.ID()) {
							fmt.Fprint(w, "   *")
						}
					}
				case 1:
					fmt.Fprintf(w, "h:  %.4v", d.dStarLite.heuristic(n, d.dStarLite.Here()))
				case 2:
					fmt.Fprintf(w, "g:  %.4v", n.g)
				case 3:
					fmt.Fprintf(w, "rhs:%.4v", n.rhs)
				case 4:
					if n.g != n.rhs {
						fmt.Fprintf(w, "key:%.3f", n.key)
					}
					if !n.key.isBadKey() {
						// Mark keys for nodes in the priority queue.
						// We use NaN inequality for this check since all
						// keys not in the queue must have their key set
						// to badKey.
						//
						// This should always mark cells where key is
						// printed.
						fmt.Fprint(w, "*")
					}
					if n.g > n.rhs {
						fmt.Fprint(w, "^")
					}
					if n.g < n.rhs {
						fmt.Fprint(w, "v")
					}
				default:
					fmt.Fprint(w, "-------------------")
				}
			}
			fmt.Fprintln(w)
		}
	}
	w.Flush()
	fmt.Fprintln(d.w)
}

// printEdges pretty prints the given edges to the dumper's io.Writer using the provided
// format string. The edges are first formatted to a string, so the format string must use
// the %s verb to indicate where the edges are to be printed.
func (d *dumper) printEdges(format string, edges []simple.WeightedEdge) {
	if d == nil {
		return
	}
	var buf bytes.Buffer
	slices.SortFunc(edges, func(a, b simple.WeightedEdge) int {
		if n := cmp.Compare(a.From().ID(), b.From().ID()); n != 0 {
			return n
		}
		return cmp.Compare(a.To().ID(), b.To().ID())
	})
	for i, e := range edges {
		if i != 0 {
			fmt.Fprint(&buf, ", ")
		}
		fmt.Fprintf(&buf, "%d->%d:%.4v", e.From().ID(), e.To().ID(), e.Weight())
	}
	if len(edges) == 0 {
		fmt.Fprint(&buf, "none")
	}
	fmt.Fprintf(d.w, format, buf.Bytes())
}
