package main

import (
	"fmt"
	"math"
	"sort"
)

type CycleError struct {
	Processed        int
	Total            int
	RemainingNodeIDs []int
	RemainingIRISample []string
}

func (e *CycleError) Error() string {
	msg := fmt.Sprintf("Cycle detected in subClassOf graph (processed %d/%d nodes).", e.Processed, e.Total)
	if len(e.RemainingIRISample) > 0 {
		msg += " Example nodes: " + stringsJoin(e.RemainingIRISample, ", ")
	}
	return msg
}

func levelSynchronousKahnLayers(nodeCount int, edges [][2]int) ([][]int, *CycleError) {
	n := nodeCount
	if n <= 0 {
		return [][]int{}, nil
	}

	adj := make([][]int, n)
	indeg := make([]int, n)

	for _, e := range edges {
		u, v := e[0], e[1]
		if u == v {
			continue
		}
		if u < 0 || u >= n || v < 0 || v >= n {
			continue
		}
		adj[u] = append(adj[u], v)
		indeg[v]++
	}

	q := make([]int, 0, n)
	for i, d := range indeg {
		if d == 0 {
			q = append(q, i)
		}
	}

	layers := make([][]int, 0)
	processed := 0
	for len(q) > 0 {
		layer := append([]int(nil), q...)
		q = q[:0]
		layers = append(layers, layer)

		for _, u := range layer {
			processed++
			for _, v := range adj[u] {
				indeg[v]--
				if indeg[v] == 0 {
					q = append(q, v)
				}
			}
		}
	}

	if processed != n {
		remaining := make([]int, 0)
		for i, d := range indeg {
			if d > 0 {
				remaining = append(remaining, i)
			}
		}
		return nil, &CycleError{Processed: processed, Total: n, RemainingNodeIDs: remaining}
	}

	return layers, nil
}

func radialPositionsFromLayers(nodeCount int, layers [][]int, maxR float64) (xs []float64, ys []float64) {
	n := nodeCount
	if n <= 0 {
		return []float64{}, []float64{}
	}

	xs = make([]float64, n)
	ys = make([]float64, n)
	if len(layers) == 0 {
		return xs, ys
	}

	twoPi := 2.0 * math.Pi
	golden := math.Pi * (3.0 - math.Sqrt(5.0))

	layerCount := float64(len(layers))
	denom := layerCount + 1.0

	for li, layer := range layers {
		m := len(layer)
		if m == 0 {
			continue
		}
		r := (float64(li+1) / denom) * maxR
		offset := math.Mod(float64(li)*golden, twoPi)

		if m == 1 {
			nid := layer[0]
			if nid >= 0 && nid < n {
				xs[nid] = r * math.Cos(offset)
				ys[nid] = r * math.Sin(offset)
			}
			continue
		}

		step := twoPi / float64(m)
		for j, nid := range layer {
			if nid < 0 || nid >= n {
				continue
			}
			t := offset + step*float64(j)
			xs[nid] = r * math.Cos(t)
			ys[nid] = r * math.Sin(t)
		}
	}

	return xs, ys
}

func sortLayerByIRI(layer []int, idToIRI []string) {
	sort.Slice(layer, func(i, j int) bool {
		return idToIRI[layer[i]] < idToIRI[layer[j]]
	})
}

func stringsJoin(parts []string, sep string) string {
	if len(parts) == 0 {
		return ""
	}
	out := parts[0]
	for i := 1; i < len(parts); i++ {
		out += sep
		out += parts[i]
	}
	return out
}
