package rdf

import (
	
	
)

// Quad is an RDF statement in a named graph: a triple plus the graph it belongs
// to. A zero-value graph term (G) denotes the default graph, so a Quad is a
// strict superset of a Triple.
type Quad struct {
	S, P, O, G Term
}

// Triple projects the quad onto its statement, dropping the graph term.
func ( Quad) () Triple { return Triple{.S, .P, .O} }

// Dataset holds quads — RDF statements each tagged with the graph they belong
// to — in document order. It is the quad-level analogue of Graph and what
// ParseNQuads returns; it is how provenance (which source a statement came
// from) is carried.
//
// Like [Graph.Triples], Quads is a list and may repeat a statement the document
// stated twice; see the Graph doc for why parsing preserves that.
type Dataset struct {
	// Quads are in document order and may repeat; see the type doc.
	Quads []Quad

	counts    []graphCount // lazy statement count per graph term, built in one pass
	countsGen int          // len(Quads) when counts was built, to detect appends
}

// graphCount is one graph term's statement count. The counts are a slice, not a
// map: a dataset carries a handful of provenance graphs, so a linear scan over a
// few entries beats hashing a Term (three strings) for every quad in the dataset.
type graphCount struct {
	g Term
	n int
}

// Add appends a quad.
func ( *Dataset) (, , ,  Term) { .Quads = append(.Quads, Quad{, , , }) }

// graphCounts returns the statement count of every graph term, built lazily in a
// single pass and shared by every view of this dataset. One pass answers how big
// each graph is, so code reading several graphs — or probing for an empty one —
// need not scan the dataset once per graph.
func ( *Dataset) () []graphCount {
	if .counts != nil && .countsGen == len(.Quads) {
		return .counts
	}
	 := make([]graphCount, 0, 4)
	 := 0 // quads of one graph cluster, so the previous hit usually hits again
	for  := range .Quads {
		 := .Quads[].G
		if  < len() && [].g ==  {
			[].n++
			continue
		}
		 := false
		for  := range  {
			if [].g ==  {
				[].n++
				,  = , true
				break
			}
		}
		if ! {
			 = append(, graphCount{, 1})
			 = len() - 1
		}
	}
	.counts, .countsGen = , len(.Quads)
	return 
}

// GraphLen returns the number of statements in the given graph term; pass a
// zero-value term for the default graph. It reads a cached per-graph count, so
// after the first call on a dataset it costs no scan — asking about several
// graphs, or about a graph that turns out to be empty, is one pass in total
// rather than one pass per graph.
func ( *Dataset) ( Term) int {
	for ,  := range .graphCounts() {
		if .g ==  {
			return .n
		}
	}
	return 0
}

// HasGraph reports whether the dataset carries any statement in the given graph
// term. Like GraphLen it answers from the cached counts rather than scanning, so
// skipping an absent graph is free.
func ( *Dataset) ( Term) bool { return .GraphLen() > 0 }

// Graph returns the triples belonging to the given graph term as a Graph; pass a
// zero-value term for the default graph. It copies one Triple per matching quad,
// so a caller that only reads the result should use GraphView instead, which
// answers the same queries over the dataset's quads without copying.
func ( *Dataset) ( Term) *Graph {
	 := &Graph{}
	for ,  := range .Quads {
		if .G ==  {
			.Triples = append(.Triples, .Triple())
		}
	}
	return 
}

// Graphs returns the distinct graph terms present in the dataset, in first-seen
// order — the set of provenance sources. It reads the same cached counts GraphLen
// does, which record each graph on first sight, so repeated calls cost no scan.
func ( *Dataset) () []Term {
	 := .graphCounts()
	if len() == 0 {
		return nil
	}
	 := make([]Term, len())
	for ,  := range  {
		[] = .g
	}
	return 
}

// ---- serialization ----

// Encoder serializes a sequence of graphs into one document — N-Triples,
// N-Quads or Turtle — keeping blank-node labels unique across the graphs it
// writes. Blank-node labels are scoped to the whole document, so a single
// Encoder must be reused for every graph written to one stream; a fresh encoder
// per graph would let graphs that each number their blanks from scratch (such as
// one BIBFRAME graph per record) collide and merge. It is the serialization
// counterpart to Decoder; the whole-graph Graph.NTriples, Graph.NQuads and
// Graph.Turtle methods each use a fresh Encoder internally.
type Encoder struct {
	bn blankNamer
}

// AppendQuad appends one quad to b as an N-Quads line. A default-graph quad
// (zero-value G) is written as a three-term N-Triples line.
func ( *Encoder) ( []byte,  Quad) []byte {
	 = appendNTTerm(, .S, &.bn)
	 = append(, ' ')
	 = appendNTTerm(, .P, &.bn)
	 = append(, ' ')
	 = appendNTTerm(, .O, &.bn)
	if .G != (Term{}) { // omit the graph term for the default graph
		 = append(, ' ')
		 = appendNTTerm(, .G, &.bn)
	}
	return append(, ' ', '.', '\n')
}

// AppendNQuads appends every triple of g to b as N-Quads tagged with the graph
// term — g's named graph / provenance. A zero-value graph term writes the
// default graph (plain N-Triples). Each call is a fresh blank-node scope, so
// graphs that label their blanks from scratch never merge, while their output
// labels stay unique across the whole document.
func ( *Encoder) ( []byte,  *Graph,  Term) []byte {
	.bn.newScope()
	 := 0
	for ,  := range .Triples {
		 += quadBytes(.S, .P, .O, )
	}
	 = slices.Grow(, ) // one growth instead of the log(n) of an empty start
	for ,  := range .Triples {
		 = .AppendQuad(, Quad{.S, .P, .O, })
	}
	return 
}

// AppendNTriples appends g's triples to b as N-Triples in a fresh blank-node
// scope — N-Triples being N-Quads restricted to the default graph.
func ( *Encoder) ( []byte,  *Graph) []byte {
	return .AppendNQuads(, , Term{})
}

// quadBytes estimates the serialized N-Quads byte length of one statement, used
// to pre-size output buffers. It ignores escaping, so the count is a slight
// undercount that removes the reallocations of growing a buffer from empty.
func quadBytes(, , ,  Term) int {
	 := 14 + len(.Value) + len(.Value) + len(.Value) // brackets, spaces, " .\n"
	if .Kind == Literal {
		 += len(.Lang) + len(.Datatype) + 6
	}
	if  != (Term{}) {
		 += len(.Value) + 3
	}
	return 
}

// NQuads serializes the dataset as an N-Quads document.
func ( *Dataset) () []byte {
	var  Encoder
	 := 0
	for ,  := range .Quads {
		 += quadBytes(.S, .P, .O, .G)
	}
	 := make([]byte, 0, )
	for ,  := range .Quads {
		 = .AppendQuad(, )
	}
	return 
}

// NQuads serializes the graph's triples as N-Quads, each tagged with the given
// graph term (its named graph / provenance). A zero-value graph term produces
// plain N-Triples.
func ( *Graph) ( Term) []byte {
	var  Encoder
	return .AppendNQuads(nil, , )
}

// ---- N-Quads parsing ----

// ParseNQuads parses an N-Quads document into a Dataset, preserving each
// statement's graph term; lines carrying only three terms fall in the default
// graph. It shares the N-Triples term reader, so the same escaping and leniency
// apply — blank, comment and malformed lines are skipped. One private copy of
// the input backs every term, so data is free for reuse once it returns.
func ( []byte) (*Dataset, error) {
	return parseNQuads(string())
}

// ParseNQuadsShared is ParseNQuads without the private input copy: terms are
// zero-copy views into data itself, saving one input-sized allocation — worth
// it when read-heavy consumers parse corpus-scale documents. In exchange the
// caller must not modify data while the Dataset, or any Graph or Term derived
// from it, remains in use.
func ( []byte) (*Dataset, error) {
	return parseNQuads(bytesView())
}

func parseNQuads( string) (*Dataset, error) {
	 := &Dataset{Quads: make([]Quad, 0, strings.Count(, "\n")+1)}
	var  arena
	 := 0
	for  := range strings.SplitSeq(, "\n") {
		++
		switch ,  := parseNQuadLine(, &);  {
		case lineStatement:
			.Quads = append(.Quads, )
		case lineMalformed:
			return , &SyntaxError{Line: , Text: strings.TrimSpace()}
		}
	}
	return , nil
}