package graph

import (
	
	
)

// Store represents a storage for vertices and edges. The graph library provides an in-memory store
// by default and accepts any Store implementation to work with - for example, an SQL store.
//
// When implementing your own Store, make sure the individual methods and their behavior adhere to
// this documentation. Otherwise, the graphs aren't guaranteed to behave as expected.
type Store[ comparable,  any] interface {
	// AddVertex should add the given vertex with the given hash value and vertex properties to the
	// graph. If the vertex already exists, it is up to you whether ErrVertexAlreadyExists or no
	// error should be returned.
	AddVertex(hash , value , properties VertexProperties) error

	// Vertex should return the vertex and vertex properties with the given hash value. If the
	// vertex doesn't exist, ErrVertexNotFound should be returned.
	Vertex(hash ) (, VertexProperties, error)

	// RemoveVertex should remove the vertex with the given hash value. If the vertex doesn't
	// exist, ErrVertexNotFound should be returned. If the vertex has edges to other vertices,
	// ErrVertexHasEdges should be returned.
	RemoveVertex(hash ) error

	// ListVertices should return all vertices in the graph in a slice.
	ListVertices() ([], error)

	// VertexCount should return the number of vertices in the graph. This should be equal to the
	// length of the slice returned by ListVertices.
	VertexCount() (int, error)

	// AddEdge should add an edge between the vertices with the given source and target hashes.
	//
	// If either vertex doesn't exit, ErrVertexNotFound should be returned for the respective
	// vertex. If the edge already exists, ErrEdgeAlreadyExists should be returned.
	AddEdge(sourceHash, targetHash , edge Edge[]) error

	// UpdateEdge should update the edge between the given vertices with the data of the given
	// Edge instance. If the edge doesn't exist, ErrEdgeNotFound should be returned.
	UpdateEdge(sourceHash, targetHash , edge Edge[]) error

	// RemoveEdge should remove the edge between the vertices with the given source and target
	// hashes.
	//
	// If either vertex doesn't exist, it is up to you whether ErrVertexNotFound or no error should
	// be returned. If the edge doesn't exist, it is up to you whether ErrEdgeNotFound or no error
	// should be returned.
	RemoveEdge(sourceHash, targetHash ) error

	// Edge should return the edge joining the vertices with the given hash values. It should
	// exclusively look for an edge between the source and the target vertex, not vice versa. The
	// graph implementation does this for undirected graphs itself.
	//
	// Note that unlike Graph.Edge, this function is supposed to return an Edge[K], i.e. an edge
	// that only contains the vertex hashes instead of the vertices themselves.
	//
	// If the edge doesn't exist, ErrEdgeNotFound should be returned.
	Edge(sourceHash, targetHash ) (Edge[], error)

	// ListEdges should return all edges in the graph in a slice.
	ListEdges() ([]Edge[], error)
}

type memoryStore[ comparable,  any] struct {
	lock             sync.RWMutex
	vertices         map[]
	vertexProperties map[]VertexProperties

	// outEdges and inEdges store all outgoing and ingoing edges for all vertices. For O(1) access,
	// these edges themselves are stored in maps whose keys are the hashes of the target vertices.
	outEdges map[]map[]Edge[] // source -> target
	inEdges  map[]map[]Edge[] // target -> source
}

func newMemoryStore[ comparable,  any]() Store[, ] {
	return &memoryStore[, ]{
		vertices:         make(map[]),
		vertexProperties: make(map[]VertexProperties),
		outEdges:         make(map[]map[]Edge[]),
		inEdges:          make(map[]map[]Edge[]),
	}
}

func ( *memoryStore[, ]) ( ,  ,  VertexProperties) error {
	.lock.Lock()
	defer .lock.Unlock()

	if ,  := .vertices[];  {
		return ErrVertexAlreadyExists
	}

	.vertices[] = 
	.vertexProperties[] = 

	return nil
}

func ( *memoryStore[, ]) () ([], error) {
	.lock.RLock()
	defer .lock.RUnlock()

	 := make([], 0, len(.vertices))
	for  := range .vertices {
		 = append(, )
	}

	return , nil
}

func ( *memoryStore[, ]) () (int, error) {
	.lock.RLock()
	defer .lock.RUnlock()

	return len(.vertices), nil
}

func ( *memoryStore[, ]) ( ) (, VertexProperties, error) {
	.lock.RLock()
	defer .lock.RUnlock()

	,  := .vertices[]
	if ! {
		return , VertexProperties{}, ErrVertexNotFound
	}

	 := .vertexProperties[]

	return , , nil
}

func ( *memoryStore[, ]) ( ) error {
	.lock.RLock()
	defer .lock.RUnlock()

	if ,  := .vertices[]; ! {
		return ErrVertexNotFound
	}

	if ,  := .inEdges[];  {
		if len() > 0 {
			return ErrVertexHasEdges
		}
		delete(.inEdges, )
	}

	if ,  := .outEdges[];  {
		if len() > 0 {
			return ErrVertexHasEdges
		}
		delete(.outEdges, )
	}

	delete(.vertices, )
	delete(.vertexProperties, )

	return nil
}

func ( *memoryStore[, ]) (,  ,  Edge[]) error {
	.lock.Lock()
	defer .lock.Unlock()

	if ,  := .outEdges[]; ! {
		.outEdges[] = make(map[]Edge[])
	}

	.outEdges[][] = 

	if ,  := .inEdges[]; ! {
		.inEdges[] = make(map[]Edge[])
	}

	.inEdges[][] = 

	return nil
}

func ( *memoryStore[, ]) (,  ,  Edge[]) error {
	if ,  := .Edge(, );  != nil {
		return 
	}

	.lock.Lock()
	defer .lock.Unlock()

	.outEdges[][] = 
	.inEdges[][] = 

	return nil
}

func ( *memoryStore[, ]) (,  ) error {
	.lock.Lock()
	defer .lock.Unlock()

	delete(.inEdges[], )
	delete(.outEdges[], )
	return nil
}

func ( *memoryStore[, ]) (,  ) (Edge[], error) {
	.lock.RLock()
	defer .lock.RUnlock()

	,  := .outEdges[]
	if ! {
		return Edge[]{}, ErrEdgeNotFound
	}

	,  := []
	if ! {
		return Edge[]{}, ErrEdgeNotFound
	}

	return , nil
}

func ( *memoryStore[, ]) () ([]Edge[], error) {
	.lock.RLock()
	defer .lock.RUnlock()

	 := make([]Edge[], 0)
	for ,  := range .outEdges {
		for ,  := range  {
			 = append(, )
		}
	}
	return , nil
}

// CreatesCycle is a fastpath version of [CreatesCycle] that avoids calling
// [PredecessorMap], which generates large amounts of garbage to collect.
//
// Because CreatesCycle doesn't need to modify the PredecessorMap, we can use
// inEdges instead to compute the same thing without creating any copies.
func ( *memoryStore[, ]) (,  ) (bool, error) {
	if , ,  := .Vertex();  != nil {
		return false, fmt.Errorf("could not get vertex with hash %v: %w", , )
	}

	if , ,  := .Vertex();  != nil {
		return false, fmt.Errorf("could not get vertex with hash %v: %w", , )
	}

	if  ==  {
		return true, nil
	}

	 := make([], 0)
	 := make(map[]struct{})

	 = append(, )
	for len() > 0 {
		 := [len()-1]
		 = [:len()-1]

		if ,  := []; ! {
			// If the adjacent vertex also is the target vertex, the target is a
			// parent of the source vertex. An edge would introduce a cycle.
			if  ==  {
				return true, nil
			}

			[] = struct{}{}

			for  := range .inEdges[] {
				 = append(, )
			}
		}
	}

	return false, nil
}