#include "PersistentStorage.h"

#include <queue>
#include <sstream>

#include "AccessKind.h"
#include "ApplicationSettings.h"
#include "ElementComponentKind.h"
#include "FileInfo.h"
#include "FilePath.h"
#include "Graph.h"
#include "MessageErrorCountUpdate.h"
#include "MessageStatus.h"
#include "NodeTypeSet.h"
#include "ParseLocation.h"
#include "SourceLocationCollection.h"
#include "SourceLocationFile.h"
#include "TextAccess.h"
#include "TextCodec.h"
#include "TimeStamp.h"
#include "TokenComponentAccess.h"
#include "TokenComponentBundledEdges.h"
#include "TokenComponentFilePath.h"
#include "TokenComponentInheritanceChain.h"
#include "TokenComponentIsAmbiguous.h"
#include "UnorderedCache.h"
#include "logging.h"
#include "tracing.h"
#include "utility.h"
#include "utilityApp.h"

PersistentStorage::PersistentStorage(const FilePath& dbPath, const FilePath& bookmarkPath)
	: m_sqliteIndexStorage(dbPath), m_sqliteBookmarkStorage(bookmarkPath)
{
	m_commandIndex.addNode(0, SearchMatch::getCommandName(SearchMatch::COMMAND_ALL));
	m_commandIndex.addNode(0, SearchMatch::getCommandName(SearchMatch::COMMAND_ERROR));
	m_commandIndex.addNode(0, SearchMatch::getCommandName(SearchMatch::COMMAND_LEGEND));

	for (const NodeType& nodeType: NodeTypeSet::all().getNodeTypes())
	{
		if (nodeType.hasSearchFilter())
		{
			m_commandIndex.addNode(0, nodeType.getReadableTypeWString());
		}
	}

	m_commandIndex.finishSetup();
}

std::pair<Id, bool> PersistentStorage::addNode(const StorageNodeData& data)
{
	return std::make_pair(m_sqliteIndexStorage.addNode(data), true);
}

std::vector<Id> PersistentStorage::addNodes(const std::vector<StorageNode>& nodes)
{
	return m_sqliteIndexStorage.addNodes(nodes);
}

void PersistentStorage::addSymbol(const StorageSymbol& data)
{
	m_sqliteIndexStorage.addSymbol(data);
}

void PersistentStorage::addSymbols(const std::vector<StorageSymbol>& symbols)
{
	m_sqliteIndexStorage.addSymbols(symbols);
}

void PersistentStorage::addFile(const StorageFile& data)
{
	const StorageFile storedFile = m_sqliteIndexStorage.getFirstById<StorageFile>(data.id);

	if (storedFile.id == 0)
	{
		m_sqliteIndexStorage.addFile(data);
	}
	else
	{
		if (!storedFile.indexed && data.indexed)
		{
			m_sqliteIndexStorage.setFileIndexed(storedFile.id, data.indexed);
		}

		if (storedFile.complete != data.complete)
		{
			m_sqliteIndexStorage.setFileCompleteIfNoError(
				storedFile.id, storedFile.filePath, data.complete);
		}
	}
}

Id PersistentStorage::addEdge(const StorageEdgeData& data)
{
	return m_sqliteIndexStorage.addEdge(data);
}

std::vector<Id> PersistentStorage::addEdges(const std::vector<StorageEdge>& edges)
{
	return m_sqliteIndexStorage.addEdges(edges);
}

Id PersistentStorage::addLocalSymbol(const StorageLocalSymbolData& data)
{
	return m_sqliteIndexStorage.addLocalSymbol(data);
}

std::vector<Id> PersistentStorage::addLocalSymbols(const std::set<StorageLocalSymbol>& symbols)
{
	return m_sqliteIndexStorage.addLocalSymbols(symbols);
}

Id PersistentStorage::addSourceLocation(const StorageSourceLocationData& data)
{
	return m_sqliteIndexStorage.addSourceLocation(data);
}

std::vector<Id> PersistentStorage::addSourceLocations(const std::vector<StorageSourceLocation>& locations)
{
	return m_sqliteIndexStorage.addSourceLocations(locations);
}

void PersistentStorage::addOccurrence(const StorageOccurrence& data)
{
	m_sqliteIndexStorage.addOccurrence(data);
}

void PersistentStorage::addOccurrences(const std::vector<StorageOccurrence>& occurrences)
{
	m_sqliteIndexStorage.addOccurrences(occurrences);
}

void PersistentStorage::addComponentAccess(const StorageComponentAccess& componentAccess)
{
	m_sqliteIndexStorage.addComponentAccess(componentAccess);
}

void PersistentStorage::addComponentAccesses(const std::vector<StorageComponentAccess>& componentAccesses)
{
	m_sqliteIndexStorage.addComponentAccesses(componentAccesses);
}

void PersistentStorage::addElementComponent(const StorageElementComponent& component)
{
	m_sqliteIndexStorage.addElementComponent(component);
}

void PersistentStorage::addElementComponents(const std::vector<StorageElementComponent>& components)
{
	m_sqliteIndexStorage.addElementComponents(components);
}

Id PersistentStorage::addError(const StorageErrorData& data)
{
	return m_sqliteIndexStorage.addError(data).id;
}

void PersistentStorage::removeElement(const Id id)
{
	m_sqliteIndexStorage.removeElement(id);
}

void PersistentStorage::removeElements(const std::vector<Id>& ids)
{
	m_sqliteIndexStorage.removeElements(ids);
}

void PersistentStorage::removeOccurrence(const StorageOccurrence& occurrence)
{
	m_sqliteIndexStorage.removeOccurrence(occurrence);
}

void PersistentStorage::removeOccurrences(const std::vector<StorageOccurrence>& occurrences)
{
	m_sqliteIndexStorage.removeOccurrences(occurrences);
}

void PersistentStorage::removeElementsWithoutOccurrences(const std::vector<Id>& elementIds)
{
	m_sqliteIndexStorage.removeElementsWithoutOccurrences(elementIds);
}

const std::vector<StorageNode>& PersistentStorage::getStorageNodes() const
{
	return m_storageData.nodes = m_sqliteIndexStorage.getAll<StorageNode>();
}

const std::vector<StorageFile>& PersistentStorage::getStorageFiles() const
{
	return m_storageData.files = m_sqliteIndexStorage.getAll<StorageFile>();
}

const std::vector<StorageSymbol>& PersistentStorage::getStorageSymbols() const
{
	return m_storageData.symbols = m_sqliteIndexStorage.getAll<StorageSymbol>();
}

const std::vector<StorageEdge>& PersistentStorage::getStorageEdges() const
{
	return m_storageData.edges = m_sqliteIndexStorage.getAll<StorageEdge>();
}

const std::set<StorageLocalSymbol>& PersistentStorage::getStorageLocalSymbols() const
{
	return m_storageData.locals = utility::toSet(m_sqliteIndexStorage.getAll<StorageLocalSymbol>());
}

const std::set<StorageSourceLocation>& PersistentStorage::getStorageSourceLocations() const
{
	return m_storageData.locations = utility::toSet(
			   m_sqliteIndexStorage.getAll<StorageSourceLocation>());
}

const std::set<StorageOccurrence>& PersistentStorage::getStorageOccurrences() const
{
	return m_storageData.occurrences = utility::toSet(
			   m_sqliteIndexStorage.getAll<StorageOccurrence>());
}

const std::set<StorageComponentAccess>& PersistentStorage::getComponentAccesses() const
{
	return m_storageData.accesses = utility::toSet(
			   m_sqliteIndexStorage.getAll<StorageComponentAccess>());
}

const std::set<StorageElementComponent>& PersistentStorage::getElementComponents() const
{
	return m_storageData.components = utility::toSet(
			   m_sqliteIndexStorage.getAll<StorageElementComponent>());
}

const std::vector<StorageError>& PersistentStorage::getErrors() const
{
	std::vector<StorageError> errors;
	for (const StorageError& error: m_sqliteIndexStorage.getAll<StorageError>())
	{
		errors.emplace_back(error);
	}
	return m_storageData.errors = errors;
}

void PersistentStorage::startInjection()
{
	beforeErrorRecording();

	m_sqliteIndexStorage.beginTransaction();
}

void PersistentStorage::finishInjection()
{
	m_sqliteIndexStorage.commitTransaction();

	afterErrorRecording();
}

void PersistentStorage::rollbackInjection()
{
	m_sqliteIndexStorage.rollbackTransaction();

	afterErrorRecording();
}

const std::vector<ErrorInfo> PersistentStorage::getErrorInfos() const
{
	return m_sqliteIndexStorage.getAllErrorInfos();
}

void PersistentStorage::beforeErrorRecording()
{
	m_preInjectionErrorCount = m_sqliteIndexStorage.getErrorCount();

	if (!m_preIndexingErrorCountSet)
	{
		m_preIndexingErrorCount = m_preInjectionErrorCount;
		m_preIndexingErrorCountSet = true;
	}
}

void PersistentStorage::afterErrorRecording()
{
	std::vector<ErrorInfo> errors = getErrorInfos();
	if (m_preInjectionErrorCount < errors.size())
	{
		ErrorCountInfo errorCount(errors);
		errors.erase(
			errors.begin(), errors.begin() + m_preInjectionErrorCount - m_preIndexingErrorCount);
		MessageErrorCountUpdate(errorCount, errors).dispatch();
		m_preIndexingErrorCount = 0;
	}
}

void PersistentStorage::setMode(const SqliteIndexStorage::StorageModeType mode)
{
	m_sqliteIndexStorage.setMode(mode);
}

FilePath PersistentStorage::getIndexDbFilePath() const
{
	return m_sqliteIndexStorage.getDbFilePath();
}

FilePath PersistentStorage::getBookmarkDbFilePath() const
{
	return m_sqliteBookmarkStorage.getDbFilePath();
}

bool PersistentStorage::isEmpty() const
{
	return m_sqliteIndexStorage.isEmpty();
}

bool PersistentStorage::isIncompatible() const
{
	return m_sqliteIndexStorage.isIncompatible();
}

std::string PersistentStorage::getProjectSettingsText() const
{
	return m_sqliteIndexStorage.getProjectSettingsText();
}

void PersistentStorage::setProjectSettingsText(std::string text)
{
	m_sqliteIndexStorage.setProjectSettingsText(text);
}

void PersistentStorage::setup()
{
	m_sqliteIndexStorage.setup();
	m_sqliteBookmarkStorage.setup();

	m_sqliteBookmarkStorage.migrateIfNecessary();
}

void PersistentStorage::updateVersion()
{
	m_sqliteIndexStorage.setVersion(m_sqliteIndexStorage.getStaticVersion());
	if (m_sqliteBookmarkStorage.isEmpty())
	{
		m_sqliteBookmarkStorage.setVersion(m_sqliteBookmarkStorage.getStaticVersion());
	}
}

void PersistentStorage::clear()
{
	m_sqliteIndexStorage.clear();

	clearCaches();
}

void PersistentStorage::clearCaches()
{
	m_symbolIndex.clear();
	m_fileIndex.clear();

	m_fileNodeIds.clear();
	m_lowerCasefileNodeIds.clear();
	m_fileNodePaths.clear();
	m_fileNodeComplete.clear();
	m_fileNodeIndexed.clear();
	m_fileNodeLanguage.clear();
	m_symbolDefinitionKinds.clear();

	m_hierarchyCache.clear();
	m_fullTextSearchIndex.clear();
	m_fullTextSearchCodec = "";
}

std::set<FilePath> PersistentStorage::getReferenced(const std::set<FilePath>& filePaths) const
{
	TRACE();
	std::set<FilePath> referenced;

	utility::append(referenced, getReferencedByIncludes(filePaths));
	utility::append(referenced, getReferencedByImports(filePaths));

	return referenced;
}

std::set<FilePath> PersistentStorage::getReferencing(const std::set<FilePath>& filePaths) const
{
	TRACE();
	std::set<FilePath> referencing;

	utility::append(referencing, getReferencingByIncludes(filePaths));
	utility::append(referencing, getReferencingByImports(filePaths));

	return referencing;
}

void PersistentStorage::clearAllErrors()
{
	TRACE();

	m_sqliteIndexStorage.removeAllErrors();
}

void PersistentStorage::clearFileElements(
	const std::vector<FilePath>& filePaths, std::function<void(int)> updateStatusCallback)
{
	TRACE();

	std::vector<Id> fileNodeIds;
	for (const StorageFile& file: m_sqliteIndexStorage.getFilesByPaths(filePaths))
	{
		fileNodeIds.push_back(file.id);
	}

	if (!fileNodeIds.empty())
	{
		m_sqliteIndexStorage.beginTransaction();
		m_sqliteIndexStorage.removeElementsWithLocationInFiles(fileNodeIds, updateStatusCallback);
		m_sqliteIndexStorage.removeElements(fileNodeIds);
		m_sqliteIndexStorage.commitTransaction();
		updateStatusCallback(100);
	}
}

std::vector<FileInfo> PersistentStorage::getFileInfoForAllFiles() const
{
	TRACE();

	std::vector<FileInfo> fileInfos;

	m_sqliteIndexStorage.forEach<StorageFile>([&](StorageFile&& file) {
		boost::posix_time::ptime modificationTime = boost::posix_time::not_a_date_time;
		if (file.modificationTime != "not-a-date-time")
		{
			modificationTime = boost::posix_time::time_from_string(file.modificationTime);
		}

		fileInfos.emplace_back(FilePath(file.filePath), modificationTime);
	});

	return fileInfos;
}

std::set<FilePath> PersistentStorage::getIncompleteFiles() const
{
	TRACE();

	std::set<FilePath> incompleteFiles;
	for (auto p: m_fileNodeComplete)
	{
		if (p.second == false)
		{
			incompleteFiles.insert(getFileNodePath(p.first));
		}
	}

	return incompleteFiles;
}

bool PersistentStorage::getFilePathIndexed(const FilePath& path) const
{
	Id fileId = getFileNodeId(path);
	if (fileId)
	{
		return getFileNodeIndexed(fileId);
	}

	return false;
}

void PersistentStorage::buildCaches()
{
	TRACE();

	clearCaches();

	buildFilePathMaps();
	buildSearchIndex();
	buildMemberEdgeIdOrderMap();
	buildHierarchyCache();
}

void PersistentStorage::optimizeMemory()
{
	TRACE();

	m_sqliteIndexStorage.setTime();
	m_sqliteIndexStorage.optimizeMemory();

	m_sqliteBookmarkStorage.optimizeMemory();
}

Id PersistentStorage::getNodeIdForFileNode(const FilePath& filePath) const
{
	return getFileNodeId(filePath);
}

Id PersistentStorage::getNodeIdForNameHierarchy(const NameHierarchy& nameHierarchy) const
{
	return m_sqliteIndexStorage.getNodeBySerializedName(NameHierarchy::serialize(nameHierarchy)).id;
}

std::vector<Id> PersistentStorage::getNodeIdsForNameHierarchies(
	const std::vector<NameHierarchy> nameHierarchies) const
{
	std::vector<Id> nodeIds;
	for (const NameHierarchy& name: nameHierarchies)
	{
		Id nodeId = getNodeIdForNameHierarchy(name);
		if (nodeId)
		{
			nodeIds.push_back(nodeId);
		}
	}
	return nodeIds;
}

NameHierarchy PersistentStorage::getNameHierarchyForNodeId(Id nodeId) const
{
	TRACE();

	return NameHierarchy::deserialize(
		m_sqliteIndexStorage.getFirstById<StorageNode>(nodeId).serializedName);
}

std::vector<NameHierarchy> PersistentStorage::getNameHierarchiesForNodeIds(
	const std::vector<Id>& nodeIds) const
{
	TRACE();

	std::vector<NameHierarchy> nameHierarchies;
	for (const StorageNode& storageNode: m_sqliteIndexStorage.getAllByIds<StorageNode>(nodeIds))
	{
		nameHierarchies.push_back(NameHierarchy::deserialize(storageNode.serializedName));
	}
	return nameHierarchies;
}

std::map<Id, std::pair<Id, NameHierarchy>> PersistentStorage::getNodeIdToParentFileMap(
	const std::vector<Id>& nodeIds) const
{
	std::map<Id, std::pair<Id, NameHierarchy>> nodeIdToParentFileMap;

	std::shared_ptr<SourceLocationCollection> locations =
		m_sqliteIndexStorage.getSourceLocationsForElementIds(nodeIds);

	// prefer scope locations if available
	locations->forEachSourceLocation([this, &nodeIdToParentFileMap](SourceLocation* location) {
		if (location->isStartLocation() && location->isScopeLocation())
		{
			for (Id tokenId: location->getTokenIds())
			{
				nodeIdToParentFileMap.emplace(
					tokenId,
					std::make_pair(
						getFileNodeId(location->getFilePath()),
						NameHierarchy(location->getFilePath().wstr(), NAME_DELIMITER_FILE)));
			}
		}
	});

	// fill in missing ones
	locations->forEachSourceLocation([this, &nodeIdToParentFileMap](SourceLocation* location) {
		if (!location->isStartLocation())
		{
			return;
		}

		for (Id tokenId: location->getTokenIds())
		{
			if (nodeIdToParentFileMap.find(tokenId) == nodeIdToParentFileMap.end())
			{
				nodeIdToParentFileMap.emplace(
					tokenId,
					std::make_pair(
						getFileNodeId(location->getFilePath()),
						NameHierarchy(location->getFilePath().wstr(), NAME_DELIMITER_FILE)));
			}
		}
	});

	return nodeIdToParentFileMap;
}

NodeType PersistentStorage::getNodeTypeForNodeWithId(Id nodeId) const
{
	return NodeType(intToNodeKind(m_sqliteIndexStorage.getFirstById<StorageNode>(nodeId).type));
}

StorageEdge PersistentStorage::getEdgeById(Id edgeId) const
{
	return m_sqliteIndexStorage.getEdgeById(edgeId);
}

std::shared_ptr<SourceLocationCollection> PersistentStorage::getFullTextSearchLocations(
	const std::wstring& searchTerm, bool caseSensitive) const
{
	TRACE();

	std::shared_ptr<SourceLocationCollection> collection =
		std::make_shared<SourceLocationCollection>();
	if (searchTerm.empty())
	{
		return collection;
	}

	const TextCodec codec(ApplicationSettings::getInstance()->getTextEncoding());
	{
		std::lock_guard<std::mutex> lock(m_fullTextSearchMutex);

		if (m_fullTextSearchCodec != codec.getName())
		{
			MessageStatus(L"Building fulltext search index", false, true).dispatch();
			buildFullTextSearchIndex();
		}
	}

	MessageStatus(
		std::wstring(L"Searching fulltext (case-") +
			(caseSensitive ? L"sensitive" : L"insensitive") + L"): " + searchTerm,
		false,
		true)
		.dispatch();

	{
		std::vector<std::shared_ptr<std::thread>> threads;
		std::mutex collectionMutex;
		for (std::vector<FullTextSearchResult> fileResults: utility::splitToEquallySizedParts(
				 m_fullTextSearchIndex.searchForTerm(searchTerm), utility::getIdealThreadCount()))
		{
			std::shared_ptr<std::thread> thread = std::make_shared<std::thread>(
				[this,
				 &searchTerm,
				 &caseSensitive,
				 &codec,
				 /*no ref here!*/ fileResults,
				 &collection,
				 &collectionMutex]() {
					const int termLength = static_cast<int>(searchTerm.length());
					for (const FullTextSearchResult& fileResult: fileResults)
					{
						const FilePath filePath = getFileNodePath(fileResult.fileId);
						std::shared_ptr<TextAccess> fileContent = getFileContent(filePath, false);

						int charsTotal = 0;
						int lineNumber = 1;
						std::wstring line = codec.decode(fileContent->getLine(lineNumber));

						for (int pos: fileResult.positions)
						{
							while (charsTotal + (int)line.length() <= pos)
							{
								charsTotal += static_cast<int>(line.length());
								lineNumber++;
								line = codec.decode(fileContent->getLine(lineNumber));
							}

							ParseLocation location;
							location.startLineNumber = lineNumber;
							location.startColumnNumber = pos - charsTotal + 1;

							if (caseSensitive &&
								line.substr(location.startColumnNumber - 1, termLength) != searchTerm)
							{
								continue;
							}
							while ((charsTotal + (int)line.length()) < pos + termLength)
							{
								charsTotal += static_cast<int>(line.length());
								lineNumber++;
								line = codec.decode(fileContent->getLine(lineNumber));
							}
							location.endLineNumber = lineNumber;
							location.endColumnNumber = pos + termLength - charsTotal;

							{
								std::lock_guard<std::mutex> lock(collectionMutex);
								// Set first bit to 1 to avoid collisions
								const Id locationId = ~(~Id(0) >> 1) +
									collection->getSourceLocationCount() + 1;
								collection->addSourceLocation(
									LOCATION_FULLTEXT_SEARCH,
									locationId,
									std::vector<Id>(),
									filePath,
									location.startLineNumber,
									location.startColumnNumber,
									location.endLineNumber,
									location.endColumnNumber);
							}
						}
					}
				});
			threads.push_back(thread);
		}

		for (std::shared_ptr<std::thread> thread: threads)
		{
			thread->join();
		}
	}

	addCompleteFlagsToSourceLocationCollection(collection.get());

	MessageStatus(
		std::to_wstring(collection->getSourceLocationCount()) + L" results in " +
			std::to_wstring(collection->getSourceLocationFileCount()) +
			L" files for fulltext search (case-" + (caseSensitive ? L"sensitive" : L"insensitive") +
			L"): " + searchTerm,
		false,
		false)
		.dispatch();

	return collection;
}

std::vector<SearchMatch> PersistentStorage::getAutocompletionMatches(
	const std::wstring& query, NodeTypeSet acceptedNodeTypes, bool acceptCommands) const
{
	TRACE();

	// search in indices
	const size_t maxResultsCount = static_cast<size_t>(std::pow(3, query.size() + 3));
	const size_t maxBestScoredResultsLength = 100;
	const size_t maxMatchesReturned = 1000;

	// create SearchMatches
	std::vector<SearchMatch> matches;

	if (!acceptedNodeTypes
			 .getWithMatchingRemoved([](const NodeType& type) { return type.isFile(); })
			 .isEmpty())
	{
		matches = getAutocompletionSymbolMatches(
			query, acceptedNodeTypes, maxResultsCount, maxBestScoredResultsLength);
	}

	if (acceptedNodeTypes.containsMatching([](const NodeType& type) { return type.isFile(); }))
	{
		utility::append(matches, getAutocompletionFileMatches(query, maxResultsCount));
	}

	if (acceptCommands)
	{
		utility::append(matches, getAutocompletionCommandMatches(query, acceptedNodeTypes));
	}

	// Rescore search matches to check if better score is achieved with higher indices
	std::vector<SearchMatch> rescoredMatches;
	rescoredMatches.reserve(matches.size());

	for (SearchMatch match: matches)
	{
		// rescore match
		if (!match.subtext.empty() && match.indices.size())
		{
			SearchResult newResult = SearchIndex::rescoreText(
				match.name, match.text, match.indices, match.score, maxBestScoredResultsLength);

			match.score = newResult.score;
			match.indices = std::move(newResult.indices);
		}

		rescoredMatches.emplace_back(match);
	}

	// Score child symbol matches with same score as parent lower
	const SearchMatch* lastMatch = nullptr;
	std::set<SearchMatch> matchesSet;

	for (SearchMatch match: rescoredMatches)
	{
		if (lastMatch == nullptr || !utility::isPrefix(lastMatch->name, match.name))
		{
			lastMatch = &match;
		}
		else if (lastMatch->score == match.score && match.tokenNames.size())
		{
			size_t lastSize = lastMatch->name.size();
			if (match.name.find(match.tokenNames[0].getDelimiter(), lastSize) == lastSize)
			{
				match.score -= 10;
			}
			else
			{
				lastMatch = &match;
			}
		}

		matchesSet.insert(match);
	}

	if (matchesSet.size() > maxMatchesReturned)
	{
		auto it = matchesSet.begin();
		std::advance(it, maxMatchesReturned);
		matches = std::vector<SearchMatch>(matchesSet.begin(), it);
	}
	else
	{
		matches = utility::toVector(matchesSet);
	}

	// for (auto a : matches)
	// {
	// 	std::wcout << a.score << " " << a.name << std::endl;
	// }

	return matches;
}

std::vector<SearchMatch> PersistentStorage::getAutocompletionSymbolMatches(
	const std::wstring& query,
	const NodeTypeSet& acceptedNodeTypes,
	size_t maxResultsCount,
	size_t maxBestScoredResultsLength) const
{
	// search in indices
	const std::vector<SearchResult> results = m_symbolIndex.search(
		query, acceptedNodeTypes, maxResultsCount, maxBestScoredResultsLength);

	// fetch StorageNodes for node ids
	std::map<Id, StorageNode> storageNodeMap;
	{
		std::vector<Id> elementIds;

		for (const SearchResult& result: results)
		{
			elementIds.insert(elementIds.end(), result.elementIds.begin(), result.elementIds.end());
		}

		for (const StorageNode& node: m_sqliteIndexStorage.getAllByIds<StorageNode>(elementIds))
		{
			storageNodeMap.emplace(node.id, node);
		}
	}

	// create SearchMatches
	std::vector<SearchMatch> matches;
	for (const SearchResult& result: results)
	{
		SearchMatch match;
		const StorageNode* firstNode = nullptr;

		for (const Id& elementId: result.elementIds)
		{
			if (elementId != 0)
			{
				StorageNode* node = &storageNodeMap[elementId];

				match.tokenIds.push_back(elementId);
				match.tokenNames.push_back(NameHierarchy::deserialize(node->serializedName));

				if (!match.hasChildren &&
					acceptedNodeTypes ==
						NodeTypeSet::all())	   // TODO: check if node types of children match
				{
					match.hasChildren = m_hierarchyCache.nodeHasChildren(elementId);
				}

				if (!firstNode)
				{
					firstNode = node;
				}
			}
		}

		if (!firstNode)
		{
			continue;
		}

		match.name = result.text;
		match.text = result.text;

		NameHierarchy name = NameHierarchy::deserialize(firstNode->serializedName);
		if (name.getQualifiedName() == match.name)
		{
			const size_t idx = m_hierarchyCache.getIndexOfLastVisibleParentNode(firstNode->id);
			if (idx != 0)
			{
				match.text = name.getRange(idx, name.size()).getQualifiedName();
				match.subtext = name.getRange(0, idx).getQualifiedName();
			}
		}

		match.indices = result.indices;
		match.score = result.score;
		match.nodeType = NodeType(intToNodeKind(firstNode->type));
		match.typeName = match.nodeType.getReadableTypeWString();
		match.searchType = SearchMatch::SEARCH_TOKEN;

		if (m_symbolDefinitionKinds.find(firstNode->id) == m_symbolDefinitionKinds.end())
		{
			match.typeName = L"non-indexed " + match.typeName;
		}

		matches.push_back(match);
	}

	return matches;
}

std::vector<SearchMatch> PersistentStorage::getAutocompletionFileMatches(
	const std::wstring& query, size_t maxResultsCount) const
{
	const std::vector<SearchResult> results = m_fileIndex.search(
		query,
		NodeTypeSet::all().getWithMatchingKept([](const NodeType& type) { return type.isFile(); }),
		maxResultsCount,
		100);

	// create SearchMatches
	std::vector<SearchMatch> matches;
	for (const SearchResult& result: results)
	{
		SearchMatch match;

		match.name = result.text;

		match.text = FilePath(match.name).fileName();
		match.subtext = match.name;

		match.tokenIds = result.elementIds;
		for (Id tokenId: match.tokenIds)
		{
			match.tokenNames.push_back(
				NameHierarchy(getFileNodePath(tokenId).wstr(), NAME_DELIMITER_FILE));
		}

		if (!match.tokenNames.size())
		{
			match.tokenNames.push_back(NameHierarchy(match.name, NAME_DELIMITER_FILE));
		}

		match.indices = result.indices;
		match.score = result.score;

		match.nodeType = NodeType(NODE_FILE);
		match.typeName = match.nodeType.getReadableTypeWString();

		match.searchType = SearchMatch::SEARCH_TOKEN;

		matches.push_back(match);
	}

	return matches;
}

std::vector<SearchMatch> PersistentStorage::getAutocompletionCommandMatches(
	const std::wstring& query, NodeTypeSet acceptedNodeTypes) const
{
	// search in indices
	const std::vector<SearchResult> results = m_commandIndex.search(query, NodeTypeSet::all(), 0);

	// create SearchMatches
	std::vector<SearchMatch> matches;
	for (const SearchResult& result: results)
	{
		SearchMatch match;

		match.name = result.text;
		match.text = result.text;

		match.indices = result.indices;
		match.score = result.score;

		match.searchType = SearchMatch::SEARCH_COMMAND;
		match.typeName = L"command";

		if (match.getCommandType() == SearchMatch::COMMAND_NODE_FILTER)
		{
			match.nodeType = NodeType(getNodeKindForReadableNodeKindString(match.name));
			match.typeName = L"filter";
		}

		if (acceptedNodeTypes == NodeTypeSet::all() ||
			(match.getCommandType() == SearchMatch::COMMAND_NODE_FILTER &&
			 !(acceptedNodeTypes.contains(match.nodeType))))
		{
			matches.push_back(match);
		}
	}

	return matches;
}

std::vector<SearchMatch> PersistentStorage::getSearchMatchesForTokenIds(
	const std::vector<Id>& elementIds) const
{
	TRACE();

	// todo: what if all these elements share the same node in the searchindex?
	// In that case there should be only one search match.
	std::vector<SearchMatch> matches;

	// fetch StorageNodes for node ids
	std::map<Id, StorageNode> storageNodeMap;
	for (StorageNode& node: m_sqliteIndexStorage.getAllByIds<StorageNode>(elementIds))
	{
		storageNodeMap.emplace(node.id, node);
	}

	for (Id elementId: elementIds)
	{
		if (storageNodeMap.find(elementId) == storageNodeMap.end())
		{
			continue;
		}

		StorageNode node = storageNodeMap[elementId];

		SearchMatch match;
		const NameHierarchy nameHierarchy = NameHierarchy::deserialize(node.serializedName);
		match.name = nameHierarchy.getQualifiedName();
		match.text = nameHierarchy.getRawName();

		match.tokenIds.push_back(elementId);
		match.tokenNames.push_back(nameHierarchy);
		match.nodeType = NodeType(intToNodeKind(node.type));
		match.searchType = SearchMatch::SEARCH_TOKEN;

		if (match.nodeType.isFile())
		{
			match.text = FilePath(match.text).fileName();
		}

		matches.push_back(match);
	}

	return matches;
}

std::shared_ptr<Graph> PersistentStorage::getGraphForAll() const
{
	TRACE();

	std::shared_ptr<Graph> graph = std::make_shared<Graph>();
	const size_t sdk_size = m_symbolDefinitionKinds.size();
	m_sqliteIndexStorage.forEach<StorageNode>([&, sdk_size](StorageNode&& storageNode) {
		const NodeType type(intToNodeKind(storageNode.type));
		if (type.isFile())
		{
			auto fn_it = m_fileNodeIndexed.find(storageNode.id);
			if (fn_it != m_fileNodeIndexed.end() && fn_it->second)
			{
				addFileNodeToGraph(storageNode, graph.get());
			}
		}
		else
		{
			bool showNode = true;
			if (sdk_size)
			{
				auto it = m_symbolDefinitionKinds.find(storageNode.id);
				showNode = (it != m_symbolDefinitionKinds.end() && it->second == DEFINITION_EXPLICIT);
			}
			if (showNode &&
				(type.isPackage() ||
				 !m_hierarchyCache.isChildOfVisibleNodeOrInvisible(storageNode.id)))
			{
				addNodeToGraph(storageNode, type, graph.get(), false);
			}
		}
	});
	return graph;
}

std::shared_ptr<Graph> PersistentStorage::getGraphForNodeTypes(NodeTypeSet nodeTypes) const
{
	TRACE();

	std::vector<Id> tokenIds;

	m_sqliteIndexStorage.forEach<StorageNode>([&](StorageNode&& node) {
		if (nodeTypes.contains(NodeType(intToNodeKind(node.type))))
		{
			auto it = m_symbolDefinitionKinds.find(node.id);
			if (it != m_symbolDefinitionKinds.end() && it->second == DEFINITION_EXPLICIT)
			{
				tokenIds.push_back(node.id);
			}
		}
	});

	if (nodeTypes.containsMatching([](const NodeType& type) { return type.isFile(); }))
	{
		for (const auto& p: m_fileNodePaths)
		{
			tokenIds.push_back(p.first);
		}
	}

	std::shared_ptr<Graph> graph = std::make_shared<Graph>();
	addNodesWithParentsAndEdgesToGraph(tokenIds, std::vector<Id>(), graph.get(), false);

	return graph;
}

std::shared_ptr<Graph> PersistentStorage::getGraphForActiveTokenIds(
	const std::vector<Id>& tokenIds, const std::vector<Id>& expandedNodeIds, bool* isActiveNamespace) const
{
	TRACE();

	std::vector<Id> ids(tokenIds);
	bool isPackage = false;

	std::vector<Id> nodeIds;
	std::vector<Id> edgeIds;

	bool addBundledEdges = false;
	std::vector<StorageEdge> edgesToBundle;

	bool addFileContents = false;

	if (tokenIds.size() == 1)
	{
		const Id elementId = tokenIds[0];
		const StorageNode node = m_sqliteIndexStorage.getFirstById<StorageNode>(elementId);

		if (node.id > 0)
		{
			const NodeType nodeType(intToNodeKind(node.type));
			if (nodeType.isPackage())
			{
				ids.clear();
				m_hierarchyCache.addFirstChildIdsForNodeId(elementId, &ids, &edgeIds);
				edgeIds.clear();

				isPackage = true;
			}
			else
			{
				m_hierarchyCache.addFirstChildIdsForNodeId(elementId, &nodeIds, &edgeIds);

				// don't expand active node if it has too many child nodes
				if (nodeIds.size() > 100 && nodeType.isCollapsible())
				{
					nodeIds.clear();
				}

				nodeIds.push_back(elementId);
				edgeIds.clear();

				for (const StorageEdge& edge:
					 m_sqliteIndexStorage.getEdgesBySourceOrTargetId(elementId))
				{
					Edge::EdgeType edgeType = Edge::intToType(edge.type);
					if (edgeType == Edge::EDGE_MEMBER)
					{
						continue;
					}

					if (nodeType.isUsable() && (edgeType & Edge::EDGE_TYPE_USAGE) &&
						m_hierarchyCache.isChildOfVisibleNodeOrInvisible(edge.sourceNodeId) &&
						(m_hierarchyCache.getLastVisibleParentNodeId(edge.targetNodeId) !=
						 m_hierarchyCache.getLastVisibleParentNodeId(edge.sourceNodeId)))
					{
						edgesToBundle.push_back(edge);
					}
					else
					{
						edgeIds.push_back(edge.id);
					}
				}

				if (nodeType.isFile())
				{
					addFileContents = true;
				}
				else
				{
					addBundledEdges = true;
				}
			}
		}
		else if (m_sqliteIndexStorage.isEdge(elementId))
		{
			edgeIds.push_back(elementId);
		}
	}

	if (ids.size() >= 1 || isPackage)
	{
		std::set<Id> symbolIds;
		for (const StorageSymbol& symbol: m_sqliteIndexStorage.getAllByIds<StorageSymbol>(ids))
		{
			if (symbol.id > 0 &&
				(!isPackage || intToDefinitionKind(symbol.definitionKind) != DEFINITION_IMPLICIT))
			{
				nodeIds.push_back(symbol.id);
			}
			symbolIds.insert(symbol.id);
		}
		for (const StorageNode& node: m_sqliteIndexStorage.getAllByIds<StorageNode>(ids))
		{
			if (symbolIds.find(node.id) == symbolIds.end())
			{
				nodeIds.push_back(node.id);
			}
		}

		if (!isPackage)
		{
			if (nodeIds.size() != ids.size())
			{
				for (const StorageEdge& edge: m_sqliteIndexStorage.getAllByIds<StorageEdge>(ids))
				{
					if (edge.id > 0)
					{
						edgeIds.push_back(edge.id);
					}
				}
			}
		}
	}

	std::shared_ptr<Graph> g = std::make_shared<Graph>();
	Graph* graph = g.get();

	if (isPackage)
	{
		addNodesToGraph(nodeIds, graph, false);
	}
	else
	{
		addNodesWithParentsAndEdgesToGraph(nodeIds, edgeIds, graph, true);
	}

	if (addBundledEdges)
	{
		addBundledEdgesToGraph(tokenIds[0], edgesToBundle, graph);
	}
	else if (addFileContents)
	{
		addFileContentsToGraph(tokenIds[0], graph);
	}

	if (!isPackage)
	{
		std::vector<Id> expandedChildIds;
		std::vector<Id> expandedChildEdgeIds;

		for (Id nodeId: expandedNodeIds)
		{
			if (graph->getNodeById(nodeId))
			{
				m_hierarchyCache.addFirstChildIdsForNodeId(
					nodeId, &expandedChildIds, &expandedChildEdgeIds);
			}
		}

		if (expandedChildIds.size())
		{
			addNodesToGraph(expandedChildIds, graph, true);
			addEdgesToGraph(expandedChildEdgeIds, graph);
		}

		addInheritanceChainsToGraph(nodeIds, graph);
	}

	addComponentAccessToGraph(graph);
	addComponentIsAmbiguousToGraph(graph);

	if (isActiveNamespace)
	{
		*isActiveNamespace = isPackage;
	}

	return g;
}

std::shared_ptr<Graph> PersistentStorage::getGraphForChildrenOfNodeId(Id nodeId) const
{
	TRACE();

	std::vector<Id> nodeIds;
	std::vector<Id> edgeIds;

	nodeIds.push_back(nodeId);
	m_hierarchyCache.addFirstChildIdsForNodeId(nodeId, &nodeIds, &edgeIds);

	std::shared_ptr<Graph> graph = std::make_shared<Graph>();
	addNodesToGraph(nodeIds, graph.get(), true);
	addEdgesToGraph(edgeIds, graph.get());

	addComponentAccessToGraph(graph.get());
	return graph;
}

std::shared_ptr<Graph> PersistentStorage::getGraphForTrail(
	Id originId,
	Id targetId,
	NodeKindMask nodeTypes,
	Edge::TypeMask edgeTypes,
	bool nodeNonIndexed,
	size_t depth,
	bool directed) const
{
	TRACE();

	std::set<Id> nodeIds;
	std::set<Id> edgeIds;

	nodeIds.insert(originId ? originId : targetId);
	bool forward = originId;
	size_t currentDepth = 0;

	std::vector<Id> nodeIdsToProcess = {*nodeIds.begin()};

	struct TrailNode
	{
		Id id = 0;
		std::set<TrailNode*> parents;
		std::set<Id> edgeIds;
	};

	bool isTerminatedTrail = originId && targetId;
	std::map<Id, TrailNode> trailNodes;

	if (isTerminatedTrail)
	{
		TrailNode root;
		root.id = originId;
		trailNodes.emplace(originId, root);
	}

	while (nodeIdsToProcess.size() && (!depth || currentDepth < depth))
	{
		std::vector<StorageEdge> edges = forward
			? m_sqliteIndexStorage.getEdgesBySourceIds(nodeIdsToProcess)
			: m_sqliteIndexStorage.getEdgesByTargetIds(nodeIdsToProcess);

		if (!directed || edgeTypes & Edge::LAYOUT_VERTICAL)
		{
			utility::append(
				edges,
				forward ? m_sqliteIndexStorage.getEdgesByTargetIds(nodeIdsToProcess)
						: m_sqliteIndexStorage.getEdgesBySourceIds(nodeIdsToProcess));
		}

		std::vector<Id> nodeIdsToCheck;
		std::map<Id, std::vector<StorageEdge>> edgesToInsert;

		for (const StorageEdge& edge: edges)
		{
			if (Edge::intToType(edge.type) & edgeTypes && edgeIds.find(edge.id) == edgeIds.end())
			{
				bool isForward = forward == !(Edge::intToType(edge.type) & Edge::LAYOUT_VERTICAL);

				const Id targetNodeId = isForward ? edge.targetNodeId : edge.sourceNodeId;
				const Id sourceNodeId = isForward ? edge.sourceNodeId : edge.targetNodeId;

				if (nodeIds.find(targetNodeId) == nodeIds.end())
				{
					nodeIdsToCheck.push_back(targetNodeId);
					edgesToInsert[targetNodeId].push_back(edge);
				}
				else if (nodeIds.find(sourceNodeId) == nodeIds.end())
				{
					if (!directed)
					{
						nodeIdsToCheck.push_back(sourceNodeId);
						edgesToInsert[sourceNodeId].push_back(edge);
					}
				}
				else
				{
					edgeIds.insert(edge.id);

					if (isTerminatedTrail)
					{
						TrailNode& target = trailNodes[targetNodeId];
						TrailNode& origin = trailNodes[sourceNodeId];
						target.id = targetNodeId;
						origin.id = sourceNodeId;
						target.parents.insert(&origin);
						target.edgeIds.insert(edge.id);
					}
				}
			}
		}

		nodeIdsToProcess.clear();

		if (nodeTypes != 0)
		{
			for (const StorageNode& node:
				 m_sqliteIndexStorage.getAllByIds<StorageNode>(nodeIdsToCheck))
			{
				NodeKind kind = intToNodeKind(node.type);
				if (kind & nodeTypes || (kind == NODE_SYMBOL && nodeNonIndexed))
				{
					if (!nodeNonIndexed)
					{
						if (kind == NODE_FILE)
						{
							auto it = m_fileNodeIndexed.find(node.id);
							if (it == m_fileNodeIndexed.end() || !it->second)
							{
								continue;
							}
						}
						else
						{
							auto it = m_symbolDefinitionKinds.find(node.id);
							if (it == m_symbolDefinitionKinds.end() || it->second == DEFINITION_NONE)
							{
								continue;
							}
						}
					}

					// FIXME: don't add namespace nodes to the graph, because it destroys trail
					// layouting Remove when namespaces are proper nodes with children
					if ((kind & (NODE_MODULE | NODE_NAMESPACE | NODE_PACKAGE)) == 0)
					{
						nodeIds.insert(node.id);
						for (const StorageEdge& edge: edgesToInsert[node.id])
						{
							if ((Edge::intToType(edge.type) & Edge::EDGE_MEMBER) == 0)
							{
								edgeIds.insert(edge.id);
							}
						}
					}
					nodeIdsToProcess.push_back(node.id);

					if (isTerminatedTrail)
					{
						TrailNode& targetNode = trailNodes[node.id];
						targetNode.id = node.id;

						for (const StorageEdge& edge: edgesToInsert[node.id])
						{
							targetNode.edgeIds.insert(edge.id);

							Id sourceNodeId =
								(edge.targetNodeId == node.id ? edge.sourceNodeId
															  : edge.targetNodeId);
							TrailNode& oldNode = trailNodes[sourceNodeId];
							targetNode.parents.insert(&oldNode);
						}
					}
				}
			}
		}
		else
		{
			for (const Id nodeId: nodeIdsToCheck)
			{
				nodeIds.insert(nodeId);
				nodeIdsToProcess.push_back(nodeId);

				for (const StorageEdge& edge: edgesToInsert[nodeId])
				{
					edgeIds.insert(edge.id);
				}
			}
		}

		edgesToInsert.clear();

		currentDepth++;
	}

	if (isTerminatedTrail)
	{
		nodeIds.clear();
		edgeIds.clear();

		if (trailNodes.find(targetId) != trailNodes.end())
		{
			std::queue<TrailNode*> nodesToProcess;
			nodesToProcess.push(&trailNodes[targetId]);

			while (nodesToProcess.size())
			{
				TrailNode* currentNode = nodesToProcess.front();
				nodesToProcess.pop();

				if (nodeIds.find(currentNode->id) != nodeIds.end())
				{
					continue;
				}

				nodeIds.insert(currentNode->id);
				edgeIds.insert(currentNode->edgeIds.begin(), currentNode->edgeIds.end());

				for (TrailNode* parent: currentNode->parents)
				{
					nodesToProcess.push(parent);
				}
			}
		}
		else
		{
			nodeIds.insert(originId);
		}
	}

	std::shared_ptr<Graph> graph = std::make_shared<Graph>();

	addNodesWithParentsAndEdgesToGraph(
		utility::toVector(nodeIds), utility::toVector(edgeIds), graph.get(), false);
	addComponentAccessToGraph(graph.get());
	addComponentIsAmbiguousToGraph(graph.get());

	return graph;
}

NodeKindMask PersistentStorage::getAvailableNodeTypes() const
{
	TRACE();

	NodeKindMask mask = 0;
	for (int type: m_sqliteIndexStorage.getAvailableNodeTypes())
	{
		mask |= intToNodeKind(type);
	}
	return mask;
}

Edge::TypeMask PersistentStorage::getAvailableEdgeTypes() const
{
	TRACE();

	Edge::TypeMask mask = 0;
	for (int type: m_sqliteIndexStorage.getAvailableEdgeTypes())
	{
		mask |= Edge::intToType(type);
	}
	return mask;
}

// TODO: rename: getActiveElementIdsForId; TODO: make separate function for declarationId
std::vector<Id> PersistentStorage::getActiveTokenIdsForId(Id tokenId, Id* declarationId) const
{
	TRACE();

	std::vector<Id> activeTokenIds;

	bool isNode = m_sqliteIndexStorage.isNode(tokenId);
	bool isEdge = m_sqliteIndexStorage.isEdge(tokenId);

	if (!isEdge && !isNode)
	{
		return activeTokenIds;
	}

	activeTokenIds.push_back(tokenId);

	if (isNode)
	{
		*declarationId = tokenId;

		for (const StorageEdge& edge: m_sqliteIndexStorage.getEdgesByTargetId(tokenId))
		{
			activeTokenIds.push_back(edge.id);
		}
	}

	return activeTokenIds;
}

std::vector<Id> PersistentStorage::getNodeIdsForLocationIds(const std::vector<Id>& locationIds) const
{
	TRACE();

	std::set<Id> nodeIds;
	std::set<Id> implicitNodeIds;

	for (const StorageOccurrence& occurrence:
		 m_sqliteIndexStorage.getOccurrencesForLocationIds(locationIds))
	{
		Id elementId = occurrence.elementId;

		const StorageEdge edge = m_sqliteIndexStorage.getFirstById<StorageEdge>(elementId);
		if (edge.id != 0)
		{
			elementId = edge.targetNodeId;
		}

		auto it = m_symbolDefinitionKinds.find(elementId);
		if ((it != m_symbolDefinitionKinds.end() && it->second == DEFINITION_IMPLICIT) ||
			Edge::intToType(edge.type) == Edge::EDGE_OVERRIDE)
		{
			implicitNodeIds.insert(elementId);
		}
		else
		{
			nodeIds.insert(elementId);
		}
	}

	if (nodeIds.size())
	{
		return utility::toVector(nodeIds);
	}

	return utility::toVector(implicitNodeIds);
}

std::shared_ptr<SourceLocationCollection> PersistentStorage::getSourceLocationsForTokenIds(
	const std::vector<Id>& tokenIds) const
{
	TRACE();

	std::map<Id, FilePath> filePaths;
	std::vector<Id> nonFileIds;

	for (const Id tokenId: tokenIds)
	{
		FilePath path = getFileNodePath(tokenId);

		// check for non-indexed file
		if (path.empty() && m_symbolDefinitionKinds.find(tokenId) == m_symbolDefinitionKinds.end())
		{
			const StorageNode fileNode = m_sqliteIndexStorage.getNodeById(tokenId);
			if (NodeType(intToNodeKind(fileNode.type)).isFile())
			{
				path = FilePath(
					NameHierarchy::deserialize(fileNode.serializedName).getQualifiedName());
			}
		}

		if (path.empty())
		{
			nonFileIds.push_back(tokenId);
		}
		else
		{
			filePaths.emplace(tokenId, path);
		}
	}

	std::shared_ptr<SourceLocationCollection> collection =
		std::make_shared<SourceLocationCollection>();
	for (const std::pair<Id, FilePath>& p: filePaths)
	{
		collection->addSourceLocationFile(std::make_shared<SourceLocationFile>(
			p.second, getFileNodeLanguage(p.first), true, false, false));
	}

	if (nonFileIds.size())
	{
		// FIXME: can we use get SqliteIndexStorage::getSourceLocationsForElementIds() here instead?
		std::vector<Id> locationIds;
		std::unordered_map<Id, Id> locationIdToElementIdMap;
		for (const StorageOccurrence& occurrence:
			 m_sqliteIndexStorage.getOccurrencesForElementIds(nonFileIds))
		{
			locationIds.push_back(occurrence.sourceLocationId);
			locationIdToElementIdMap[occurrence.sourceLocationId] = occurrence.elementId;
		}

		for (const StorageSourceLocation& sourceLocation:
			 m_sqliteIndexStorage.getAllByIds<StorageSourceLocation>(locationIds))
		{
			const LocationType type = intToLocationType(sourceLocation.type);
			if (type != LOCATION_TOKEN && type != LOCATION_SCOPE && type != LOCATION_LOCAL_SYMBOL &&
				type != LOCATION_UNSOLVED)
			{
				continue;
			}

			auto it = locationIdToElementIdMap.find(sourceLocation.id);
			if (it == locationIdToElementIdMap.end())
			{
				continue;
			}

			FilePath path = getFileNodePath(sourceLocation.fileNodeId);
			// FIXME: This shouldn't be necessary since all files are stored, even non-indexed
			if (path.empty())
			{
				const StorageNode fileNode = m_sqliteIndexStorage.getNodeById(
					sourceLocation.fileNodeId);
				if (fileNode.id)
				{
					const FilePath path2 = FilePath(
						NameHierarchy::deserialize(fileNode.serializedName).getQualifiedName());
					if (path2.exists())
					{
						path = path2;
					}
				}
			}

			if (!path.empty())
			{
				collection->addSourceLocation(
					type,
					sourceLocation.id,
					{it->second},
					path,
					sourceLocation.startLine,
					sourceLocation.startCol,
					sourceLocation.endLine,
					sourceLocation.endCol);
			}
		}
	}

	addCompleteFlagsToSourceLocationCollection(collection.get());

	return collection;
}

std::shared_ptr<SourceLocationCollection> PersistentStorage::getSourceLocationsForLocationIds(
	const std::vector<Id>& locationIds) const
{
	TRACE();

	std::shared_ptr<SourceLocationCollection> collection =
		std::make_shared<SourceLocationCollection>();

	std::map<Id, std::vector<Id>> m_locationIdToElementIds;
	for (const StorageOccurrence& occurrence:
		 m_sqliteIndexStorage.getOccurrencesForLocationIds(locationIds))
	{
		m_locationIdToElementIds[occurrence.sourceLocationId].push_back(occurrence.elementId);
	}

	for (StorageSourceLocation location:
		 m_sqliteIndexStorage.getAllByIds<StorageSourceLocation>(locationIds))
	{
		const LocationType type = intToLocationType(location.type);
		if (type != LOCATION_TOKEN && type != LOCATION_SCOPE && type != LOCATION_LOCAL_SYMBOL &&
			type != LOCATION_UNSOLVED)
		{
			continue;
		}

		collection->addSourceLocation(
			intToLocationType(location.type),
			location.id,
			m_locationIdToElementIds[location.id],
			getFileNodePath(location.fileNodeId),
			location.startLine,
			location.startCol,
			location.endLine,
			location.endCol);
	}

	addCompleteFlagsToSourceLocationCollection(collection.get());

	return collection;
}

std::shared_ptr<SourceLocationFile> PersistentStorage::getSourceLocationsForFile(
	const FilePath& filePath) const
{
	TRACE();

	return m_sqliteIndexStorage.getSourceLocationsForFile(filePath)->getFilteredByTypes(
		{LOCATION_TOKEN, LOCATION_SCOPE, LOCATION_QUALIFIER, LOCATION_LOCAL_SYMBOL, LOCATION_UNSOLVED});
}

std::shared_ptr<SourceLocationFile> PersistentStorage::getSourceLocationsForLinesInFile(
	const FilePath& filePath, size_t startLine, size_t endLine) const
{
	TRACE();

	return m_sqliteIndexStorage.getSourceLocationsForLinesInFile(filePath, startLine, endLine)
		->getFilteredByLines(startLine, endLine)
		->getFilteredByTypes(
			{LOCATION_TOKEN,
			 LOCATION_SCOPE,
			 LOCATION_QUALIFIER,
			 LOCATION_LOCAL_SYMBOL,
			 LOCATION_UNSOLVED});
}

std::shared_ptr<SourceLocationFile> PersistentStorage::getSourceLocationsOfTypeInFile(
	const FilePath& filePath, LocationType type) const
{
	TRACE();

	return m_sqliteIndexStorage.getSourceLocationsOfTypeInFile(filePath, type);
}

std::shared_ptr<TextAccess> PersistentStorage::getFileContent(
	const FilePath& filePath, bool showsErrors) const
{
	TRACE();

	std::shared_ptr<TextAccess> fileContent = m_sqliteIndexStorage.getFileContentByPath(
		filePath.wstr());
	if (fileContent->getLineCount() > 0)
	{
		return fileContent;
	}
	return TextAccess::createFromFile(FilePath(filePath));
}

bool PersistentStorage::hasContentForFile(const FilePath& filePath) const
{
	std::shared_ptr<TextAccess> fileContent = m_sqliteIndexStorage.getFileContentByPath(
		filePath.wstr());
	if (fileContent->getLineCount() > 0)
	{
		return true;
	}
	return false;
}

FileInfo PersistentStorage::getFileInfoForFileId(Id id) const
{
	StorageFile storageFile = m_sqliteIndexStorage.getFirstById<StorageFile>(id);
	return FileInfo(FilePath(storageFile.filePath), storageFile.modificationTime);
}

FileInfo PersistentStorage::getFileInfoForFilePath(const FilePath& filePath) const
{
	return getFileInfoForFileId(getFileNodeId(filePath));
}

std::vector<FileInfo> PersistentStorage::getFileInfosForFilePaths(
	const std::vector<FilePath>& filePaths) const
{
	std::vector<FileInfo> fileInfos;

	for (const StorageFile& file: m_sqliteIndexStorage.getFilesByPaths(filePaths))
	{
		fileInfos.push_back(FileInfo(FilePath(file.filePath), file.modificationTime));
	}

	return fileInfos;
}

StorageStats PersistentStorage::getStorageStats() const
{
	TRACE();

	StorageStats stats;

	stats.nodeCount = m_sqliteIndexStorage.getNodeCount();
	stats.edgeCount = m_sqliteIndexStorage.getEdgeCount();

	stats.fileCount = m_sqliteIndexStorage.getFileCount();
	stats.completedFileCount = m_sqliteIndexStorage.getCompletedFileCount();
	stats.fileLOCCount = m_sqliteIndexStorage.getFileLineSum();

	stats.timestamp = m_sqliteIndexStorage.getTime();

	return stats;
}

ErrorCountInfo PersistentStorage::getErrorCount() const
{
	return ErrorCountInfo(m_sqliteIndexStorage.getAllErrorInfos());
}

std::vector<ErrorInfo> PersistentStorage::getErrorsLimited(const ErrorFilter& filter) const
{
	return filter.filterErrors(m_sqliteIndexStorage.getAllErrorInfos());
}

std::vector<ErrorInfo> PersistentStorage::getErrorsForFileLimited(
	const ErrorFilter& filter, const FilePath& filePath) const
{
	Id fileId = getFileNodeId(filePath);
	std::set<Id> fileIds = {fileId};

	std::unordered_map<Id, std::set<Id>> includedMap = getFileIdToIncludedFileIdMap();
	std::set<Id> fileIdsToProcess = includedMap[getFileNodeId(filePath)];
	std::set<Id> processedFileIds;

	while (fileIdsToProcess.size())
	{
		std::set<Id> nextFileIdsToProcess;
		for (Id id: fileIdsToProcess)
		{
			if (fileIds.insert(id).second)
			{
				utility::append(nextFileIdsToProcess, includedMap[id]);
			}
		}
		fileIdsToProcess = nextFileIdsToProcess;
	}

	std::vector<ErrorInfo> res;

	std::vector<ErrorInfo> errors = m_sqliteIndexStorage.getAllErrorInfos();
	for (const ErrorInfo& error: errors)
	{
		if (filter.filter(error) &&
			fileIds.find(getFileNodeId(FilePath(error.filePath))) != fileIds.end())
		{
			res.push_back(error);
		}
	}

	if (res.empty())
	{
		std::unordered_map<Id, std::set<Id>> includingMap = getFileIdToIncludingFileIdMap();
		fileIds.clear();

		fileIdsToProcess = includingMap[fileId];
		while (fileIdsToProcess.size())
		{
			std::set<Id> nextFileIdsToProcess;
			for (Id id: fileIdsToProcess)
			{
				if (fileIds.insert(id).second)
				{
					utility::append(nextFileIdsToProcess, includingMap[id]);
				}
			}
			fileIdsToProcess = nextFileIdsToProcess;
		}

		for (const ErrorInfo& error: errors)
		{
			if (error.fatal && filter.filter(error) &&
				fileIds.find(getFileNodeId(FilePath(error.filePath))) != fileIds.end())
			{
				res.push_back(error);
			}
		}
	}

	return res;
}

std::shared_ptr<SourceLocationCollection> PersistentStorage::getErrorSourceLocations(
	const std::vector<ErrorInfo>& errors) const
{
	TRACE();

	std::shared_ptr<SourceLocationCollection> collection =
		std::make_shared<SourceLocationCollection>();

	for (const ErrorInfo& error: errors)
	{
		// Set first bit to 1 to avoid collisions
		Id locationId = ~(~Id(0) >> 1) + error.id;

		collection->addSourceLocation(
			LOCATION_ERROR,
			locationId,
			std::vector<Id>(1, error.id),
			FilePath(error.filePath),
			error.lineNumber,
			error.columnNumber,
			error.lineNumber,
			error.columnNumber);
	}

	addCompleteFlagsToSourceLocationCollection(collection.get());

	return collection;
}

Id PersistentStorage::addNodeBookmark(const NodeBookmark& bookmark)
{
	const Id categoryId = addBookmarkCategory(bookmark.getCategory().getName());
	const Id id = m_sqliteBookmarkStorage
					  .addBookmark(StorageBookmarkData(
						  bookmark.getName(),
						  bookmark.getComment(),
						  bookmark.getTimeStamp().toString(),
						  categoryId))
					  .id;

	for (const Id& nodeId: bookmark.getNodeIds())
	{
		m_sqliteBookmarkStorage.addBookmarkedNode(
			StorageBookmarkedNodeData(id, m_sqliteIndexStorage.getNodeById(nodeId).serializedName));
	}

	return id;
}

Id PersistentStorage::addEdgeBookmark(const EdgeBookmark& bookmark)
{
	const Id categoryId = addBookmarkCategory(bookmark.getCategory().getName());
	const Id id = m_sqliteBookmarkStorage
					  .addBookmark(StorageBookmarkData(
						  bookmark.getName(),
						  bookmark.getComment(),
						  bookmark.getTimeStamp().toString(),
						  categoryId))
					  .id;
	for (const Id& edgeId: bookmark.getEdgeIds())
	{
		const StorageEdge storageEdge = m_sqliteIndexStorage.getEdgeById(edgeId);

		bool sourceNodeActive = storageEdge.sourceNodeId == bookmark.getActiveNodeId();
		m_sqliteBookmarkStorage.addBookmarkedEdge(StorageBookmarkedEdgeData(
			id,
			// todo: optimization for multiple edges in same bookmark: use a local cache here
			m_sqliteIndexStorage.getNodeById(storageEdge.sourceNodeId).serializedName,
			m_sqliteIndexStorage.getNodeById(storageEdge.targetNodeId).serializedName,
			storageEdge.type,
			sourceNodeActive));
	}
	return id;
}

Id PersistentStorage::addBookmarkCategory(const std::wstring& name)
{
	if (name.empty())
	{
		return 0;
	}

	Id id = m_sqliteBookmarkStorage.getBookmarkCategoryByName(name).id;
	if (id == 0)
	{
		id = m_sqliteBookmarkStorage.addBookmarkCategory(StorageBookmarkCategoryData(name)).id;
	}
	return id;
}

void PersistentStorage::updateBookmark(
	const Id bookmarkId,
	const std::wstring& name,
	const std::wstring& comment,
	const std::wstring& categoryName)
{
	const Id categoryId = addBookmarkCategory(
		categoryName);	  // only creates category if id didn't exist before;
	m_sqliteBookmarkStorage.updateBookmark(bookmarkId, name, comment, categoryId);
}

void PersistentStorage::removeBookmark(const Id id)
{
	m_sqliteBookmarkStorage.removeBookmark(id);
}

void PersistentStorage::removeBookmarkCategory(Id id)
{
	m_sqliteBookmarkStorage.removeBookmarkCategory(id);
}

std::vector<NodeBookmark> PersistentStorage::getAllNodeBookmarks() const
{
	std::unordered_map<Id, StorageBookmarkCategory> bookmarkCategories;
	for (const StorageBookmarkCategory& bookmarkCategory:
		 m_sqliteBookmarkStorage.getAllBookmarkCategories())
	{
		bookmarkCategories[bookmarkCategory.id] = bookmarkCategory;
	}

	std::unordered_map<Id, std::vector<Id>> bookmarkIdToBookmarkedNodeIds;
	for (const StorageBookmarkedNode& bookmarkedNode: m_sqliteBookmarkStorage.getAllBookmarkedNodes())
	{
		bookmarkIdToBookmarkedNodeIds[bookmarkedNode.bookmarkId].push_back(
			m_sqliteIndexStorage.getNodeBySerializedName(bookmarkedNode.serializedNodeName).id);
	}

	std::vector<NodeBookmark> nodeBookmarks;

	for (const StorageBookmark& storageBookmark: m_sqliteBookmarkStorage.getAllBookmarks())
	{
		auto itCategories = bookmarkCategories.find(storageBookmark.categoryId);
		auto itNodeIds = bookmarkIdToBookmarkedNodeIds.find(storageBookmark.id);
		if (itCategories != bookmarkCategories.end() &&
			itNodeIds != bookmarkIdToBookmarkedNodeIds.end())
		{
			NodeBookmark bookmark(
				storageBookmark.id,
				storageBookmark.name,
				storageBookmark.comment,
				storageBookmark.timestamp,
				BookmarkCategory(itCategories->second.id, itCategories->second.name));
			bookmark.setNodeIds(itNodeIds->second);
			bookmark.setIsValid();
			nodeBookmarks.push_back(bookmark);
		}
	}

	return nodeBookmarks;
}

std::vector<EdgeBookmark> PersistentStorage::getAllEdgeBookmarks() const
{
	std::unordered_map<Id, StorageBookmarkCategory> bookmarkCategories;
	for (const StorageBookmarkCategory& bookmarkCategory:
		 m_sqliteBookmarkStorage.getAllBookmarkCategories())
	{
		bookmarkCategories[bookmarkCategory.id] = bookmarkCategory;
	}

	std::unordered_map<Id, std::vector<StorageBookmarkedEdge>> bookmarkIdToBookmarkedEdges;
	for (const StorageBookmarkedEdge& bookmarkedEdge: m_sqliteBookmarkStorage.getAllBookmarkedEdges())
	{
		bookmarkIdToBookmarkedEdges[bookmarkedEdge.bookmarkId].push_back(bookmarkedEdge);
	}

	std::vector<EdgeBookmark> edgeBookmarks;

	UnorderedCache<std::wstring, Id> nodeIdCache([&](const std::wstring& serializedNodeName) {
		return m_sqliteIndexStorage.getNodeBySerializedName(serializedNodeName).id;
	});

	for (const StorageBookmark& storageBookmark: m_sqliteBookmarkStorage.getAllBookmarks())
	{
		auto itCategories = bookmarkCategories.find(storageBookmark.categoryId);
		auto itBookmarkedEdges = bookmarkIdToBookmarkedEdges.find(storageBookmark.id);
		if (itCategories != bookmarkCategories.end() &&
			itBookmarkedEdges != bookmarkIdToBookmarkedEdges.end())
		{
			EdgeBookmark bookmark(
				storageBookmark.id,
				storageBookmark.name,
				storageBookmark.comment,
				storageBookmark.timestamp,
				BookmarkCategory(itCategories->second.id, itCategories->second.name));

			Id activeNodeId = 0;
			for (const StorageBookmarkedEdge& bookmarkedEdge: itBookmarkedEdges->second)
			{
				const Id sourceNodeId = nodeIdCache.getValue(bookmarkedEdge.serializedSourceNodeName);
				const Id targetNodeId = nodeIdCache.getValue(bookmarkedEdge.serializedTargetNodeName);
				const Id edgeId = m_sqliteIndexStorage
									  .getEdgeBySourceTargetType(
										  sourceNodeId, targetNodeId, bookmarkedEdge.edgeType)
									  .id;
				bookmark.addEdgeId(edgeId);

				if (activeNodeId == 0)
				{
					activeNodeId = bookmarkedEdge.sourceNodeActive ? sourceNodeId : targetNodeId;
				}
			}
			bookmark.setActiveNodeId(activeNodeId);
			bookmark.setIsValid();
			edgeBookmarks.push_back(bookmark);
		}
	}

	return edgeBookmarks;
}

std::vector<BookmarkCategory> PersistentStorage::getAllBookmarkCategories() const
{
	std::vector<BookmarkCategory> categories;
	for (const StorageBookmarkCategory& storageBookmarkCategory:
		 m_sqliteBookmarkStorage.getAllBookmarkCategories())
	{
		categories.push_back(
			BookmarkCategory(storageBookmarkCategory.id, storageBookmarkCategory.name));
	}
	return categories;
}

TooltipInfo PersistentStorage::getTooltipInfoForTokenIds(
	const std::vector<Id>& tokenIds, TooltipOrigin origin) const
{
	TRACE();

	TooltipInfo info;

	if (!tokenIds.size())
	{
		return info;
	}

	StorageNode node = m_sqliteIndexStorage.getFirstById<StorageNode>(tokenIds[0]);
	if (node.id == 0 && origin == TOOLTIP_ORIGIN_CODE)
	{
		const StorageEdge edge = m_sqliteIndexStorage.getFirstById<StorageEdge>(tokenIds[0]);

		if (edge.id > 0)
		{
			node = m_sqliteIndexStorage.getFirstById<StorageNode>(edge.targetNodeId);
		}
	}

	if (node.id == 0)
	{
		return info;
	}

	const NodeType type(intToNodeKind(node.type));
	info.title = type.getReadableTypeWString();

	DefinitionKind defKind = DEFINITION_NONE;
	const StorageSymbol symbol = m_sqliteIndexStorage.getFirstById<StorageSymbol>(node.id);
	if (symbol.id > 0)
	{
		defKind = intToDefinitionKind(symbol.definitionKind);
	}

	if (type.isPotentialMember())
	{
		const StorageComponentAccess access = m_sqliteIndexStorage.getComponentAccessByNodeId(node.id);
		if (access.nodeId != 0)
		{
			info.title = accessKindToString(intToAccessKind(access.type)) + L" " + info.title;
		}
	}

	if (type.isFile())
	{
		if (!getFileNodeIndexed(node.id))
		{
			info.title = L"non-indexed " + info.title;
		}

		if (!getFileNodeComplete(node.id))
		{
			info.title = L"incomplete " + info.title;
		}
	}
	else if (defKind == DEFINITION_NONE)
	{
		info.title = L"non-indexed " + info.title;
	}
	else if (defKind == DEFINITION_IMPLICIT)
	{
		info.title = L"implicit " + info.title;
	}

	info.count = 0;
	info.countText = "reference";
	for (const auto& edge: m_sqliteIndexStorage.getEdgesByTargetId(node.id))
	{
		if (Edge::intToType(edge.type) != Edge::EDGE_MEMBER)
		{
			info.count++;
		}
	}

	info.snippets.push_back(getTooltipSnippetForNode(node));

	if (origin == TOOLTIP_ORIGIN_CODE)
	{
		info.offset = Vec2i(20, 30);
	}
	else if (origin == TOOLTIP_ORIGIN_GRAPH)
	{
		info.offset = Vec2i(50, 20);
	}

	return info;
}

TooltipSnippet PersistentStorage::getTooltipSnippetForNode(const StorageNode& node) const
{
	TRACE();

	const NameHierarchy nameHierarchy = NameHierarchy::deserialize(node.serializedName);
	TooltipSnippet snippet;
	snippet.code = nameHierarchy.getQualifiedNameWithSignature();
	snippet.locationFile = std::make_shared<SourceLocationFile>(
		FilePath(L"main.txt"), L"", true, true, true);

	// set file language
	std::vector<StorageOccurrence> occurrences = m_sqliteIndexStorage.getOccurrencesForElementIds(
		{node.id});
	if (occurrences.size())
	{
		const Id locationId = occurrences.front().sourceLocationId;
		const Id fileId =
			m_sqliteIndexStorage.getFirstById<StorageSourceLocation>(locationId).fileNodeId;
		snippet.locationFile->setLanguage(getFileNodeLanguage(fileId));
	}

	if (nameHierarchy.hasSignature())
	{
		std::shared_ptr<SourceLocationCollection> locations =
			m_sqliteIndexStorage.getSourceLocationsForElementIds({node.id});
		SourceLocation* sigLoc = nullptr;

		locations->forEachSourceLocation([&sigLoc](SourceLocation* location) {
			if (!sigLoc && location->isStartLocation() && location->getType() == LOCATION_SIGNATURE)
			{
				sigLoc = location;
			}
		});

		// if node has a signature location use that one
		// use while for breaking out
		while (sigLoc)
		{
			struct Annotation
			{
				Id locationId = 0;
				size_t startPos = 0;
				size_t endPos = 0;
			};

			std::vector<Annotation> annotations;
			std::vector<std::string> lines =
				getFileContent(sigLoc->getFilePath(), false)
					->getLines(
						static_cast<unsigned int>(sigLoc->getLineNumber()),
						static_cast<unsigned int>(sigLoc->getEndLocation()->getLineNumber()));

			// check if signature location refers to correct locations in the code
			// wrongly recorded signature locations of implicit template methods in C++ caused crashes
			if (lines.empty() ||
				lines.size() !=
					sigLoc->getOtherLocation()->getLineNumber() - sigLoc->getLineNumber() + 1 ||
				sigLoc->getColumnNumber() > lines[0].size() ||
				sigLoc->getOtherLocation()->getColumnNumber() > lines[lines.size() - 1].size())
			{
				break;
			}

			std::shared_ptr<SourceLocationFile> file = getSourceLocationsForLinesInFile(
				sigLoc->getFilePath(),
				sigLoc->getStartLocation()->getLineNumber(),
				sigLoc->getEndLocation()->getLineNumber());

			file->forEachStartSourceLocation([&sigLoc, &annotations, &lines](SourceLocation* loc) {
				if ((loc->getType() == LOCATION_TOKEN || loc->getType() == LOCATION_QUALIFIER) &&
					sigLoc->contains(*loc))
				{
					Annotation annotation;
					annotation.locationId = loc->getLocationId();

					for (size_t i = 0;
						 i < loc->getLineNumber() - sigLoc->getLineNumber() && i < lines.size();
						 i++)
					{
						annotation.startPos += lines[i].size();
					}
					annotation.startPos += loc->getColumnNumber() - sigLoc->getColumnNumber();

					for (size_t i = 0;
						 i < loc->getEndLocation()->getLineNumber() - sigLoc->getLineNumber() &&
						 i < lines.size();
						 i++)
					{
						annotation.endPos += lines[i].size();
					}
					annotation.endPos += loc->getEndLocation()->getColumnNumber() -
						sigLoc->getColumnNumber();

					annotations.push_back(annotation);
				}
			});

			// remove characters after signature end
			lines[lines.size() - 1] = lines[lines.size() - 1].substr(
				0, sigLoc->getEndLocation()->getColumnNumber());

			// remove characters before signature start
			lines[0] = lines[0].substr(sigLoc->getColumnNumber() - 1);

			std::wstring code = utility::decodeFromUtf8(utility::join(lines, ""));

			// store texts of annotations
			std::vector<std::pair<Id, std::wstring>> annotatedTexts;
			std::wstring delimiter = nameHierarchy.getDelimiter();
			size_t offset = 0;
			for (const Annotation& annotation: annotations)
			{
				std::wstring text = code.substr(
					annotation.startPos + offset, annotation.endPos - annotation.startPos + 1);

				// only replace name if not already prepended with other qualifiers
				size_t delimiterPos = code.rfind(delimiter, annotation.startPos);

				// if is function name itself, replace with qualified name
				if (utility::containsElement(
						file->getSourceLocationById(annotation.locationId)->getTokenIds(), node.id) &&
					(delimiterPos == std::wstring::npos ||
					 delimiterPos < annotation.startPos - delimiter.size()) &&
					text.size() <= nameHierarchy.getRawName().size())
				{
					std::wstring name = nameHierarchy.getQualifiedName();
					offset = name.size() - text.size();

					code = code.replace(
						annotation.startPos, annotation.endPos - annotation.startPos + 1, name);
					text = name;
				}
				else
				{
					text = utility::convertWhiteSpacesToSingleSpaces(text);
				}
				annotatedTexts.push_back(std::make_pair(annotation.locationId, text));
			}

			// format
			code = utility::convertWhiteSpacesToSingleSpaces(code);
			snippet.code = utility::breakSignature(
				code, 50, ApplicationSettings::getInstance()->getCodeTabWidth());

			// create source locations for annotations via stored texts
			size_t pos = 0;
			for (const std::pair<Id, std::wstring>& p: annotatedTexts)
			{
				pos = snippet.code.find(p.second, pos);
				if (pos != std::wstring::npos)
				{
					SourceLocation* loc = file->getSourceLocationById(p.first);

					snippet.locationFile->addSourceLocation(
						loc->getType(),
						loc->getLocationId(),
						loc->getTokenIds(),
						1,
						pos + 1,
						1,
						pos + p.second.size());

					pos += p.second.size();
				}
			}

			return snippet;
		}

		// otherwise augment the name with signature with locations for type usages
		snippet.code = utility::breakSignature(
			nameHierarchy.getSignature().getPrefix(),
			nameHierarchy.getQualifiedName(),
			nameHierarchy.getSignature().getPostfix(),
			50,
			ApplicationSettings::getInstance()->getCodeTabWidth());

		std::vector<Id> typeNodeIds;
		for (const auto& edge: m_sqliteIndexStorage.getEdgesBySourceId(node.id))
		{
			if (Edge::intToType(edge.type) == Edge::EDGE_TYPE_USAGE)
			{
				typeNodeIds.push_back(edge.targetNodeId);
			}
		}

		std::set<
			std::pair<std::wstring, Id>,
			bool (*)(const std::pair<std::wstring, Id>&, const std::pair<std::wstring, Id>&)>
			typeNames([](const std::pair<std::wstring, Id>& a, const std::pair<std::wstring, Id>& b) {
				if (a.first.size() == b.first.size())
				{
					return a.first < b.first;
				}

				return a.first.size() > b.first.size();
			});

		typeNames.insert(std::make_pair(nameHierarchy.getQualifiedName(), node.id));
		for (const auto& typeNode: m_sqliteIndexStorage.getAllByIds<StorageNode>(typeNodeIds))
		{
			typeNames.insert(std::make_pair(
				NameHierarchy::deserialize(typeNode.serializedName).getQualifiedName(), typeNode.id));
		}

		std::vector<std::pair<size_t, size_t>> locationRanges;
		for (const auto& p: typeNames)
		{
			size_t pos = 0;
			while (pos != std::wstring::npos)
			{
				pos = snippet.code.find(p.first, pos);
				if (pos == std::wstring::npos)
				{
					continue;
				}

				bool inRange = false;
				for (const auto& locationRange: locationRanges)
				{
					if (pos + 1 >= locationRange.first && pos + 1 <= locationRange.second)
					{
						inRange = true;
						pos = locationRange.second + 1;
						break;
					}
				}

				if (!inRange)
				{
					snippet.locationFile->addSourceLocation(
						LOCATION_TOKEN,
						0,
						std::vector<Id>(1, p.second),
						1,
						pos + 1,
						1,
						pos + p.first.size());

					locationRanges.push_back(std::make_pair(pos + 1, pos + p.first.size()));
					pos += p.first.size();
				}
			}
		}
	}
	else
	{
		snippet.locationFile->addSourceLocation(
			LOCATION_TOKEN, 0, std::vector<Id>(1, node.id), 1, 1, 1, snippet.code.size());
	}

	return snippet;
}

TooltipInfo PersistentStorage::getTooltipInfoForSourceLocationIdsAndLocalSymbolIds(
	const std::vector<Id>& locationIds, const std::vector<Id>& localSymbolIds) const
{
	TRACE();

	const TextCodec codec(ApplicationSettings::getInstance()->getTextEncoding());

	TooltipInfo info;

	if (locationIds.empty() && localSymbolIds.empty())
	{
		return info;
	}

	if (!locationIds.empty())
	{
		std::wstring fileLanguage = getFileNodeLanguage(
			m_sqliteIndexStorage.getFirstById<StorageSourceLocation>(locationIds.front()).fileNodeId);

		const std::vector<Id> nodeIds = getNodeIdsForLocationIds(locationIds);

		for (const StorageNode& node: m_sqliteIndexStorage.getAllByIds<StorageNode>(nodeIds))
		{
			TooltipSnippet snippet;

			const NameHierarchy nameHierarchy = NameHierarchy::deserialize(node.serializedName);
			snippet.code = nameHierarchy.getQualifiedName();
			snippet.locationFile = std::make_shared<SourceLocationFile>(
				FilePath(L"main.txt"), fileLanguage, true, true, true);

			snippet.locationFile->addSourceLocation(
				LOCATION_TOKEN, 0, std::vector<Id>(1, node.id), 1, 1, 1, snippet.code.size());

			if (NodeType(intToNodeKind(node.type)).isCallable())
			{
				snippet.code += L"()";
			}

			info.snippets.push_back(snippet);
		}
	}

	for (Id id: localSymbolIds)
	{
		TooltipSnippet snippet;

		snippet.code = L"local symbol";
		snippet.locationFile = std::make_shared<SourceLocationFile>(
			FilePath(L"main.txt"), L"", true, true, true);
		snippet.locationFile->addSourceLocation(
			LOCATION_LOCAL_SYMBOL, 0, std::vector<Id>(1, id), 1, 1, 1, snippet.code.size());

		info.snippets.push_back(snippet);
	}

	info.offset = Vec2i(0, 15);

	return info;
}

Id PersistentStorage::getFileNodeId(const FilePath& filePath) const
{
	if (filePath.empty())
	{
		LOG_ERROR("No file path set");
		return 0;
	}

	{
		std::map<FilePath, Id>::const_iterator it = m_fileNodeIds.find(filePath);
		if (it != m_fileNodeIds.end())
		{
			return it->second;
		}
	}
	{
		std::map<FilePath, Id>::const_iterator it = m_lowerCasefileNodeIds.find(
			filePath.getLowerCase());
		if (it != m_lowerCasefileNodeIds.end())
		{
			return it->second;
		}
	}

	return 0;
}

std::vector<Id> PersistentStorage::getFileNodeIds(const std::vector<FilePath>& filePaths) const
{
	std::vector<Id> ids;
	for (const FilePath& path: filePaths)
	{
		ids.push_back(getFileNodeId(path));
	}
	return ids;
}

std::set<Id> PersistentStorage::getFileNodeIds(const std::set<FilePath>& filePaths) const
{
	std::set<Id> ids;
	for (const FilePath& path: filePaths)
	{
		ids.insert(getFileNodeId(path));
	}
	return ids;
}

FilePath PersistentStorage::getFileNodePath(Id fileId) const
{
	if (fileId == 0)
	{
		LOG_ERROR("No file id set");
		return FilePath();
	}

	std::map<Id, FilePath>::const_iterator it = m_fileNodePaths.find(fileId);

	if (it != m_fileNodePaths.end())
	{
		return it->second;
	}

	return FilePath();
}

bool PersistentStorage::getFileNodeComplete(Id fileId) const
{
	auto it = m_fileNodeComplete.find(fileId);
	if (it != m_fileNodeComplete.end())
	{
		return it->second;
	}

	return false;
}

bool PersistentStorage::getFileNodeIndexed(Id fileId) const
{
	auto it = m_fileNodeIndexed.find(fileId);
	if (it != m_fileNodeIndexed.end())
	{
		return it->second;
	}

	return false;
}

std::wstring PersistentStorage::getFileNodeLanguage(Id fileId) const
{
	auto it = m_fileNodeLanguage.find(fileId);
	if (it != m_fileNodeLanguage.end())
	{
		return it->second;
	}

	return L"";
}

std::unordered_map<Id, std::set<Id>> PersistentStorage::getFileIdToIncludingFileIdMap() const
{
	std::unordered_map<Id, std::set<Id>> fileIdToIncludingFileIdMap;

	m_sqliteIndexStorage.forEachOfType<StorageEdge>(
		Edge::typeToInt(Edge::EDGE_INCLUDE), [&fileIdToIncludingFileIdMap](StorageEdge&& edge) {
			fileIdToIncludingFileIdMap[edge.targetNodeId].insert(edge.sourceNodeId);
		});

	return fileIdToIncludingFileIdMap;
}

std::unordered_map<Id, std::set<Id>> PersistentStorage::getFileIdToIncludedFileIdMap() const
{
	std::unordered_map<Id, std::set<Id>> fileIdToIncludingFileIdMap;

	m_sqliteIndexStorage.forEachOfType<StorageEdge>(
		Edge::typeToInt(Edge::EDGE_INCLUDE), [&fileIdToIncludingFileIdMap](StorageEdge&& edge) {
			fileIdToIncludingFileIdMap[edge.sourceNodeId].insert(edge.targetNodeId);
		});

	return fileIdToIncludingFileIdMap;
}

std::unordered_map<Id, std::set<Id>> PersistentStorage::getFileIdToImportingFileIdMap() const
{
	std::unordered_map<Id, std::set<Id>> fileIdToImportingFileIdMap;
	{
		std::vector<Id> importedElementIds;
		std::map<Id, std::set<Id>> elementIdToImportingFileIds;

		m_sqliteIndexStorage.forEachOfType<StorageEdge>(
			Edge::typeToInt(Edge::EDGE_IMPORT),
			[&importedElementIds, &elementIdToImportingFileIds](StorageEdge&& edge) {
				importedElementIds.push_back(edge.targetNodeId);
				elementIdToImportingFileIds[edge.targetNodeId].insert(edge.sourceNodeId);
			});

		std::unordered_map<Id, Id> importedElementIdToFileNodeId;
		{
			std::vector<Id> importedSourceLocationIds;
			std::unordered_map<Id, Id> importedSourceLocationToElementIds;
			for (const StorageOccurrence& occurrence:
				 m_sqliteIndexStorage.getOccurrencesForElementIds(importedElementIds))
			{
				importedSourceLocationIds.push_back(occurrence.sourceLocationId);
				importedSourceLocationToElementIds[occurrence.sourceLocationId] = occurrence.elementId;
			}

			for (const StorageSourceLocation& sourceLocation:
				 m_sqliteIndexStorage.getAllByIds<StorageSourceLocation>(importedSourceLocationIds))
			{
				auto it = importedSourceLocationToElementIds.find(sourceLocation.id);
				if (it != importedSourceLocationToElementIds.end())
				{
					importedElementIdToFileNodeId[it->second] = sourceLocation.fileNodeId;
				}
			}
		}

		for (const auto& it: elementIdToImportingFileIds)
		{
			auto importedFileIt = importedElementIdToFileNodeId.find(it.first);
			if (importedFileIt != importedElementIdToFileNodeId.end())
			{
				fileIdToImportingFileIdMap[importedFileIt->second].insert(
					it.second.begin(), it.second.end());
			}
		}
	}
	return fileIdToImportingFileIdMap;
}

std::set<Id> PersistentStorage::getReferenced(
	const std::set<Id>& ids, std::unordered_map<Id, std::set<Id>> idToReferencingIdMap) const
{
	std::unordered_map<Id, std::set<Id>> idToReferencedIdMap;
	for (const auto& it: idToReferencingIdMap)
	{
		for (Id referencingId: it.second)
		{
			idToReferencedIdMap[referencingId].insert(it.first);
		}
	}

	return getReferencing(ids, idToReferencedIdMap);
}

std::set<Id> PersistentStorage::getReferencing(
	const std::set<Id>& ids, std::unordered_map<Id, std::set<Id>> idToReferencingIdMap) const
{
	std::set<Id> referencingIds;

	std::set<Id> processingIds = ids;
	std::set<Id> processedIds;

	while (!processingIds.empty())
	{
		std::set<Id> tempIds = processingIds;
		utility::append(processedIds, processingIds);
		processingIds.clear();

		for (Id id: tempIds)
		{
			utility::append(referencingIds, idToReferencingIdMap[id]);
			for (Id referencingId: idToReferencingIdMap[id])
			{
				if (processedIds.find(referencingId) == processedIds.end())
				{
					processingIds.insert(referencingId);
				}
			}
		}
	}

	return referencingIds;
}

std::set<FilePath> PersistentStorage::getReferencedByIncludes(const std::set<FilePath>& filePaths) const
{
	const std::set<Id> ids = getReferenced(
		getFileNodeIds(filePaths), getFileIdToIncludingFileIdMap());

	std::set<FilePath> paths;
	for (Id id: ids)
	{	 // TODO: performance optimize: use just one request for all ids!
		paths.insert(getFileNodePath(id));
	}

	return paths;
}

std::set<FilePath> PersistentStorage::getReferencedByImports(const std::set<FilePath>& filePaths) const
{
	const std::set<Id> ids = getReferenced(
		getFileNodeIds(filePaths), getFileIdToImportingFileIdMap());

	std::set<FilePath> paths;
	for (Id id: ids)
	{
		paths.insert(getFileNodePath(id));
	}

	return paths;
}

std::set<FilePath> PersistentStorage::getReferencingByIncludes(const std::set<FilePath>& filePaths) const
{
	const std::set<Id> ids = getReferencing(
		getFileNodeIds(filePaths), getFileIdToIncludingFileIdMap());

	std::set<FilePath> paths;
	for (Id id: ids)
	{
		paths.insert(getFileNodePath(id));
	}

	return paths;
}

std::set<FilePath> PersistentStorage::getReferencingByImports(const std::set<FilePath>& filePaths) const
{
	const std::set<Id> ids = getReferencing(
		getFileNodeIds(filePaths), getFileIdToImportingFileIdMap());

	std::set<FilePath> paths;
	for (Id id: ids)
	{
		paths.insert(getFileNodePath(id));
	}

	return paths;
}

void PersistentStorage::addNodesToGraph(
	const std::vector<Id>& newNodeIds, Graph* graph, bool addChildCount) const
{
	TRACE();

	std::vector<Id> nodeIds;
	if (graph->getNodeCount())
	{
		for (Id id: newNodeIds)
		{
			if (!graph->getNodeById(id))
			{
				nodeIds.push_back(id);
			}
		}
	}
	else
	{
		nodeIds = newNodeIds;
	}

	if (nodeIds.size() == 0)
	{
		return;
	}

	for (const StorageNode& storageNode: m_sqliteIndexStorage.getAllByIds<StorageNode>(nodeIds))
	{
		const NodeType type(intToNodeKind(storageNode.type));
		if (type.isFile())
		{
			addFileNodeToGraph(storageNode, graph);
		}
		else
		{
			addNodeToGraph(storageNode, type, graph, addChildCount);
		}
	}
}

void PersistentStorage::addFileNodeToGraph(const StorageNode& storageNode, Graph* const graph) const
{
	NameHierarchy nameHierarchy = NameHierarchy::deserialize(storageNode.serializedName);
	const FilePath filePath(nameHierarchy.getRawName());

	bool complete = getFileNodeComplete(storageNode.id);
	bool indexed = getFileNodeIndexed(storageNode.id);

	Node* node = graph->createNode(
		storageNode.id,
		NodeType(NODE_FILE),
		NameHierarchy(filePath.fileName(), NAME_DELIMITER_FILE),
		indexed ? DEFINITION_EXPLICIT : DEFINITION_NONE);
	node->addComponent(std::make_shared<TokenComponentFilePath>(filePath, complete));
}

void PersistentStorage::addNodeToGraph(
	const StorageNode& newNode, const NodeType& type, Graph* graph, bool addChildCount) const
{
	NameHierarchy nameHierarchy = NameHierarchy::deserialize(newNode.serializedName);
	DefinitionKind defKind = DEFINITION_NONE;
	auto it = m_symbolDefinitionKinds.find(newNode.id);
	if (it != m_symbolDefinitionKinds.end())
	{
		defKind = it->second;
	}

	Node* node = graph->createNode(newNode.id, type, std::move(nameHierarchy), defKind);

	if (addChildCount)
	{
		node->setChildCount(m_hierarchyCache.getFirstChildIdsCountForNodeId(newNode.id));
	}
}

void PersistentStorage::addEdgesToGraph(const std::vector<Id>& newEdgeIds, Graph* graph) const
{
	TRACE();

	std::vector<Id> edgeIds;
	for (Id id: newEdgeIds)
	{
		if (!graph->getEdgeById(id))
		{
			edgeIds.push_back(id);
		}
	}

	if (edgeIds.size() == 0)
	{
		return;
	}

	for (const StorageEdge& storageEdge: m_sqliteIndexStorage.getAllByIds<StorageEdge>(edgeIds))
	{
		Node* sourceNode = graph->getNodeById(storageEdge.sourceNodeId);
		Node* targetNode = graph->getNodeById(storageEdge.targetNodeId);

		if (sourceNode && targetNode)
		{
			Edge::EdgeType type = Edge::intToType(storageEdge.type);
			Id edgeId = storageEdge.id;
			if (type & Edge::EDGE_MEMBER && m_memberEdgeIdOrderMap.size())
			{
				auto it = m_memberEdgeIdOrderMap.find(edgeId);
				if (it != m_memberEdgeIdOrderMap.end())
				{
					edgeId = it->second;
				}
			}

			graph->createEdge(edgeId, type, sourceNode, targetNode);
		}
		else
		{
			LOG_ERROR("Can't add edge because nodes are not present");
		}
	}
}

void PersistentStorage::addNodesWithParentsAndEdgesToGraph(
	const std::vector<Id>& nodeIds, const std::vector<Id>& edgeIds, Graph* graph, bool addChildCount) const
{
	TRACE();

	std::set<Id> allNodeIds(nodeIds.begin(), nodeIds.end());
	std::set<Id> allEdgeIds(edgeIds.begin(), edgeIds.end());

	if (edgeIds.size() > 0)
	{
		for (const StorageEdge& storageEdge: m_sqliteIndexStorage.getAllByIds<StorageEdge>(edgeIds))
		{
			allNodeIds.insert(storageEdge.sourceNodeId);
			allNodeIds.insert(storageEdge.targetNodeId);
		}
	}

	std::set<Id> parentNodeIds;
	for (Id nodeId: allNodeIds)
	{
		m_hierarchyCache.addAllVisibleParentIdsForNodeId(nodeId, &parentNodeIds, &allEdgeIds);
	}

	allNodeIds.insert(parentNodeIds.begin(), parentNodeIds.end());

	addNodesToGraph(utility::toVector(allNodeIds), graph, addChildCount);
	addEdgesToGraph(utility::toVector(allEdgeIds), graph);
}

void PersistentStorage::addBundledEdgesToGraph(
	Id nodeId, const std::vector<StorageEdge>& edgesToBundle, Graph* graph) const
{
	TRACE();

	struct EdgeInfo
	{
		Id edgeId;
		bool forward;
	};

	// build bundled edges:
	// get all children of the active node
	std::set<Id> childNodeIdsSet, edgeIdsSet;
	m_hierarchyCache.addAllChildIdsForNodeId(nodeId, &childNodeIdsSet, &edgeIdsSet);
	const std::vector<Id> childNodeIds = utility::toVector(childNodeIdsSet);
	if (childNodeIds.size() == 0 && edgesToBundle.size() == 0)
	{
		return;
	}

	// get all edges of the children
	std::map<Id, std::vector<EdgeInfo>> connectedNodeIds;
	for (const StorageEdge& edge: edgesToBundle)
	{
		bool isSource = nodeId == edge.sourceNodeId;
		EdgeInfo edgeInfo;
		edgeInfo.edgeId = edge.id;
		edgeInfo.forward = isSource;
		connectedNodeIds[isSource ? edge.targetNodeId : edge.sourceNodeId].push_back(edgeInfo);
	}

	const std::vector<StorageEdge> outgoingEdges = m_sqliteIndexStorage.getEdgesBySourceIds(
		childNodeIds);
	for (const StorageEdge& outEdge: outgoingEdges)
	{
		EdgeInfo edgeInfo;
		edgeInfo.edgeId = outEdge.id;
		edgeInfo.forward = true;
		connectedNodeIds[outEdge.targetNodeId].push_back(edgeInfo);
	}

	const std::vector<StorageEdge> incomingEdges = m_sqliteIndexStorage.getEdgesByTargetIds(
		childNodeIds);
	for (const StorageEdge& inEdge: incomingEdges)
	{
		EdgeInfo edgeInfo;
		edgeInfo.edgeId = inEdge.id;
		edgeInfo.forward = false;
		connectedNodeIds[inEdge.sourceNodeId].push_back(edgeInfo);
	}

	// get all parent nodes of all connected nodes (up to last level except namespace/undefined)
	const Id nodeParentNodeId = m_hierarchyCache.getLastVisibleParentNodeId(nodeId);

	std::map<Id, std::vector<EdgeInfo>> connectedParentNodeIds;
	for (const std::pair<Id, std::vector<EdgeInfo>>& p: connectedNodeIds)
	{
		const Id parentNodeId = m_hierarchyCache.getLastVisibleParentNodeId(p.first);

		if (parentNodeId != nodeParentNodeId)
		{
			utility::append(connectedParentNodeIds[parentNodeId], p.second);
		}
	}

	// add hierarchies of these parents
	std::vector<Id> nodeIdsToAdd;
	for (const std::pair<Id, std::vector<EdgeInfo>>& p: connectedParentNodeIds)
	{
		const Id bundledEdgesTargetNodeId = p.first;
		if (!graph->getNodeById(bundledEdgesTargetNodeId))
		{
			nodeIdsToAdd.push_back(bundledEdgesTargetNodeId);
		}
	}
	addNodesWithParentsAndEdgesToGraph(nodeIdsToAdd, std::vector<Id>(), graph, true);

	// create bundled edges between parents and active node
	Node* sourceNode = graph->getNodeById(nodeId);
	for (const std::pair<Id, std::vector<EdgeInfo>>& p: connectedParentNodeIds)
	{
		const Id bundledEdgesTargetNodeId = p.first;

		Node* targetNode = graph->getNodeById(bundledEdgesTargetNodeId);
		if (!targetNode)
		{
			LOG_ERROR("Bundled edges target node not present.");
		}

		std::shared_ptr<TokenComponentBundledEdges> componentBundledEdges =
			std::make_shared<TokenComponentBundledEdges>();
		for (const EdgeInfo& edgeInfo: p.second)
		{
			componentBundledEdges->addBundledEdgesId(edgeInfo.edgeId, edgeInfo.forward);
		}

		// Set first bit to 1 to avoid collisions
		const Id bundledEdgesId = ~(~Id(0) >> 1) + *componentBundledEdges->getBundledEdgesIds().begin();

		Edge* edge = graph->createEdge(bundledEdgesId, Edge::EDGE_BUNDLED_EDGES, sourceNode, targetNode);
		edge->addComponent(componentBundledEdges);
	}
}

void PersistentStorage::addFileContentsToGraph(Id fileId, Graph* graph) const
{
	FilePath path = getFileNodePath(fileId);
	if (path.empty())
	{
		return;
	}

	std::vector<Id> tokenIds;
	std::set<Id> tokenIdsSet;

	std::shared_ptr<SourceLocationFile> locationFile =
		m_sqliteIndexStorage.getSourceLocationsForFile(path);
	locationFile->forEachStartSourceLocation([this, &tokenIds, &tokenIdsSet](SourceLocation* location) {
		if (location->getType() != LOCATION_TOKEN)
		{
			return;
		}

		for (Id tokenId: location->getTokenIds())
		{
			if (tokenIdsSet.insert(tokenId).second)
			{
				auto it = m_symbolDefinitionKinds.find(tokenId);
				if (it == m_symbolDefinitionKinds.end() || it->second != DEFINITION_IMPLICIT)
				{
					tokenIds.push_back(tokenId);
				}
			}
		}
	});

	addNodesWithParentsAndEdgesToGraph(tokenIds, {}, graph, true);

	Node* fileNode = graph->getNodeById(fileId);
	Id memberEdgeId = 0;
	for (Id tokenId: tokenIds)
	{
		Id nodeId = m_hierarchyCache.getLastVisibleParentNodeId(tokenId);
		Node* node = graph->getNodeById(nodeId);
		if (node && !node->getMemberEdge())
		{
			// Set first bit to 1 to avoid collisions
			graph->createEdge(~(~Id(0) >> 1) + memberEdgeId++, Edge::EDGE_MEMBER, fileNode, node);
		}
	}

	fileNode->setChildCount(memberEdgeId);
}

void PersistentStorage::addComponentAccessToGraph(Graph* graph) const
{
	TRACE();

	std::vector<Id> nodeIds;
	graph->forEachNode([&nodeIds](Node* node) { nodeIds.push_back(node->getId()); });

	std::vector<StorageComponentAccess> accesses =
		m_sqliteIndexStorage.getComponentAccessesByNodeIds(nodeIds);
	for (const StorageComponentAccess& access: accesses)
	{
		if (access.nodeId != 0)
		{
			graph->getNodeById(access.nodeId)
				->addComponent(std::make_shared<TokenComponentAccess>(intToAccessKind(access.type)));
		}
	}
}

void PersistentStorage::addComponentIsAmbiguousToGraph(Graph* graph) const
{
	TRACE();

	std::vector<Id> edgeIds;
	graph->forEachEdge([&edgeIds](Edge* edge) { edgeIds.push_back(edge->getId()); });

	int componentKind = elementComponentKindToInt(ElementComponentKind::IS_AMBIGUOUS);
	for (const StorageElementComponent& component:
		 m_sqliteIndexStorage.getElementComponentsByElementIds(edgeIds))
	{
		if (component.type == componentKind)
		{
			graph->getEdgeById(component.elementId)
				->addComponent(std::make_shared<TokenComponentIsAmbiguous>());
		}
	}
}

void PersistentStorage::addCompleteFlagsToSourceLocationCollection(
	SourceLocationCollection* collection) const
{
	TRACE();

	collection->forEachSourceLocationFile([this](std::shared_ptr<SourceLocationFile> file) {
		Id fileId = getFileNodeId(file->getFilePath());
		file->setIsComplete(getFileNodeComplete(fileId));
		file->setIsIndexed(getFileNodeIndexed(fileId));
		file->setLanguage(getFileNodeLanguage(fileId));
	});
}

void PersistentStorage::addInheritanceChainsToGraph(const std::vector<Id>& activeNodeIds, Graph* graph) const
{
	TRACE();

	std::set<Id> activeNodeIdsSet;
	for (Id activeNodeId: activeNodeIds)
	{
		std::set<Id> visibleParentIds, edgeIds;
		visibleParentIds.insert(activeNodeId);
		m_hierarchyCache.addAllVisibleParentIdsForNodeId(activeNodeId, &visibleParentIds, &edgeIds);

		for (Id nodeId: visibleParentIds)
		{
			Node* node = graph->getNodeById(nodeId);
			if (node && node->getType().isInheritable())
			{
				activeNodeIdsSet.insert(node->getId());
			}
		}
	}

	std::set<Id> nodeIdsSet;
	graph->forEachNode([&nodeIdsSet, &activeNodeIdsSet](Node* node) {
		if (node->getType().isInheritable() &&
			activeNodeIdsSet.find(node->getId()) == activeNodeIdsSet.end())
		{
			nodeIdsSet.insert(node->getId());
		}
	});

	std::vector<std::set<Id>*> nodeIdSets;
	nodeIdSets.push_back(&activeNodeIdsSet);
	nodeIdSets.push_back(&nodeIdsSet);

	size_t inheritanceEdgeCount = 1;

	for (size_t i = 0; i < nodeIdSets.size(); i++)
	{
		for (const Id nodeId: *nodeIdSets[i])
		{
			for (const std::tuple<Id, Id, std::vector<Id>>& edge:
				 m_hierarchyCache.getInheritanceEdgesForNodeId(nodeId, *nodeIdSets[(i + 1) % 2]))
			{
				const Id sourceId = std::get<0>(edge);
				const Id targetId = std::get<1>(edge);
				const std::vector<Id> edgeIds = std::get<2>(edge);

				if (!edgeIds.size() || (edgeIds.size() == 1 && graph->getEdgeById(edgeIds[0])))
				{
					continue;
				}

				// Set first 2 bits to 1 to avoid collisions
				const Id inheritanceEdgeId = ~(~Id(0) >> 2) + inheritanceEdgeCount++;

				Edge* inheritanceEdge = graph->createEdge(
					inheritanceEdgeId,
					Edge::EDGE_INHERITANCE,
					graph->getNodeById(sourceId),
					graph->getNodeById(targetId));

				inheritanceEdge->addComponent(
					std::make_shared<TokenComponentInheritanceChain>(edgeIds));
			}
		}
	}
}

void PersistentStorage::buildFilePathMaps()
{
	TRACE();

	m_sqliteIndexStorage.forEach<StorageFile>([&](StorageFile&& file) {
		const FilePath path(file.filePath);

		m_fileNodeIds.emplace(path, file.id);
		m_lowerCasefileNodeIds.emplace(path.getLowerCase(), file.id);
		m_fileNodePaths.emplace(file.id, path);
		m_fileNodeComplete.emplace(file.id, file.complete);
		m_fileNodeIndexed.emplace(file.id, file.indexed);
		m_fileNodeLanguage.emplace(file.id, file.languageIdentifier);

		if (!m_hasJavaFiles && path.extension() == L".java")
		{
			m_hasJavaFiles = true;
		}
	});

	m_sqliteIndexStorage.forEach<StorageSymbol>([&](StorageSymbol&& symbol) {
		m_symbolDefinitionKinds.emplace(symbol.id, intToDefinitionKind(symbol.definitionKind));
	});
}

void PersistentStorage::buildSearchIndex()
{
	TRACE();

	const FilePath dbPath = getIndexDbFilePath();

	m_sqliteIndexStorage.forEach<StorageNode>([&](StorageNode&& node) {
		const NodeType type(intToNodeKind(node.type));
		if (type.isFile())
		{
			bool indexed = getFileNodeIndexed(node.id);
			if (!indexed)
			{
				return;
			}

			auto it = m_fileNodePaths.find(node.id);
			if (it != m_fileNodePaths.end())
			{
				FilePath filePath(it->second);

				if (filePath.exists())
				{
					filePath.makeRelativeTo(dbPath);
				}

				m_fileIndex.addNode(node.id, filePath.wstr(), type);
			}
		}
		else
		{
			auto it = m_symbolDefinitionKinds.find(node.id);
			const DefinitionKind defKind =
				(it != m_symbolDefinitionKinds.end() ? it->second : DEFINITION_NONE);
			if (defKind != DEFINITION_IMPLICIT)
			{
				const NameHierarchy nameHierarchy = NameHierarchy::deserialize(node.serializedName);

				// we don't use the signature here, so elements with the same signature share the
				// same node.
				std::wstring name = nameHierarchy.getQualifiedName();

				// replace template arguments with .. to avoid clutter in search results and have
				// different template specializations share the same node.
				if (defKind == DEFINITION_NONE &&
					nameHierarchy.getDelimiter() == nameDelimiterTypeToString(NAME_DELIMITER_CXX))
				{
					name = utility::replaceBetween(name, L'<', L'>', L"..");
				}

				m_symbolIndex.addNode(node.id, std::move(name), type);
			}
		}
	});

	m_symbolIndex.finishSetup();
	m_fileIndex.finishSetup();
}

void PersistentStorage::buildFullTextSearchIndex() const
{
	TRACE();

	TextCodec codec(ApplicationSettings::getInstance()->getTextEncoding());

	m_fullTextSearchCodec = codec.getName();

	m_fullTextSearchIndex.clear();

	std::vector<std::shared_ptr<std::thread>> threads;
	{
		std::vector<StorageFile> indexedFiles;
		for (const StorageFile& file: m_sqliteIndexStorage.getAll<StorageFile>())
		{
			if (file.indexed)
			{
				indexedFiles.push_back(file);
			}
		}
		for (std::vector<StorageFile> part:
			 utility::splitToEquallySizedParts(indexedFiles, utility::getIdealThreadCount()))
		{
			std::shared_ptr<std::thread> thread = std::make_shared<std::thread>(
				[&](const std::vector<StorageFile>& files) {
					for (const StorageFile& file: files)
					{
						m_fullTextSearchIndex.addFile(
							file.id,
							codec.decode(m_sqliteIndexStorage.getFileContentById(file.id)->getText()));
					}
				},
				part);
			threads.push_back(thread);
		}
	}
	for (std::shared_ptr<std::thread> thread: threads)
	{
		thread->join();
	}
}

void PersistentStorage::buildMemberEdgeIdOrderMap()
{
	TRACE();

	if (!m_hasJavaFiles)
	{
		return;
	}

	std::vector<Id> childNodeIds;
	std::unordered_map<Id, Id> childIdToMemberEdgeIdMap;

	m_sqliteIndexStorage.forEachOfType<StorageEdge>(
		Edge::typeToInt(Edge::EDGE_MEMBER),
		[&childNodeIds, &childIdToMemberEdgeIdMap](StorageEdge&& edge) {
			childNodeIds.push_back(edge.targetNodeId);
			childIdToMemberEdgeIdMap.emplace(edge.targetNodeId, edge.id);
		});

	std::vector<Id> locationIds;
	std::unordered_map<Id, Id> locationIdToElementIdMap;
	for (const StorageOccurrence& occurrence:
		 m_sqliteIndexStorage.getOccurrencesForElementIds(childNodeIds))
	{
		locationIds.push_back(occurrence.sourceLocationId);
		locationIdToElementIdMap.emplace(occurrence.sourceLocationId, occurrence.elementId);
	}

	SourceLocationCollection collection;
	for (const StorageSourceLocation& location:
		 m_sqliteIndexStorage.getAllByIds<StorageSourceLocation>(locationIds))
	{
		const LocationType locType = intToLocationType(location.type);
		if (locType != LOCATION_TOKEN)
		{
			continue;
		}

		const FilePath path(m_fileNodePaths[location.fileNodeId]);
		if (path.extension() == L".java")
		{
			collection.addSourceLocation(
				intToLocationType(location.type),
				location.id,
				std::vector<Id>(),
				FilePath(std::to_wstring(location.fileNodeId)),
				location.startLine,
				location.startCol,
				location.endLine,
				location.endCol);
		}
	}

	// Set first 3 bits to 1 to avoid collisions
	Id baseId = ~(~Id(0) >> 3) + 1;

	collection.forEachSourceLocation([&](SourceLocation* location) {
		auto it = locationIdToElementIdMap.find(location->getLocationId());
		if (it != locationIdToElementIdMap.end())
		{
			auto it2 = childIdToMemberEdgeIdMap.find(it->second);
			if (it2 != childIdToMemberEdgeIdMap.end())
			{
				if (m_memberEdgeIdOrderMap.emplace(it2->second, baseId).second)
				{
					baseId++;
				}
			}
		}
	});
}

void PersistentStorage::buildHierarchyCache()
{
	TRACE();

	std::vector<Id> sourceNodeIds;
	std::vector<StorageEdge> memberEdges;

	m_sqliteIndexStorage.forEachOfType<StorageEdge>(
		Edge::typeToInt(Edge::EDGE_MEMBER), [&sourceNodeIds, &memberEdges](StorageEdge&& edge) {
			sourceNodeIds.push_back(edge.sourceNodeId);
			memberEdges.emplace_back(edge);
		});

	std::set<Id> invisibleParentSourceNodeIds;

	m_sqliteIndexStorage.forEachByIds<StorageNode>(
		sourceNodeIds, [&invisibleParentSourceNodeIds](StorageNode&& node) {
			if (!NodeType(intToNodeKind(node.type)).isVisibleAsParentInGraph())
			{
				invisibleParentSourceNodeIds.insert(node.id);
			}
		});

	for (const StorageEdge& edge: memberEdges)
	{
		bool sourceIsVisible = true;
		if (invisibleParentSourceNodeIds.find(edge.sourceNodeId) != invisibleParentSourceNodeIds.end())
		{
			sourceIsVisible = false;
		}

		bool sourceIsImplicit = false;
		auto it = m_symbolDefinitionKinds.find(edge.sourceNodeId);
		if (it != m_symbolDefinitionKinds.end())
		{
			sourceIsImplicit = (it->second == DEFINITION_IMPLICIT);
		}

		bool targetIsImplicit = false;
		it = m_symbolDefinitionKinds.find(edge.targetNodeId);
		if (it != m_symbolDefinitionKinds.end())
		{
			targetIsImplicit = (it->second == DEFINITION_IMPLICIT);
		}

		m_hierarchyCache.createConnection(
			edge.id,
			edge.sourceNodeId,
			edge.targetNodeId,
			sourceIsVisible,
			sourceIsImplicit,
			targetIsImplicit);
	}

	m_sqliteIndexStorage.forEachOfType<StorageEdge>(
		Edge::typeToInt(Edge::EDGE_INHERITANCE), [this](StorageEdge&& edge) {
			m_hierarchyCache.createInheritance(edge.id, edge.sourceNodeId, edge.targetNodeId);
		});
}