/*****************************************************************************
*   $Id: sort.c,v 3.1 1997/05/13 05:34:26 darren Exp $
*
*   Copyright (c) 1996-1997, Darren Hiebert
*
*   Contains functions to sort the tag entries.
*****************************************************************************/

/*============================================================================
=   Include files
============================================================================*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef DEBUG
# include <assert.h>
#endif
#include "ctags.h"

/*============================================================================
=   Function prototypes
============================================================================*/

/*  Tag sorting functions.
 */
#ifdef EXTERNAL_SORT
static void catFile __ARGS((const char *const name));
static void reportWarnings __ARGS((void));
#else
static void failedSort __ARGS((const char *const msg));
static int compareTags __ARGS((const void *const one, const void *const two));
static void writeSortedTags __ARGS((char **const table, const boolean toStdout));
#endif

/*============================================================================
=   Function definitions
============================================================================*/

#ifdef EXTERNAL_SORT

static void catFile( name )
    const char *const name;
{
    FILE *const fp = fopen(name, "r");

    if (fp != NULL)
    {
	int c;

	while ((c = getc(fp)) != EOF)
	    putchar(c);
	fclose(fp);
    }
}

static void reportWarnings()
{
#ifdef AWK
    if (strlen(AWK) > (size_t)0)
    {
	const char *const awkProgTemplate = "%s{if ($1==prev){printf(\"Duplicate entry in \") | \"cat>&2\"; if ($%d!=prevfile) printf(\"%%s and \",prevfile) | \"cat>&2\"; printf(\"%%s: %%s\\n\",$%d,$1) | \"cat>&2\"; } else {prev=$1;prevfile=$%d}}";
	char *awkProg;
	const char *begin;
	size_t length;
	int fileArg;

	if (Option.xref)    { fileArg = 4;  begin = ""; }
	else		    { fileArg = 2;  begin = "BEGIN{FS=\"\\t\"}"; }

	length = strlen(awkProgTemplate) + strlen(begin) + 3;
	awkProg = (char *)malloc(length);
	if (awkProg != NULL)
	{
	    const char *const cmdTemplate = "%s '%s' %s";
	    char *cmd;
	    int ret;

	    sprintf(awkProg, awkProgTemplate, begin, fileArg, fileArg, fileArg);
	    length = strlen(cmdTemplate) + strlen(AWK) +
		    strlen(awkProg) + strlen(TagFile.name);
	    cmd = (char *)malloc(length);
	    if (cmd != NULL)
	    {
		sprintf(cmd, cmdTemplate, AWK, awkProg, TagFile.name);
		ret = system(cmd);
		free(cmd);
	    }
	    free(awkProg);
	}
    }
#endif
}

extern void externalSortTags( toStdout )
    const boolean toStdout;
{
    const char *const sortTemplate = "sort -o %s %s";
    const size_t length	    = strlen(sortTemplate) + 2 * strlen(TagFile.name);
    char *const cmd	    = (char *)malloc(length);

    if (cmd != NULL)
    {
	int ret;

	sprintf(cmd, sortTemplate, TagFile.name, TagFile.name);
	ret = system(cmd);
	free(cmd);

	if (ret == 0  &&  Option.warnings)
	    reportWarnings();
    }
    if (toStdout)
	catFile(TagFile.name);
}

#else

/*----------------------------------------------------------------------------
 *  These functions provide a basic internal sort. No great memory
 *  optimization is performed (e.g. recursive subdivided sorts),
 *  so have lots of memory if you have large tag files.
 *--------------------------------------------------------------------------*/

static void failedSort( msg )
    const char *const msg;
{
    if (TagFile.fp != NULL)
    {
	fclose(TagFile.fp);
	TagFile.fp = NULL;
    }
    if (msg == NULL)
	fputs("ctags: Insufficient memory\n", errout);
    else
	perror(msg);
    fprintf(errout, "Cannot sort tag file.\n");
    exit(1);
}

static int compareTags( one, two )
    const void *const one;
    const void *const two;
{
    const char *const line1 = *(const char *const *const)one;
    const char *const line2 = *(const char *const *const)two;

    return strcmp(line1, line2);
}

static void writeSortedTags( table, toStdout )
    char **const table;
    const boolean toStdout;
{
    char *thisTag = NULL, *prevTag = NULL, *thisFile = NULL, *prevFile = NULL;
    size_t i;

    if (Option.warnings)
    {
	thisTag  = malloc(TagFile.max.tag  + (size_t)1);
	prevTag  = malloc(TagFile.max.tag  + (size_t)1);
	thisFile = malloc(TagFile.max.file + (size_t)1);
	prevFile = malloc(TagFile.max.file + (size_t)1);

	if (thisTag  == NULL  ||   prevTag == NULL  ||
	    thisFile == NULL  ||  prevFile == NULL)
	{
	    perror("Cannot generate duplicate warnings\n");
	}
	*prevTag  = *thisTag  = '\0';
	*prevFile = *thisFile = '\0';
    }

    /*	Write the sorted lines back into the tag file.
     */
    if (toStdout)
	TagFile.fp = stdout;
    else
    {
	TagFile.fp = fopen(TagFile.name, "w");
	if (TagFile.fp == NULL)
	    failedSort(TagFile.name);
    }
    for (i = 0 ; i < TagFile.numTags ; ++i)
    {
	/*  Here we filter out identical tag *lines* (including search
	 *  pattern) if this is not an xref file.
	 */
	if (i == 0  ||  Option.xref  ||  strcmp(table[i], table[i-1]) != 0)
	    if (fputs(table[i], TagFile.fp) == EOF)
		failedSort(TagFile.name);

	if (Option.warnings)
	{
	    int fields;

	    if (Option.xref)
		fields = sscanf(table[i],"%s %*s %*s %s", thisTag, thisFile);
	    else
		fields = sscanf(table[i],"%[^\t]\t%[^\t]", thisTag, thisFile);

	    if (fields == 2  &&  strcmp(thisTag, prevTag) == 0)
	    {
		fprintf(errout, "Duplicate entry in ");
		if (strcmp(thisFile, prevFile) != 0)
		    fprintf(errout, "%s and ", prevFile);
		fprintf(errout, "%s: %s\n", thisFile, thisTag);
	    }
	    strcpy(prevTag , thisTag );
	    strcpy(prevFile, thisFile);
	}
    }
    if (Option.warnings)
    {
	free(thisTag);
	free(prevTag);
	free(thisFile);
	free(prevFile);
    }
    if (! toStdout)
	fclose(TagFile.fp);
}

extern void internalSortTags( toStdout )
    const boolean toStdout;
{
    char *buffer, **table;
    const size_t maxLength = TagFile.max.line + 1;  /* include room for null */
    size_t tableSize, mallocSize;
    size_t i;

    /*	Allocate the memory buffer for the tag file lines. This includes a
     *	table of line pointers which will be sorted.
     */
    tableSize  = TagFile.numTags * sizeof(*table);
    buffer = malloc(maxLength);			    /* read buffer */
    table  = malloc(tableSize);
    if (buffer == NULL  ||  table == NULL)
	failedSort(NULL);

    mallocSize = maxLength + tableSize;

    /*	Open the tag file and place its lines into allocated buffers.
     */
    TagFile.fp = fopen(TagFile.name, "r");
    if (TagFile.fp == NULL)
	failedSort(TagFile.name);
    for (i = 0 ; i < TagFile.numTags && !feof(TagFile.fp) ; ++i)
    {
	size_t stringSize;

	if (fgets(buffer, (int)maxLength, TagFile.fp) == NULL  &&
							    ! feof(TagFile.fp))
	    failedSort(TagFile.name);
	stringSize = strlen(buffer) + 1;
	table[i] = (char *)malloc(stringSize);
	if (table[i] == NULL)
	    failedSort(NULL);
	mallocSize += stringSize;
	strcpy(table[i], buffer);
    }
#ifdef DEBUG
    assert(i == TagFile.numTags);   /* this should always be the case */
#endif
    fclose(TagFile.fp);

    /*	Sort the lines.
     */
    qsort(table, TagFile.numTags, sizeof(*table),
#ifndef USING_PROTOTYPES
	  (int (*)())
#endif
	  compareTags);

    writeSortedTags(table, toStdout);

#ifdef DEBUG
    if (debug(DEBUG_STATUS))
	printf("sort memory: %ld bytes\n", (long)mallocSize);
#endif
    for (i = 0 ; i < TagFile.numTags ; ++i)
	free(table[i]);
    free(table);
    free(buffer);
}

#endif

/* vi:set tabstop=8 shiftwidth=4: */
