/*
*   Copyright (c) 2019, Jiri Techet
*
*   This source code is released for free distribution under the terms of the
*   GNU General Public License version 2 or (at your option) any later version.
*
*   Provides a simple application using ctags as a library and using the same
*   set of ctags functions as the Geany editor
*/

#include "general.h"  /* must always come first */

#include "types.h"
#include "routines.h"
#include "mio.h"
#include "error_p.h"
#include "writer_p.h"
#include "parse_p.h"
#include "options_p.h"
#include "trashbox_p.h"
#include "field_p.h"
#include "xtag_p.h"
#include "entry_p.h"
#include "param_p.h"

#include <stdio.h>
#include <string.h>
#include <errno.h>

static int writeEntry (tagWriter *writer, MIO * mio, const tagEntryInfo *const tag, void *clientData);
static void rescanFailed (tagWriter *writer, unsigned long validTagNum, void *clientData);

/* we need to be able to provide a custom writer using which we collect the tags */
tagWriter customWriter = {
	.writeEntry = writeEntry,
	.writePtagEntry = NULL,
	.preWriteEntry = NULL,
	.postWriteEntry = NULL,
	.rescanFailedEntry = rescanFailed,
	.treatFieldAsFixed = NULL,
	.defaultFileName = "tags_file_which_should_never_appear_anywhere",
	.private = NULL,
};


/* we need to be able to provide an error printer which doesn't crash Geany on error */
CTAGS_ATTR_PRINTF(2, 0)
static bool nofatalErrorPrinter (const errorSelection selection,
					  const char *const format,
					  va_list ap, void *data CTAGS_ATTR_UNUSED)
{
	fprintf (stderr, "%s: ", (selection & WARNING) ? "Warning: " : "Error");
	vfprintf (stderr, format, ap);
	if (selection & PERROR)
#ifdef HAVE_STRERROR
		fprintf (stderr, " : %s", strerror (errno));
#else
		perror (" ");
#endif
	fputs ("\n", stderr);

	return false;
}


static void enableRoles(unsigned int lang, unsigned int kind)
{
	unsigned int c = countLanguageRoles(lang, kind);

	for (unsigned int i = 0; i < c; i++)
	{
		roleDefinition* rdef = getLanguageRole(lang, kind, (int)i);
		enableRole(rdef, true);
	}
}


/* we need to be able to enable all kinds and roles for all languages (some are disabled by default) */
static void enableKindsAndRoles(void)
{
	unsigned int lang;

	for (lang = 0; lang < countParsers(); lang++)
	{
		unsigned int kindNum = countLanguageKinds(lang);
		unsigned int kind;

		for (kind = 0; kind < kindNum; kind++)
		{
			kindDefinition *def = getLanguageKind(lang, kind);
			enableKind(def, true);
			enableRoles(lang, kind);
		}
	}
}


/* we need to be able to initialize ctags like in main() but skipping some things */
static void ctagsInit(void)
{
	initDefaultTrashBox ();

	setErrorPrinter (nofatalErrorPrinter, NULL);
	setTagWriter (WRITER_CUSTOM, &customWriter);

	checkRegex ();
	initFieldObjects ();
	initXtagObjects ();

	initializeParsing ();
	initOptions ();
	initRegexOptscript ();

	/* make sure all parsers are initialized */
	initializeParser (LANG_AUTO);

	/* change default value which is false */
	enableXtag(XTAG_TAGS_GENERATED_BY_GUEST_PARSERS, true);
	enableXtag(XTAG_REFERENCE_TAGS, true);

	/* some kinds and roles we are interested in are disabled by default */
	enableKindsAndRoles();
}


/* we need to be able to get a name of a given language */
static const char *ctagsGetLangName(int lang)
{
	return getLanguageName(lang);
}


/* we need to be able to get an int representing a given language */
static int ctagsGetNamedLang(const char *name)
{
	return getNamedLanguage(name, 0);
}


/* we need to be able to get kind letters provided by a given language */
static const char *ctagsGetLangKinds(int lang)
{
	unsigned int kindNum = countLanguageKinds(lang);
	static char kinds[257];
	unsigned int i;

	for (i = 0; i < kindNum; i++)
		kinds[i] = getLanguageKind(lang, i)->letter;
	kinds[i] = '\0';

	return kinds;
}


/* we need to be able to get kind name from a kind letter for a given language */
static const char *ctagsGetKindName(char kind, int lang)
{
	kindDefinition *def = getLanguageKindForLetter (lang, kind);
	return def ? def->name : "unknown";
}


/* we need to be able to get kind letter from a kind name for a given language */
static char ctagsGetKindFromName(const char *name, int lang)
{
	kindDefinition *def = getLanguageKindForName (lang, name);
	return def ? def->letter : '-';
}


/* we need to be able to get kind letter from a kind index */
static char ctagsGetKindFromIndex(int index, int lang)
{
	return getLanguageKind(lang, index)->letter;
}


/* we need to be able to get the number of parsers */
static unsigned int ctagsGetLangCount(void)
{
	return countParsers();
}


static void addIgnoreSymbol(const char *value)
{
	langType lang = getNamedLanguage ("CPreProcessor", 0);
	/*
	 * In the future, we will rename applyParameter to
	 * applyLanguageParam.
	 */
	applyParameter (lang, "ignore", value);
}

/*******************************************************************************
 * So let's just use what we have for our great client...
 ******************************************************************************/

/* our internal tag representation - this is all the tag information we use in Geany */
typedef struct {
	char *name;
	char *signature;
	char *scopeName;
	char *inheritance;
	char *varType;
	char *access;
	char *implementation;
	char kindLetter;
	bool isFileScope;
	unsigned long lineNumber;
	int lang;
	bool isAnon;
} Tag;


static Tag *createTag(const tagEntryInfo *info)
{
	Tag *tag = xCalloc(1, Tag);
	if (info->name)
		tag->name = eStrdup(info->name);
	if (info->extensionFields.signature)
		tag->signature = eStrdup(info->extensionFields.signature);
	if (info->extensionFields.scopeName)
		tag->scopeName = eStrdup(info->extensionFields.scopeName);
	if (info->extensionFields.inheritance)
		tag->inheritance = eStrdup(info->extensionFields.inheritance);
	if (info->extensionFields.typeRef[1])
		tag->varType = eStrdup(info->extensionFields.typeRef[1]);
	if (info->extensionFields.access)
		tag->access = eStrdup(info->extensionFields.access);
	if (info->extensionFields.implementation)
		tag->implementation = eStrdup(info->extensionFields.implementation);
	tag->kindLetter = ctagsGetKindFromIndex(info->kindIndex, info->langType);
	tag->isFileScope = info->isFileScope;
	tag->lineNumber = info->lineNumber;
	tag->lang = info->langType;
	tag->isAnon = isTagExtraBitMarked(info, XTAG_ANONYMOUS);
	return tag;
}


static void destroyTag(Tag *tag)
{
	if (tag->name)
		eFree(tag->name);
	if (tag->signature)
		eFree(tag->signature);
	if (tag->scopeName)
		eFree(tag->scopeName);
	if (tag->inheritance)
		eFree(tag->inheritance);
	if (tag->varType)
		eFree(tag->varType);
	if (tag->access)
		eFree(tag->access);
	if (tag->implementation)
		eFree(tag->implementation);
	eFree(tag);
}


/* callback from ctags informing us about a new tag */
static int writeEntry (tagWriter *writer, MIO *mio, const tagEntryInfo *const tag, void *clientData)
{
	Tag *t;

	/* apparently we have to call this to get the scope info - maybe we can
	 * specify some option during initialization so we don't have to cal this
	 * ?????? */
	getTagScopeInformation((tagEntryInfo *)tag, NULL, NULL);

	/* convert tags into our internal representation and store them into an array */
	t = createTag(tag);
	ptrArrayAdd((ptrArray *)clientData, t);

	/* output length - we don't write anything to the MIO */
	return 0;
}


/* scan has failed so we have invalid tags in our array - validTagNum should
 * tell us the number of valid tags so remove all the extra tags and shrink the array */
static void rescanFailed (tagWriter *writer, unsigned long validTagNum, void *clientData)
{
	ptrArray *tagArray = clientData;
	int num = ptrArrayCount(tagArray);

	if (num > validTagNum)
	{
		int i;
		for (i = validTagNum; i < num; i++)
		{
			Tag *tag = ptrArrayLast(tagArray);
			destroyTag(tag);
			ptrArrayRemoveLast(tagArray);
		}
	}
}


/* do whatever we want to do with the tags */
static void processCollectedTags(ptrArray *tagArray)
{
	int i;
	int num = ptrArrayCount(tagArray);

	for (i = 0; i < num; i++)
	{
		Tag *tag = ptrArrayItem(tagArray, i);
		printf("%s\tline: %lu\tkind: %s\t lang: %s\n",
			tag->name,
			tag->lineNumber,
			ctagsGetKindName(tag->kindLetter, tag->lang),
			ctagsGetLangName(tag->lang));
	}

	/* prepare for the next parsing by clearing the tag array */
	ptrArrayClear(tagArray);
}


extern int main (int argc, char **argv)
{
	/* called once when Geany starts */
	ctagsInit();

	/* create empty tag array *
	 * this is where we store the collected tags
	 * NOTE: Geany doesn't use the ptrArray type - it is used just for the purpose of this demo */
	ptrArray *tagArray = ptrArrayNew((ptrArrayDeleteFunc)destroyTag);

	printf("This parser only parses C files - provide them as arguments on the "
			"command line or get a hard-coded buffer parsed when no arguments "
			"are provided\n\n");
	if (argc == 1)  /* parsing contents of a buffer */
	{
		char *program = "FOO int foo() {}\n\n int bar() {}\n\n int main() {}\n";
		int lang = ctagsGetNamedLang("C");
		const char *kinds = ctagsGetLangKinds(lang);
		int i;

		/* we need to be able to set and clear ignore symbols */
		addIgnoreSymbol("int");
		/* clear */
		addIgnoreSymbol(NULL);
		/* set to something else */
		addIgnoreSymbol("FOO");

		printf("The total number of parsers is: %d\n", ctagsGetLangCount());
		printf("We are parsing %s which provides the following kinds:\n",
			ctagsGetLangName(lang));
		for (i = 0; kinds[i] != '\0'; i++)
		{
			printf("%c: %s\n",
				/* back and forth conversion - the same like just kinds[i] */
				ctagsGetKindFromName(ctagsGetKindName(kinds[i], lang), lang),
				ctagsGetKindName(kinds[i], lang));
		}

		printf("\nParsing buffer:\n");
		/* we always specify the language by ourselves and don't use ctags detection */
		parseRawBuffer("whatever", (unsigned char *)program, strlen(program), lang, tagArray);

		processCollectedTags(tagArray);
	}
	else  /* parsing contents of a file */
	{
		int i;
		for (i = 1; i < argc; i++)
		{
			printf("\nParsing %s:\n", argv[i]);
			/* parseRawBuffer() is called repeatadly during Geany execution */
			parseRawBuffer(argv[i], NULL, 0, getNamedLanguage("C", 0), tagArray);

			processCollectedTags(tagArray);
		}
	}

	ptrArrayDelete(tagArray);

	return 0;
}
