package main

type termKey struct {
	termType string
	key      string
}

type graphAccumulator struct {
	includeBNodes bool
	nodeLimit     int
	nodeIDByKey   map[termKey]uint32
	nodes         []Node
	edges         []Edge
	preds         *PredicateDict
}

func newGraphAccumulator(nodeLimit int, includeBNodes bool, edgeCapHint int, preds *PredicateDict) *graphAccumulator {
	if preds == nil {
		preds = NewPredicateDict(nil)
	}
	return &graphAccumulator{
		includeBNodes: includeBNodes,
		nodeLimit:     nodeLimit,
		nodeIDByKey:   make(map[termKey]uint32),
		nodes:         make([]Node, 0, min(nodeLimit, 4096)),
		edges:         make([]Edge, 0, min(edgeCapHint, 4096)),
		preds:         preds,
	}
}

func (g *graphAccumulator) getOrAddNode(term sparqlTerm) (uint32, bool) {
	if term.Type == "" || term.Value == "" {
		return 0, false
	}
	if term.Type == "literal" {
		return 0, false
	}

	var key termKey
	var node Node

	if term.Type == "bnode" {
		if !g.includeBNodes {
			return 0, false
		}
		key = termKey{termType: "bnode", key: term.Value}
		node = Node{ID: 0, TermType: "bnode", IRI: "_:" + term.Value, Label: nil, X: 0, Y: 0}
	} else {
		key = termKey{termType: "uri", key: term.Value}
		node = Node{ID: 0, TermType: "uri", IRI: term.Value, Label: nil, X: 0, Y: 0}
	}

	if existing, ok := g.nodeIDByKey[key]; ok {
		return existing, true
	}
	if len(g.nodes) >= g.nodeLimit {
		return 0, false
	}
	nid := uint32(len(g.nodes))
	g.nodeIDByKey[key] = nid
	node.ID = nid
	g.nodes = append(g.nodes, node)
	return nid, true
}

func (g *graphAccumulator) addBindings(bindings []map[string]sparqlTerm) {
	for _, b := range bindings {
		sTerm := b["s"]
		oTerm := b["o"]
		pTerm := b["p"]

		sid, okS := g.getOrAddNode(sTerm)
		oid, okO := g.getOrAddNode(oTerm)
		if !okS || !okO {
			continue
		}

		predID, ok := g.preds.GetOrAdd(pTerm.Value)
		if !ok {
			continue
		}

		g.edges = append(g.edges, Edge{
			Source:      sid,
			Target:      oid,
			PredicateID: predID,
		})
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
