#include <cstdlib>
#include <fstream>
#include <iostream>
#include <regex>

#include "SourcetrailDBWriter.h"
#include "NameHierarchy.h"
#include "SourceRange.h"

sourcetrail::NameHierarchy toNameHierarchy(const std::vector<std::string>& strs)
{
	sourcetrail::NameHierarchy name = { " - ", { } };
	for (auto str : strs)
	{
		if (str.size())
		{
			name.nameElements.emplace_back( sourcetrail::NameElement({ "", str, "" }) );
		}
	}
	return name;
}

int main(int argc, const char *argv[])
{
	sourcetrail::SourcetrailDBWriter dbWriter;

	std::cout << "\nSourcetrailDB Poetry Indexer" << std::endl;
	std::cout << std::endl;
	std::cout << "SourcetrailDB version: " << dbWriter.getVersionString() << std::endl;
	std::cout << "Supported database version: " << dbWriter.getSupportedDatabaseVersion() << std::endl;
	std::cout << std::endl;

	if (argc < 3 || argc > 4)
	{
		std::cout << "usage: poetry_indexer <database_path> <source_path> <optional:database_version>";
	}

	std::string dbPath = argv[1];
	std::string sourcePath = argv[2];

	int dbVersion = 0;
	if (argc == 4)
	{
		char* end;
		dbVersion = strtol(argv[3], &end, 10);
	}

	if (dbVersion && dbVersion != dbWriter.getSupportedDatabaseVersion())
	{
		std::cerr << "error: binary only supports database version: " << dbWriter.getSupportedDatabaseVersion()
			<< ". Requested version: " << dbVersion << std::endl;
		return 1;
	}

	std::ifstream fileStream(sourcePath.c_str());
	if (!fileStream.good())
	{
		std::cerr << "error: opening file failed: " << sourcePath << std::endl;
		return 1;
	}

	std::cout << "Opening Database: " << dbPath << std::endl;
	if (!dbWriter.open(dbPath))
	{
		std::cerr << "error: " << dbWriter.getLastError() << std::endl;
		return 1;
	}

	std::cout << "Starting Indexing: " << sourcePath << std::endl;

	if (!dbWriter.beginTransaction())
	{
		std::cerr << "error: " << dbWriter.getLastError() << std::endl;
		return 1;
	}

	int fileId = dbWriter.recordFile(sourcePath);
	if (fileId == 0)
	{
		std::cerr << "error: " << dbWriter.getLastError() << std::endl;
		return 1;
	}

	std::string author;

	struct {
		int id = 0;
		std::string name;
	} poem;

	struct {
		int id = 0;
		int number = 0;
		sourcetrail::SourceRange range;
	} paragraph;

	struct {
		int number = 0;
		std::string content;
	} line;

	int symbolId = 0;

	// foreach poem line
	while (getline(fileStream, line.content, '\n'))
	{
		line.number++;

		// first line: author
		// second line: title
		if (line.number <= 2)
		{
			// store the author as namespace, the title as type
			symbolId = dbWriter.recordSymbol(toNameHierarchy({ author, line.content }));
			dbWriter.recordSymbolDefinitionKind(symbolId, sourcetrail::DefinitionKind::EXPLICIT);
			dbWriter.recordSymbolKind(symbolId, line.number == 1 ? sourcetrail::SymbolKind::NAMESPACE : sourcetrail::SymbolKind::TYPE);

			sourcetrail::SourceRange range = { fileId, line.number, 1, line.number, static_cast<int>(line.content.size()) };
			dbWriter.recordSymbolLocation(symbolId, range);

			if (line.number == 1)
			{
				author = line.content;
			}
			else
			{
				poem.id = symbolId;
				poem.name = line.content;
			}

			continue;
		}
		else if (line.content.size() <= 1) // empty line
		{
			if (paragraph.id) // if paragraph exists then close it
			{
				dbWriter.recordSymbolScopeLocation(paragraph.id, paragraph.range);
				dbWriter.recordSymbolSignatureLocation(paragraph.id, paragraph.range);
				paragraph.id = 0;
			}
		}
		else if (!paragraph.id) // start new paragraph
		{
			paragraph.number++;

			// store each paragraph as method
			paragraph.id = dbWriter.recordSymbol(toNameHierarchy({ author, poem.name, "paragraph " + std::to_string(paragraph.number) }));
			dbWriter.recordSymbolDefinitionKind(paragraph.id, sourcetrail::DefinitionKind::EXPLICIT);
			dbWriter.recordSymbolKind(paragraph.id, sourcetrail::SymbolKind::METHOD);
			paragraph.range = { fileId, line.number, 1, 0, 0 };
			symbolId = paragraph.id;
		}

		if (line.content.size() > 1)
		{
			// match every word in the line
			std::string str = line.content;
			std::smatch match;
			std::regex regExp("[\\w']+");
			size_t pos = 0;

			while (std::regex_search(str, match, regExp))
			{
				std::string m = match[0];
				std::string word;
				std::transform(m.begin(), m.end(), std::back_inserter(word), tolower);

				int wordPos = match.position(0) + 1;
				int wordLen = match.length(0);

				// store each word as global variable
				int wordId = dbWriter.recordSymbol(toNameHierarchy({ word }));
				dbWriter.recordSymbolDefinitionKind(wordId, sourcetrail::DefinitionKind::EXPLICIT);
				dbWriter.recordSymbolKind(wordId, sourcetrail::SymbolKind::GLOBAL_VARIABLE);

				// create a reference between word and paragraph
				int referenceId = dbWriter.recordReference(symbolId, wordId, sourcetrail::ReferenceKind::USAGE);
				sourcetrail::SourceRange range = { fileId, line.number, static_cast<int>(pos + wordPos), line.number, static_cast<int>(pos + wordPos + wordLen) - 1 };
				dbWriter.recordReferenceLocation(referenceId, range);

				str = match.suffix();
				pos += wordPos + wordLen - 1;
			}
		}

		paragraph.range.endLine = line.number;
		paragraph.range.endColumn = line.content.size();
	}

	if (paragraph.id)
	{
		dbWriter.recordSymbolScopeLocation(paragraph.id, paragraph.range);
		dbWriter.recordSymbolSignatureLocation(paragraph.id, paragraph.range);
		paragraph.id = 0;
	}

	sourcetrail::SourceRange range = { fileId, 2, 1, line.number, int(line.content.size()) };
	dbWriter.recordSymbolScopeLocation(poem.id, range);

	fileStream.close();

	if (!dbWriter.commitTransaction())
	{
		std::cerr << "error: " << dbWriter.getLastError() << std::endl;
		return 1;
	}

	if (dbWriter.getLastError().size())
	{
		std::cerr << "error: " << dbWriter.getLastError() << std::endl;
		return 1;
	}

	std::cout << "done!" << std::endl;

	return 0;
}
