/*
* Copyright 2018 Coati Software KG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef SOURCETRAIL_SRCTRLDB_WRITER_H
#define SOURCETRAIL_SRCTRLDB_WRITER_H

#include <memory>
#include <string>

#include "DefinitionKind.h"
#include "EdgeKind.h"
#include "ElementComponentKind.h"
#include "LocationKind.h"
#include "NameHierarchy.h"
#include "ReferenceKind.h"
#include "SourceRange.h"
#include "SymbolKind.h"

namespace sourcetrail
{
	class DatabaseStorage;

	/**
	 * SourcetrailDBWriter
	 *
	 * This class is the main interface for writing data to a Sourcetrail project database.
	 * It be used to manage a Sourcetrail database file and to write information to it.
	 *
	 * The following code snippet illustrates a very basic usage of the SourcetrailDBWriter class:
	 *
	 *   sourcetrail::SourcetrailDBWriter writer;
	 *   writer.open("MyProject.srctrldb");
	 *   writer.recordSymbol({ "::",{ { "void", "foo", "()" } } });
	 *   writer.close();
	 */
	class SourcetrailDBWriter
	{
	public:
		SourcetrailDBWriter();
		~SourcetrailDBWriter();

		/**
		 * Provides the version of the SourcetrailDB Core as string with format "vXX.dbYY.pZZ"
		 *
		 *  - XX: interface version. This version increases on every change that breaks backwards
		 *        compatibility.
		 *  - YY: Sourcetrail database version. This version needs to match the database version
		 *        of the used Sourcetrail instance. You can find the database version of Sourcetrail
		 *        in its About dialog.
		 *  - ZZ: patch number of the build. It will increase with every release that publishes
		 *        bugfixes and features that don't break any compatibility.
		 *
		 *  return: version string
		 */
		std::string getVersionString() const;

		/**
		 * Provides the supported database version as integer
		 *
		 *  return: supported database version
		 *
		 *  see: getVersionString() for details
		 */
		int getSupportedDatabaseVersion() const;

		/**
		 * Provides the last error that occurred while using the SourcetrailDBWriter
		 *
		 * The last error is empty if no error occurred since instantiation of the class or since the
		 * error has last been cleared.
		 *
		 *  return: error message of last error that occured
		 *
		 *  see: clearLastError()
		 */
		const std::string& getLastError() const;

		/**
		 * Modifies the stored error message
		 *
		 *  param: error - the new error message
		 *
		 *  see: getLastError()
		 */
		void setLastError(const std::string& error) const;

		/**
		 * Clears the stored error message
		 *
		 *  see: getLastError()
		 */
		void clearLastError();

		/**
		 * Opens a Sourcetrail database
		 *
		 * Call this method to open a Sourcetrail database file. If the database does not have any
		 * related .srctrlprj project file, a minimal project file will be created that allows for
		 * opening the database with Sourcetrail.
		 *
		 *  param: databaseFilePath - absolute file path of the database file, including file extension
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 */
		bool open(const std::string& databaseFilePath);

		/**
		 * Closes the currently open Sourcetrail database
		 *
		 *  return: Returns true if the operation was successful. Otherwise false is returned and getLastError()
		 *    can be checked for more detailed information.
		 *
		 *  see: open(const std::string& databaseFilePath)
		 */
		bool close();

		/**
		 * Clears the currently open Sourcetrail database
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: open(const std::string& databaseFilePath)
		 */
		bool clear();

		/**
		 * Checks if the currently open database file contains any data
		 *
		 *  return: true after opening a non-existing database file or clearing the open database
		 */
		bool isEmpty() const;

		/**
		 * Checks if the currently open database is compatible with the SourcetrailDBWriter version.
		 *
		 *  return: true for an empty database or a database that has been created with the same
		 *   database version.
		 *
		 *  see: getSupportedDatabaseVersion()
		 *  see: getLoadedDatabaseVersion()
		 */
		bool isCompatible() const;

		/**
		 * Provides the database version of the loaded database file as an integer
		 *
		 *  return: database version of loaded database
		 */
		int getLoadedDatabaseVersion() const;

		/**
		 * Starts a database transaction on the currently open database
		 *
		 * Apart from allowing to restore the database to the state it was in before this method has
		 * been called, wrapping multiple calls that record data with one transaction significantly
		 * increases the performance of these database operations.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: commitTransaction()
		 *  see: rollbackTransaction()
		 */
		bool beginTransaction();

		/**
		 * Ends the current transaction and writes all of its changes persistently to the database
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 */
		bool commitTransaction();

		/**
		 * Reverts the database to the state it was in before the current transaction was started
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 */
		bool rollbackTransaction();

		/**
		 * Reduces the on disk memory consumption of the open database to a minimum
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 */
		bool optimizeDatabaseMemory();

		/**
		 * Stores a symbol to the database
		 *
		 *  note: Calling this method multiple times with the same input on the same Sourcetrail
		 *    database will always return the same id.
		 *
		 *  param: nameHierarchy - the name of the symbol to store.
		 *
		 *  return: symbolId - integer id of the stored symbol. 0 on failure. getLastError()
		 *    provides the error message.
		 *
		 *  see: NameHierarchy
		 */
		int recordSymbol(const NameHierarchy& nameHierarchy);

		/**
		 * Stores a definition kind for a specific symbol to the database
		 *
		 * Calling this method allows to store a DefinitionKind (e.g. explicitly defined, implicitly
		 * defined) for a symbol referenced by id to the database. Calling this method with the same
		 * symbolId multiple times overwrites the symbol's previously recorded DefinitionKind. If no
		 * DefinitionKind is recorded for a symbol, Sourcetrail treats this symbol as "non-indexed".
		 * This may be desired while recording a reference to a symbol with a definition that is located
		 * outside of the indexed source files.
		 *
		 *  param: symbolId - the id of the symbol for which a DefinitionKind shall be recorded.
		 *  param: definitionKind - the DefinitionKind that shall be recorded for the respective symbol.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: DefinitionKind
		 */
		bool recordSymbolDefinitionKind(int symbolId, DefinitionKind definitionKind);

		/**
		 * Stores a symbol kind for a specific symbol to the database
		 *
		 * Calling this method allows to store a SymbolKind (e.g. "class", "function", etc.) for a
		 * symbol referenced by id to the database. Calling this method with the same symbolId multiple
		 * times overwrites the symbol's previously recorded SymbolKind. If no SymbolKind is recorded
		 * for a symbol, Sourcetrail displays the type of this symbol as "symbol".
		 *
		 *  param: symbolId - the id of the symbol for which a SymbolKind shall be recorded.
		 *  param: symbolKind - the SymbolKind that shall be recorded for the respective symbol.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: SymbolKind
		 */
		bool recordSymbolKind(int symbolId, SymbolKind symbolKind);

		/**
		 * Stores a location for a specific symbol to the database
		 *
		 * This method allows to store a location for a symbol referenced by id to the database.
		 * Calling this method with the same symbolId multiple times adds multiple locations for the
		 * respective symbol. The stored location will be clickable and displayable. When clicked
		 * Sourcetrail will activate the symbol with the specified id. When the symbol with the specified
		 * id is activated, this location will be displayed and highlighted by Sourcetrail.
		 *
		 *  param: symbolId - the id of the symbol for which a location shall be recorded.
		 *  param: location - the SourceRange that shall be recorded as location for the respective symbol.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: SourceRange
		 */
		bool recordSymbolLocation(int symbolId, const SourceRange& location);

		/**
		 * Stores a scope location for a specific symbol to the database
		 *
		 * This method allows to store a scope location for a symbol referenced by id to the database.
		 * Calling this method with the same symbolId multiple times adds multiple scope locations for the
		 * respective symbol. The stored location will only be displayable and not clickable. When the
		 * symbol with the specified id is activated, this location will be fully displayed but not
		 * highlighted by Sourcetrail.
		 *
		 *  param: symbolId - the id of the symbol for which a scope location shall be recorded.
		 *  param: location - the SourceRange that shall be recorded as scope location for the respective
		 *    symbol.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: SourceRange
		 */
		bool recordSymbolScopeLocation(int symbolId, const SourceRange& location);

		/**
		 * Stores a signature location for a specific symbol to the database
		 *
		 * This method allows to store a signature location for a symbol referenced by id to the
		 * database. If a signature location is recorded for a symbol, Sourcetrail will display the
		 * respective source code in a tooltip whenever the symbol with the referenced id or any of
		 * that symbol's locations gets hovered.
		 * If no signature location is recorded for a symbol, Sourcetrail will show its name hierarchy.
		 *
		 *  note: Calling this method with the same symbolId multiple times adds multiple signature
		 *    locations for the respective symbol. It is not guaranteed which one will be used, so it is
		 *    advised to call it only once per symbol.
		 *
		 *  param: symbolId - the id of the symbol for which a signature location shall be recorded.
		 *  param: location - the SourceRange that shall be recorded as signature location for the
		 *    respective symbol.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: SourceRange
		 */
		bool recordSymbolSignatureLocation(int symbolId, const SourceRange& location);

		/**
		 * Stores a reference between two symbols to the database
		 *
		 * This method allows to store the information of symbols referencing one another to the database.
		 * For each recorded reference Sourcetrail's graph view will display an edge that originates at
		 * the reference's recorded context symbol and points to the recorded referenced symbol. The
		 * recorded ReferenceKind is used to determine the type of the displayed edge and to generate a
		 * description in the hover tooltip of the edge.
		 *
		 *  note: Calling this method multiple times with the same input on the same Sourcetrail database
		 *    will always return the same id.
		 *
		 *  param: contextSymbolId - the id of the source of the recorded reference edge
		 *  param: referencedSymbolId - the id of the target of the recorded reference edge
		 *  param: referenceKind - kind of the recorded reference edge
		 *
		 *  return: referenceId - integer id of the stored reference. 0 on failure. getLastError()
		 *    provides the error message.
		 *
		 *  see: ReferenceKind
		 */
		int recordReference(int contextSymbolId, int referencedSymbolId, ReferenceKind referenceKind);

		/**
		 * Stores a location for a specific reference to the database
		 *
		 * This method allows to store a location for a reference to the database.
		 * Calling this method with the same referenceId multiple times adds multiple locations for the
		 * respective reference. The stored location will be clickable and displayable. When the reference
		 * location is clicked Sourcetrail will activate the symbol referenced by the respective reference.
		 * When the reference with the specified id is activated, this location will be displayed and
		 * highlighted by Sourcetrail.
		 *
		 *  param: referenceId - the id of the reference for which a location shall be recorded.
		 *  param: location - the SourceRange that shall be recorded as location for the respective
		 *    reference.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: SourceRange
		 */
		bool recordReferenceLocation(int referenceId, const SourceRange& location);

		/**
		* Marks a reference that is stored in the database as "ambiguous"
		*
		* This method allows to additional information for a reference to the database. Sourcetrail will
		* display an "ambiguous" reference with a special style to emphasize that the existance of the
		* reference is questionable. This method is intended to be called in situations when an indexed
		* token may have meanings, all of which shall be recorded.
		*
		*  param: referenceId - the id of the reference that shall be marked as ambiguous.
		*
		*  return: true if successful. false on failure. getLastError() provides the error message.
		*/
		bool recordReferenceIsAmbiguous(int referenceId);

		/**
		* Stores a location between a specific context and an "unsolved" symbol to the database
		*
		* This method allows to store all available information to the database in the case that a symbol
		* is referenced in a certain context but the referenced symbol could not be resolved to a concrete
		* name. For each reference recorded by this method, Sourcetrail's graph view will display an edge
		* that originates at the recorded context symbol and points to a node called "unsolved symbol".
		* Furthermore Sourcetrail's code view will use a different highlight when the provided source range
		* gets hovered.
		*
		*  param: contextSymbolId - the id of the source of the recorded reference edge
		*  param: referenceKind - kind of the recorded reference edge
		*  param: location - the SourceRange that shall be recorded as location for the respective
		*    reference.
		*
		*  return: referenceId - integer id of the stored reference. 0 on failure. getLastError()
		*    provides the error message.
		*
		*  see: SourceRange
		*/
		int recordReferenceToUnsolvedSymhol(int contextSymbolId, ReferenceKind referenceKind, const SourceRange& location);

		/**
		* Stores a location for the usage of a symbol's name as qualifier to the database
		*
		* This method allows to store a location where a specific symbol is used as a qualifier to the
		* database. Calling this method with the same referencedSymbolId multiple times adds multiple locations
		* for the respective reference. The stored location will be clickable but not displayable: When the
		* reference location is clicked Sourcetrail will activate the symbol referenced by the respective
		* reference. When the symbol with the specified id is activated, this location will NOT be displayed and
		* highlighted by Sourcetrail.
		*
		*  param: referencedSymbolId - the id of the symbol that is used as qualifier at the current location.
		*  param: location - the SourceRange that shall be recorded as location for the symbol's occurrence as
		*    qualifier
		*
		*  return: true if successful. false on failure. getLastError() provides the error message.
		*
		*  see: SourceRange
		*/
		bool recordQualifierLocation(int referencedSymbolId, const SourceRange& location);

		/**
		 * Stores a file to the database
		 *
		 *  note: Calling this method multiple times with the same input on the same Sourcetrail database
		 *    will always return the same id.
		 *
		 *  param: filePath - the absolute path to the file to store.
		 *
		 *  return: fileId - integer id of the stored file. 0 on failure. getLastError() provides the
		 *    error message.
		 */
		int recordFile(const std::string& filePath);

		/**
		 * Stores language information for a specific file to the database
		 *
		 * This method allows to store language information for a file to the database. Language information
		 * is passed and stored as string (e.g. "cpp", "java", etc.) and allows Sourcetrail to pick the
		 * correct syntax highlighting rules when displaying the respective source file.
		 *
		 *  param: fileId - the id of the file for which language information shall be recorded.
		 *  param: languageIdentifier - a string that denotes the programming language the respective file
		 *    is written in.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 */
		bool recordFileLanguage(int fileId, const std::string& languageIdentifier);

		/**
		 * Stores a local symbol to the database
		 *
		 *  note: Calling this method multiple times with the same input on the same Sourcetrail database
		 *    will always return the same id.
		 *
		 *  param: name - a name that is unique for this local symbol (e.g. the string encoded location
		 *    of the local symbol's definition).
		 *
		 *  return: localSymbolId - integer id of the stored local symbol. 0 on failure. getLastError()
		 *    provides the error message.
		 */
		int recordLocalSymbol(const std::string& name);

		/**
		 * Stores a location for a specific local symbol to the database
		 *
		 * This method allows to store a location for a local symbol symbol referenced by id to the database.
		 * Calling this method with the same symbolId multiple times adds multiple locations for the
		 * respective local symbol. The stored location will be clickable and displayable. When clicked
		 * Sourcetrail will activate this and all other local symbol locations that share the same local
		 * symbol id.
		 *
		 *  param: localSymbolId - the id of the local symbol for which a location shall be recorded.
		 *  param: location - the SourceRange that shall be recorded as location for the respective
		 *    local symbol.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: SourceRange
		 */
		bool recordLocalSymbolLocation(int localSymbolId, const SourceRange& location);

		/**
		 * Stores an atomic SourceRange to the database
		 *
		 * This method allows to store an atomic SourceRange to the database. These ranges will
		 * be used by Sourcetrail to prevent the code view from displaying only a part of the range. Thus,
		 * if any line that lies within one of the project's atomic ranges is dispayed, the remaining
		 * lines will be displayed as well. This may be useful for dealing with multi-line comments or
		 * multi-line strings.
		 *
		 *  param: sourceRange - the SourceRange to record.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: SourceRange
		 */
		bool recordAtomicSourceRange(const SourceRange& sourceRange);

		/**
		 * Stores an indexing error to the database
		 *
		 * This method allows to store an indexing error to the database. Errors will be shown by
		 * Sourcetrail's error view. When clicking the error in Sourcetrail's error list, the code view
		 * will display this location.
		 *
		 *  param: message - an error message that will be displayed in Sourcetrail's error list.
		 *  param: fatal - boolean fatal indicates whether parsing/indexing was aborted at this point.
		 *  param: location - the SourceRange of the error encountered.
		 *
		 *  return: true if successful. false on failure. getLastError() provides the error message.
		 *
		 *  see: SourceRange
		 */
		bool recordError(const std::string& message, bool fatal, const SourceRange& location);

	private:
		void openDatabase();
		void closeDatabase();
		void setupDatabaseTables();
		void clearDatabaseTables();
		void createOrResetProjectFile();
		void updateProjectSettingsText();
		int addNodeHierarchy(const NameHierarchy& nameHierarchy);
		int addFile(const std::string& filePath);
		int addEdge(int sourceId, int targetId, EdgeKind edgeKind);
		void addSourceLocation(int elementId, const SourceRange& location, LocationKind kind);
		void addElementComponent(int elementId, ElementComponentKind kind, const std::string& data);

		std::string m_projectFilePath;
		std::string m_databaseFilePath;
		std::unique_ptr<DatabaseStorage> m_storage;
		mutable std::string m_lastError;
	};
}

#endif // SOURCETRAIL_SRCTRLDB_WRITER_H
