// Package graph contains generic implementations of basic graph algorithms.
//
// Generic graph algorithms
//
// The algorithms in this library can be applied to any graph data
// structure implementing the two Iterator methods: Order, which returns
// the number of vertices, and Visit, which iterates over the neighbors
// of a vertex.
//
// All algorithms operate on directed graphs with a fixed number
// of vertices, labeled from 0 to n-1, and edges with integer cost.
// An undirected edge {v, w} of cost c is represented by the two
// directed edges (v, w) and (w, v), both of cost c.
// A self-loop, an edge connecting a vertex to itself,
// is both directed and undirected.
//
// Graph data structures
//
// The type Mutable represents a directed graph with a fixed number
// of vertices and weighted edges that can be added or removed.
// The implementation uses hash maps to associate each vertex
// in the graph with its adjacent vertices. This gives constant
// time performance for all basic operations.
//
// The type Immutable is a compact representation of an immutable graph.
// The implementation uses lists to associate each vertex in the graph
// with its adjacent vertices. This makes for fast and predictable
// iteration: the Visit method produces its elements by reading
// from a fixed sorted precomputed list. This type supports multigraphs.
//
// Virtual graphs
//
// The subpackage graph/build offers a tool for building virtual graphs.
// In a virtual graph no vertices or edges are stored in memory,
// they are instead computed as needed. New virtual graphs are constructed
// by composing and filtering a set of standard graphs, or by writing
// functions that describe the edges of a graph.
//
// Tutorial
//
// The Basics example shows how to build  a plain graph and how to
// efficiently use the Visit iterator, the key abstraction of this package.
//
// The DFS example contains a full implementation of depth-first search.
//
package graph

import (
	"sort"
	"strconv"
)

// Iterator describes a weighted graph; an Iterator can be used
// to describe both ordinary graphs and multigraphs.
type Iterator interface {
	// Order returns the number of vertices in a graph.
	Order() int

	// Visit calls the do function for each neighbor w of vertex v,
	// with c equal to the cost of the edge from v to w.
	//
	//  • If do returns true, Visit returns immediately, skipping
	//    any remaining neighbors, and returns true.
	//
	//  • The calls to the do function may occur in any order,
	//    and the order may vary.
	//
	Visit(v int, do func(w int, c int64) (skip bool)) (aborted bool)
}

// The maximum and minum value of an edge cost.
const (
	Max int64 = 1<<63 - 1
	Min int64 = -1 << 63
)

type edge struct {
	v, w int
	c    int64
}

// String returns a description of g with two elements:
// the number of vertices, followed by a sorted list of all edges.
func String(g Iterator) string {
	n := g.Order()
	// This may be a multigraph, so we look for duplicates by counting.
	count := make(map[edge]int)
	for v := 0; v < n; v++ {
		g.Visit(v, func(w int, c int64) (skip bool) {
			count[edge{v, w, c}]++
			return
		})
	}
	edges := make([]edge, 0, len(count))
	for e := range count {
		edges = append(edges, e)
	}
	// Sort lexicographically on (v, w, c).
	sort.Slice(edges, func(i, j int) bool {
		v := edges[i].v == edges[j].v
		w := edges[i].w == edges[j].w
		switch {
		case v && w:
			return edges[i].c < edges[j].c
		case v:
			return edges[i].w < edges[j].w
		default:
			return edges[i].v < edges[j].v
		}
	})
	// Build the string.
	var buf []byte
	buf = strconv.AppendInt(buf, int64(n), 10)
	buf = append(buf, " ["...)
	for _, e := range edges {
		c := count[e]
		if e.v < e.w {
			// Collect edges in opposite directions into an undirected edge.
			back := edge{e.w, e.v, e.c}
			m := min(c, count[back])
			count[back] -= m
			buf = appendEdge(buf, e, m, true)
			buf = appendEdge(buf, e, c-m, false)
		} else {
			buf = appendEdge(buf, e, c, false)
		}
	}
	if len(edges) > 0 {
		buf = buf[:len(buf)-1] // Remove trailing ' '.
	}
	buf = append(buf, ']')
	return string(buf)
}

func appendEdge(buf []byte, e edge, count int, bi bool) []byte {
	if count <= 0 {
		return buf
	}
	if count > 1 {
		buf = strconv.AppendInt(buf, int64(count), 10)
		buf = append(buf, "×"...)
	}
	if bi {
		buf = append(buf, '{')
	} else {
		buf = append(buf, '(')
	}
	buf = strconv.AppendInt(buf, int64(e.v), 10)
	buf = append(buf, ' ')
	buf = strconv.AppendInt(buf, int64(e.w), 10)
	if bi {
		buf = append(buf, '}')
	} else {
		buf = append(buf, ')')
	}
	if e.c != 0 {
		buf = append(buf, ':')
		switch e.c {
		case Max:
			buf = append(buf, "max"...)
		case Min:
			buf = append(buf, "min"...)
		default:
			buf = strconv.AppendInt(buf, e.c, 10)
		}
	}
	buf = append(buf, ' ')
	return buf
}

// Stats holds basic data about an Iterator.
type Stats struct {
	Size     int // Number of unique edges.
	Multi    int // Number of duplicate edges.
	Weighted int // Number of edges with non-zero cost.
	Loops    int // Number of self-loops.
	Isolated int // Number of vertices with outdegree zero.
}

// Check collects data about an Iterator.
func Check(g Iterator) Stats {
	if g, ok := g.(*Immutable); ok {
		return g.stats
	}
	_, mutable := g.(*Mutable)

	n := g.Order()
	degree := make([]int, n)
	type edge struct{ v, w int }
	edges := make(map[edge]bool)
	var stats Stats
	for v := 0; v < n; v++ {
		g.Visit(v, func(w int, c int64) (skip bool) {
			if w < 0 || w >= n {
				panic("vertex out of range: " + strconv.Itoa(w))
			}
			if v == w {
				stats.Loops++
			}
			if c != 0 {
				stats.Weighted++
			}
			degree[v]++
			if mutable { // A Mutable is never a multigraph.
				stats.Size++
				return
			}
			if edges[edge{v, w}] {
				stats.Multi++
			} else {
				stats.Size++
			}
			edges[edge{v, w}] = true
			return
		})
	}
	for _, deg := range degree {
		if deg == 0 {
			stats.Isolated++
		}
	}
	return stats
}

// Equal tells if g and h have the same number of vertices,
// and the same edges with the same costs.
func Equal(g, h Iterator) bool {
	if g.Order() != h.Order() {
		return false
	}
	edges := make(map[edge]int)
	for v := 0; v < g.Order(); v++ {
		g.Visit(v, func(w int, c int64) (skip bool) {
			edges[edge{v, w, c}]++
			return
		})
		if h.Visit(v, func(w int, c int64) (skip bool) {
			if edges[edge{v, w, c}] == 0 {
				return true
			}
			edges[edge{v, w, c}]--
			return
		}) {
			return false
		}
		for _, n := range edges {
			if n > 0 {
				return false
			}
		}
	}
	return true
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}
