/*
*   Copyright (c) 1998-2002, Darren Hiebert
*
*   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.
*
*   External interface to entry.c
*/

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

#include "entry_p.h"
#include "field.h"
#include "field_p.h"
#include "mio.h"
#include "options_p.h"
#include "parse_p.h"
#include "ptag_p.h"
#include "read.h"
#include "writer_p.h"
#include "xtag.h"
#include "xtag_p.h"

#include <string.h>

#define CTAGS_FILE  "tags"


static int writeCtagsEntry (tagWriter *writer CTAGS_ATTR_UNUSED,
							MIO * mio, const tagEntryInfo *const tag,
							void *clientData);
static int writeCtagsPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED,
								MIO * mio, const ptagDesc *desc,
								const char *const fileName,
								const char *const pattern,
								const char *const parserName,
								void *clientData);
static bool treatFieldAsFixed (int fieldType);
static void checkCtagsOptions (tagWriter *writer, bool fieldsWereReset);

#ifdef WIN32
static enum filenameSepOp overrideFilenameSeparator (enum filenameSepOp currentSetting);
#endif	/* WIN32 */

struct rejection {
	bool rejectionInThisInput;
};

tagWriter uCtagsWriter = {
	.writeEntry = writeCtagsEntry,
	.writePtagEntry = writeCtagsPtagEntry,
	.printPtagByDefault = true,
	.preWriteEntry = NULL,
	.postWriteEntry = NULL,
	.rescanFailedEntry = NULL,
	.treatFieldAsFixed = treatFieldAsFixed,
	.checkOptions = checkCtagsOptions,
#ifdef WIN32
	.overrideFilenameSeparator = overrideFilenameSeparator,
#endif
	.defaultFileName = CTAGS_FILE,
};

static void *beginECtagsFile (tagWriter *writer CTAGS_ATTR_UNUSED, MIO * mio CTAGS_ATTR_UNUSED,
							  void *clientData CTAGS_ATTR_UNUSED)
{
	static struct rejection rej;

	rej.rejectionInThisInput = false;

	return &rej;
}

static bool endECTagsFile (tagWriter *writer, MIO * mio CTAGS_ATTR_UNUSED, const char* filename CTAGS_ATTR_UNUSED,
						   void *clientData CTAGS_ATTR_UNUSED)
{
	struct rejection *rej = writer->private;
	return rej->rejectionInThisInput;
}

#ifdef WIN32
static enum filenameSepOp overrideFilenameSeparator (enum filenameSepOp currentSetting)
{
	if (currentSetting == FILENAME_SEP_UNSET)
		return FILENAME_SEP_USE_SLASH;
	return currentSetting;
}
#endif

tagWriter eCtagsWriter = {
	.writeEntry = writeCtagsEntry,
	.writePtagEntry = writeCtagsPtagEntry,
	.printPtagByDefault = true,
	.preWriteEntry = beginECtagsFile,
	.postWriteEntry = endECTagsFile,
	.rescanFailedEntry = NULL,
	.treatFieldAsFixed = treatFieldAsFixed,
	.defaultFileName = CTAGS_FILE,
	.checkOptions = checkCtagsOptions,
};

static bool hasTagEntryTabOrNewlineChar (const tagEntryInfo * const tag)
{

	if (doesFieldHaveTabOrNewlineChar (FIELD_NAME, tag, NO_PARSER_FIELD)
		|| doesFieldHaveTabOrNewlineChar (FIELD_INPUT_FILE, tag, NO_PARSER_FIELD))
		return true;

	if (tag->lineNumberEntry)
	{
		if (Option.lineDirectives)
		{
			if (doesFieldHaveTabOrNewlineChar (FIELD_LINE_NUMBER, tag, NO_PARSER_FIELD))
				return true;
		}
	}
	else if (doesFieldHaveTabOrNewlineChar (FIELD_PATTERN, tag, NO_PARSER_FIELD))
	{
		/* Pattern may have a tab char. However, doesFieldHaveTabOrNewlineChar returns
		 * false because NO_PARSER_FIELD may not have doesContainAnyChar handler.
		 */
		return true;
	}

	if (includeExtensionFlags ())
	{
		if (isFieldEnabled (FIELD_SCOPE) && doesFieldHaveValue (FIELD_SCOPE, tag)
			&& (doesFieldHaveTabOrNewlineChar (FIELD_SCOPE_KIND_LONG, tag, NO_PARSER_FIELD)
				|| doesFieldHaveTabOrNewlineChar (FIELD_SCOPE, tag, NO_PARSER_FIELD)))
			return true;
		if (isFieldEnabled (FIELD_TYPE_REF) && doesFieldHaveValue (FIELD_TYPE_REF, tag)
			&& doesFieldHaveTabOrNewlineChar (FIELD_TYPE_REF, tag, NO_PARSER_FIELD))
			return true;
		if (isFieldEnabled (FIELD_FILE_SCOPE) && doesFieldHaveValue (FIELD_FILE_SCOPE, tag)
			&& doesFieldHaveTabOrNewlineChar (FIELD_FILE_SCOPE, tag, NO_PARSER_FIELD))
			return true;

		int f[] = { FIELD_INHERITANCE,
					FIELD_ACCESS,
					FIELD_IMPLEMENTATION,
					FIELD_SIGNATURE,
					FIELD_ROLES,
					FIELD_EXTRAS,
					FIELD_XPATH,
					FIELD_END_LINE,
					-1};
		for (unsigned int i = 0; f[i] >= 0; i++)
		{
			if (isFieldEnabled (f[i]) && doesFieldHaveValue (f[i], tag)
				&& doesFieldHaveTabOrNewlineChar (f[i], tag, NO_PARSER_FIELD))
				return true;
		}
	}

	for (unsigned int i = 0; i < tag->usedParserFields; i++)
	{
		const tagField *f = getParserFieldForIndex(tag, i);
		fieldType ftype = f->ftype;
		if (isFieldEnabled (ftype))
		{
			if (doesFieldHaveTabOrNewlineChar (ftype, tag, i))
				return true;
		}
	}
	return false;
}


static const char* escapeFieldValueFull (tagWriter *writer, const tagEntryInfo * tag, fieldType ftype, int fieldIndex)
{
	const char *v;
	if (writer->type == WRITER_E_CTAGS && doesFieldHaveRenderer(ftype, true))
		v = renderFieldNoEscaping (ftype, tag, fieldIndex);
	else
		v = renderField (ftype, tag, fieldIndex);

	return v;
}

static const char* escapeFieldValue (tagWriter *writer, const tagEntryInfo * tag, fieldType ftype)
{
	return escapeFieldValueFull (writer, tag, ftype, NO_PARSER_FIELD);
}

static int renderExtensionFieldMaybe (tagWriter *writer, int xftype, const tagEntryInfo *const tag, char sep[2], MIO *mio)
{
	if (isFieldEnabled (xftype) && doesFieldHaveValue (xftype, tag))
	{
		int len;
		len = mio_printf (mio, "%s\t%s:%s", sep,
				  getFieldName (xftype),
				  escapeFieldValue (writer, tag, xftype));
		sep[0] = '\0';
		return len;
	}
	else
		return 0;
}

static int addParserFields (tagWriter *writer, MIO * mio, const tagEntryInfo *const tag)
{
	unsigned int i;
	int length = 0;

	for (i = 0; i < tag->usedParserFields; i++)
	{
		const tagField *f = getParserFieldForIndex(tag, i);
		fieldType ftype = f->ftype;
		if (! isFieldEnabled (ftype))
			continue;

		length += mio_printf(mio, "\t%s:%s",
							 getFieldName (ftype),
							 escapeFieldValueFull (writer, tag, ftype, i));
	}
	return length;
}

static int writeLineNumberEntry (tagWriter *writer, MIO * mio, const tagEntryInfo *const tag)
{
	if (Option.lineDirectives)
		return mio_printf (mio, "%s", escapeFieldValue (writer, tag, FIELD_LINE_NUMBER));
	else
		return mio_printf (mio, "%lu", tag->lineNumber);
}

static int addExtensionFields (tagWriter *writer, MIO *mio, const tagEntryInfo *const tag)
{
	bool isKindKeyEnabled = isFieldEnabled (FIELD_KIND_KEY);
	bool isScopeEnabled = isFieldEnabled   (FIELD_SCOPE_KEY);

	const char* const kindKey = isKindKeyEnabled
		?getFieldName (FIELD_KIND_KEY)
		:"";
	const char* const kindFmt = isKindKeyEnabled
		?"%s\t%s:%s"
		:"%s\t%s%s";
	const char* const scopeKey = isScopeEnabled
		?getFieldName (FIELD_SCOPE_KEY)
		:"";
	const char* const scopeFmt = isScopeEnabled
		?"%s\t%s:%s:%s"
		:"%s\t%s%s:%s";

	char sep [] = {';', '"', '\0'};
	int length = 0;

	const char *str = NULL;
	kindDefinition *kdef = getLanguageKind(tag->langType, tag->kindIndex);
	const char kind_letter_str[2] = {kdef->letter, '\0'};

	if (kdef->name != NULL && (isFieldEnabled (FIELD_KIND_LONG)  ||
		 (isFieldEnabled (FIELD_KIND)  && kdef->letter == KIND_NULL_LETTER)))
	{
		/* Use kind long name */
		str = kdef->name;
	}
	else if (kdef->letter != KIND_NULL_LETTER  && (isFieldEnabled (FIELD_KIND) ||
			(isFieldEnabled (FIELD_KIND_LONG) &&  kdef->name == NULL)))
	{
		/* Use kind letter */
		str = kind_letter_str;
	}

	if (str)
	{
		length += mio_printf (mio, kindFmt, sep, kindKey, str);
		sep [0] = '\0';
	}

	if (isFieldEnabled (FIELD_LINE_NUMBER) &&  doesFieldHaveValue (FIELD_LINE_NUMBER, tag))
	{
		length += mio_printf (mio, "%s\t%s:%ld", sep,
				   getFieldName (FIELD_LINE_NUMBER),
				   tag->lineNumber);
		sep [0] = '\0';
	}

	length += renderExtensionFieldMaybe (writer, FIELD_LANGUAGE, tag, sep, mio);

	if (isFieldEnabled (FIELD_SCOPE))
	{
		const char* k, *v;

		k = escapeFieldValue (writer, tag, FIELD_SCOPE_KIND_LONG);
		v = escapeFieldValue (writer, tag, FIELD_SCOPE);
		if (k && v)
		{
			length += mio_printf (mio, scopeFmt, sep, scopeKey, k, v);
			sep [0] = '\0';
		}
	}

	if (isFieldEnabled (FIELD_TYPE_REF) && doesFieldHaveValue (FIELD_TYPE_REF, tag))
	{
		length += mio_printf (mio, "%s\t%s:%s", sep,
				      getFieldName (FIELD_TYPE_REF),
				      escapeFieldValue (writer, tag, FIELD_TYPE_REF));
		sep [0] = '\0';
	}

	if (isFieldEnabled (FIELD_FILE_SCOPE) &&  doesFieldHaveValue (FIELD_FILE_SCOPE, tag))
	{
		length += mio_printf (mio, "%s\t%s:", sep,
				      getFieldName (FIELD_FILE_SCOPE));
		sep [0] = '\0';
	}

	for (int k = FIELD_ECTAGS_LOOP_START; k <= FIELD_ECTAGS_LOOP_LAST; k++)
		length += renderExtensionFieldMaybe (writer, k, tag, sep, mio);
	for (int k = FIELD_UCTAGS_LOOP_START; k <= FIELD_BUILTIN_LAST; k++)
		length += renderExtensionFieldMaybe (writer, k, tag, sep, mio);

	return length;
}

static int writeCtagsEntry (tagWriter *writer,
							MIO * mio, const tagEntryInfo *const tag,
							void *clientData CTAGS_ATTR_UNUSED)
{
	if (writer->private)
	{
		struct rejection *rej = writer->private;
		if (hasTagEntryTabOrNewlineChar (tag))
		{
			rej->rejectionInThisInput = true;
			return 0;
		}
	}

	int length = mio_printf (mio, "%s\t%s\t",
			      escapeFieldValue (writer, tag, FIELD_NAME),
			      escapeFieldValue (writer, tag, FIELD_INPUT_FILE));

	/* This is for handling 'common' of 'fortran'.  See the
	   description of --excmd=mixed in ctags.1.  In tags output, what
	   we call "pattern" is instructions for vi.

	   However, in the other formats, pattern should be pattern as its name. */
	if (tag->lineNumberEntry)
		length += writeLineNumberEntry (writer, mio, tag);
	else
	{
		if (Option.locate == EX_COMBINE)
			length += mio_printf(mio, "%lu;", tag->lineNumber);
		length += mio_puts(mio, escapeFieldValue(writer, tag, FIELD_PATTERN));
	}

	if (includeExtensionFlags ())
	{
		length += addExtensionFields (writer, mio, tag);
		length += addParserFields (writer, mio, tag);
	}

	length += mio_printf (mio, "\n");

	return length;
}

static int writeCtagsPtagEntry (tagWriter *writer,
				MIO * mio, const ptagDesc *desc,
				const char *const fileName,
				const char *const pattern,
				const char *const parserName,
				void *clientData CTAGS_ATTR_UNUSED)
{

	bool extras = includeExtensionFlags () && isFieldEnabled (FIELD_EXTRAS);
	const char *xsep = extras? ";\"	": "";
	const char *fsep = extras? ":": "";
	const char *fieldx = extras? getFieldName (FIELD_EXTRAS): "";
	const char *xptag = extras? getXtagName (XTAG_PSEUDO_TAGS): "";

	/* Escaping:
	 *
	 * NAME:
	 *    Defined in ctags. As far as we give a good pseudo tag name,
	 *    we can print the name as is.
	 *
	 * parserName:
	 *    This can be a part of NAME. An optlib can give a
	 *    parserName but the characters that can be used in the name are
	 *    limited in [a-zA-Z0-9+#]. We can print the name as is.
	 *
	 * fileName:
	 *    Any characters can be used. Escaping is needed.
	 *
	 * pattern:
	 *    Any characters can be used. Escaping is needed always.
	 *
	 * fieldx:
	 *    No escaping is needed.
	 *
	 * xptag:
	 *    No escaping is needed.
	 *
	 */

	vString *vfileName = vStringNew ();
	if (writer->type == WRITER_U_CTAGS
#ifdef WIN32
		&& getFilenameSeparator(Option.useSlashAsFilenameSeparator) == FILENAME_SEP_USE_SLASH
#endif
		)
	{
		if (fileName)
			vStringCatSWithEscaping (vfileName, fileName);
	}
	else if (fileName)
	{
		char *c = NULL;
		if ((c = strchr (fileName, '\t')) || (c = strchr (fileName, '\n')))
		{
			vStringDelete (vfileName);
			error (WARNING, "skip priting %s%s pseudo tag; the input field of the pseudo tag includes a %s character: %s",
				   PSEUDO_TAG_PREFIX, desc->name,
				   *c == '\t'? "tab": "newline",
				   fileName);
			return 0;
		}
		vStringCatS (vfileName, fileName);
	}

	vString *vpattern = vStringNew ();
	if (pattern)
		vStringCatSWithEscapingAsPattern (vpattern, pattern);

	int r = parserName
		? mio_printf (mio, "%s%s%s%s\t%s\t/%s/%s%s%s%s\n",
			      PSEUDO_TAG_PREFIX, desc->name, PSEUDO_TAG_SEPARATOR, parserName,
			      vStringValue (vfileName), vStringValue (vpattern),
				  xsep, fieldx, fsep, xptag)
		: mio_printf (mio, "%s%s\t%s\t/%s/%s%s%s%s\n",
			      PSEUDO_TAG_PREFIX, desc->name,
			      vStringValue (vfileName), vStringValue (vpattern),
			      xsep, fieldx, fsep, xptag);

	vStringDelete (vpattern);
	vStringDelete (vfileName);

	return r;
}

static fieldType fixedFields [] = {
	FIELD_NAME,
	FIELD_INPUT_FILE,
	FIELD_PATTERN,
};

static bool treatFieldAsFixed (int fieldType)
{
	for (int i = 0; i < ARRAY_SIZE(fixedFields); i++)
		if (fixedFields [i] == fieldType)
			return true;
	return false;
}

static void checkCtagsOptions (tagWriter *writer CTAGS_ATTR_UNUSED,
							   bool fieldsWereReset)
{
	if (isFieldEnabled (FIELD_KIND_KEY)
		&& (!(isFieldEnabled (FIELD_KIND_LONG) ||
			  isFieldEnabled (FIELD_KIND))))
	{
		error (WARNING, "though %c/%s field is enabled, neither %c nor %c field is not enabled",
			   getFieldLetter (FIELD_KIND_KEY),
			   getFieldName (FIELD_KIND_KEY),
			   getFieldLetter (FIELD_KIND),
			   getFieldLetter (FIELD_KIND_LONG));
		error (WARNING, "enable the %c field to make the %c/%s field printable",
			   getFieldLetter (FIELD_KIND_LONG),
			   getFieldLetter (FIELD_KIND_KEY),
			   getFieldName (FIELD_KIND_KEY));
		enableField (FIELD_KIND_LONG, true);
	}
	if (isFieldEnabled (FIELD_SCOPE_KEY)
		&& !isFieldEnabled (FIELD_SCOPE))
	{
		error (WARNING, "though %c/%s field is enabled, %c field is not enabled",
			   getFieldLetter (FIELD_SCOPE_KEY),
			   getFieldName (FIELD_SCOPE_KEY),
			   getFieldLetter (FIELD_SCOPE));
		error (WARNING, "enable the %c field to make the %c/%s field printable",
			   getFieldLetter (FIELD_SCOPE),
			   getFieldLetter (FIELD_SCOPE_KEY),
			   getFieldName (FIELD_SCOPE_KEY));
		enableField (FIELD_SCOPE, true);
	}

	for (int i = 0; i < ARRAY_SIZE (fixedFields); i++)
	{
		if (!isFieldEnabled (fixedFields [i]))
		{
			enableField (fixedFields [i], true);

			if (fieldsWereReset)
				continue;

			const char *name = getFieldName (fixedFields [i]);
			unsigned char letter = getFieldLetter (fixedFields [i]);

			if (name && letter != NUL_FIELD_LETTER)
				error(WARNING, "Cannot disable fixed field: '%c'{%s} in ctags output mode",
				      letter, name);
			else if (name)
				error(WARNING, "Cannot disable fixed field: {%s} in ctags output mode",
				      name);
			else if (letter != NUL_FIELD_LETTER)
				error(WARNING, "Cannot disable fixed field: '%c' in ctags output mode",
				      letter);
		}
	}
}
