/*
 * A simple implementation of a tracing facility for Pan-STARRS
 *
 * Tracing is controlled on a per "component" basis, where a "component" is a
 * name of the form aaa.bbb.ccc where aaa is the Most significant part; for
 * example, the utilities library might be called "utils", the doubly-linked
 * list "utils.dlist", and the code to destroy a list "utils.dlist.del" 
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "psLib.h"

/*****************************************************************************/
/*
 * A component is a string of the form aaa.bbb.ccc, and may itself
 * contain further subcomponents. The Component structure doesn't
 * in fact contain it's full name, but only the last part.
 */
typedef struct Component {
    const char *name;			// last part of name of this component
    int level;				// trace level for this component
    int dimen;				// dimension of subcomp
    int n;				// number of subcomponents
    struct Component **subcomp;		// next level of subcomponents
} Component;

static int highest_level = 0;		// highest level requested
/*
 * tracelevel cache
 */
#define CACHE_NAME_LEN 50
#define NO_CACHE "\a"			// no name is cached

#define UNKNOWN_LEVEL -9999		// we don\'t know this name\'s level

static char cachedName[CACHE_NAME_LEN + 1] = NO_CACHE; // last name looked up
static int cachedLevel;			// level of last looked up name

/*****************************************************************************/

static Component *componentAlloc(const char *name,
				 int level)
{
    Component *comp = psAlloc(sizeof(Component));

    comp->name = psStringCopy(name);
    comp->level = level;
    comp->dimen = comp->n = 0;
    comp->subcomp = NULL;

    return comp;
}

static void componentFree(Component *comp)
{
    if (comp == NULL) { return; }

    if (comp->subcomp != NULL) {
	for (int i = 0; i < comp->n; i++) {
	    componentFree(comp->subcomp[i]);
	}
	psFree(comp->subcomp);
    }

    psFree((char *)comp->name);
    psFree(comp);
}

/*****************************************************************************/
/*
 * The root of the trace component tree
 */
static Component *croot = NULL;

static void initTrace(void) {
    if (croot == NULL) {
	croot = componentAlloc("", UNKNOWN_LEVEL);
    }
}

/*****************************************************************************/
/*
 * Free the trace tree information
 */
void psTraceReset(void)
{
    componentFree(croot);
    croot = NULL;
}

/*****************************************************************************/
/*
 * Add a new component to the tree
 */
static void componentAdd(Component *comp,
			 const char *aname,
			 int level)
{
    char name[strlen(aname) + 1];	// need a writeable copy
    strcpy(name, aname);

    char *cpt0;				// first component of name
    char *rest = name;			// rest of name
    cpt0 = strsep(&rest, ".");
    /*
     * Does cpt0 match this level?
     */
    if (strcmp(comp->name, cpt0) == 0) { // a match
	if (rest == NULL) {
	    comp->level = level;
	} else {
	    componentAdd(comp, rest, level);
	}
	
	return;
    }
    /*
     * Look for a match for cpt0 in this level's subcomps
     */
    for (int i = 0; i < comp->n; i++) {
	if (strcmp(comp->subcomp[i]->name, cpt0) == 0) { // a match
	    if (rest == NULL) {
		comp->subcomp[i]->level = level;
	    } else {
		componentAdd(comp->subcomp[i], rest, level);
	    }

	    return;
	}
    }
    /*
     * No match; add cpt0 to this level. Ensure that subcomp is sorted
     */
    if (comp->n >= comp->dimen - 1) {
	comp->dimen += 5;
	comp->subcomp = psRealloc(comp->subcomp, comp->dimen*sizeof(Component *));
    }

    Component *fcpt0 = componentAlloc(cpt0, UNKNOWN_LEVEL);
    int i;
    for (i = 0; i < comp->n; i++) {
	if (strcmp(cpt0, comp->subcomp[i]->name) > 0) {
	    for (int j = comp->n; j > i; j--) {
		comp->subcomp[j] = comp->subcomp[j - 1];
	    }
	    break;
	}
    }
    comp->subcomp[i] = fcpt0;
    comp->n++;

    if (rest == NULL) {
	fcpt0->level = level;
    } else {
	componentAdd(fcpt0, rest, level);
    }
}

/*****************************************************************************/
/*
 * Find the highest level present in the tree
 */
static void setHighestLevel(const Component *comp)
{
    if (comp->level > highest_level) {
	highest_level = comp->level;
    }
    
    for (int i = 0; i < comp->n; i++) {
	setHighestLevel(comp->subcomp[i]);
    }
}

/*****************************************************************************/

int psTraceSetLevel(const char *comp,	// component of interest
		    int level)		// desired trace level
{
    if (croot == NULL) {
	initTrace();
    }

    strncpy(cachedName, NO_CACHE, CACHE_NAME_LEN); // invalidate cache
    
    componentAdd(croot, comp, level);

    if (level > highest_level) {
	highest_level = level;
    } else {
	highest_level = UNKNOWN_LEVEL;
	setHighestLevel(croot);
    }

    return 0;
}

/*****************************************************************************/
/*
 * Return a trace level given a name
 */
static int doTraceGetLevel(const Component *comp, // end of component to search
			   const char *aname) // name to find
{
    char name[strlen(aname) + 1];	// need a writeable copy
    strcpy(name, aname);

    char *cpt0;				// first component of name
    char *rest = name;			// rest of name
    cpt0 = strsep(&rest, ".");
    /*
     * Look for a match for cpt0 in this level's subcomps
     */
    for (int i = 0; i < comp->n; i++) {
	if (strcmp(comp->subcomp[i]->name, cpt0) == 0) { // a match
	    if (rest == NULL) {		// we\'re there
		int level = comp->subcomp[i]->level;

		return (level == UNKNOWN_LEVEL) ? comp->level : level;
	    } else {
		return doTraceGetLevel(comp->subcomp[i], rest);
	    }
	}
    }
    /*
     * No match. This is as far as she goes
     */
    return comp->level;
}

int psTraceGetLevel(const char *name)	// component of interest
{
    if (croot == NULL) {
	initTrace();
    }

    if (strcmp(name, cachedName) == 0) {
	return cachedLevel;
    }

    int level = doTraceGetLevel(croot, name);

    strncpy(cachedName, name, CACHE_NAME_LEN);
    cachedLevel = level;

    return level;
}

/*****************************************************************************/
/*
 * Print a tree of trace levels
 */
static void doTracePrintLevels(const Component *comp, int depth)
{
    if (comp->name[0] == '\0') {
	printf("%*s%-*s %d\n", depth, "", 20 - depth,
	       "(root)", (comp->level == UNKNOWN_LEVEL) ? 0 : comp->level);
    } else {
	if (comp->level == UNKNOWN_LEVEL) {
	    printf("%*s%-*s %s\n", depth, "", 20 - depth,
		   comp->name, ".");
	} else {
	    printf("%*s%-*s %d\n", depth, "", 20 - depth,
		   comp->name, comp->level);
	}
    }
    
    for (int i = 0; i < comp->n; i++) {
       doTracePrintLevels(comp->subcomp[i], depth + 1);
    }
}

void psTracePrintLevels(void)
{
    if (croot == NULL) {
	initTrace();
    }

    doTracePrintLevels(croot, 0);
}

/*****************************************************************************/
/*
 * Change where we're writing to; close previous file descriptor
 * if it isn't stdout/stderr
 */
static FILE *traceFD = NULL;

void psTraceSetDestination(FILE *fp)
{
   if (traceFD != stdout && traceFD != stderr) {
      fclose(traceFD);
   }

   traceFD = fp;
}


/*****************************************************************************/
/*
 * Actually generate the trace message. Note that psTrace dealt with
 * prepending appropriate indentation
 */
void p_psTrace(const char *comp,	// component being traced
	       int level,		// desired trace level
	       ...)			// arguments
{
    if (level <= highest_level && psTraceGetLevel(comp) >= level) {
        if (traceFD == NULL) {		// not initialised, or fopen failed
	   traceFD = stdout;
	}

	va_list ap;
	va_start(ap, level);
	for (int i = 0; i < level; i++) {
	    fputc(' ', traceFD);
	}
	char *fmt = va_arg(ap, char *);
	vfprintf(traceFD, fmt, ap);
	if (strchr(fmt, '\n') == NULL) {
	    fputc('\n', traceFD);
	}
	va_end(ap);
    }
}
