#include <stdio.h>
#include <string.h>
#include "pslib.h"
#include "psAdditionals.h"

psMetadata *psMetadataLookupMD(bool *status, const psMetadata *md, const char *key)
{
    psMetadataItem *item = psMetadataLookup((psMetadata*)md, key); // The metadata with instruments
    psMetadata *value = NULL;		// The value to return
    if (!item) {
	// The given key isn't in the metadata
	if (status) {
	    *status = false;
	} else {
	    psError(PS_ERR_IO, true, "Couldn't find %s in the metadata.\n");
	}
    } else if (item->type != PS_META_META) {
	// The value at the key isn't metadata
	if (status) {
	    *status = false;
	} else {
	    psLogMsg(__func__, PS_LOG_WARN, "%s isn't of type PS_META_META, as expected.\n");
	}
	value = NULL;
    } else {
	// We have the requested metadata
	if (status) {
	    *status = true;
	}
	value = item->data.md; // The requested metadata
    }
    return value;
}


char *psMetadataLookupString(bool *status, const psMetadata *md, const char *key)
{
    psMetadataItem *item = psMetadataLookup((psMetadata*)md, key); // The metadata with instruments
    char *value = NULL;			// The value to return
    if (!item) {
	// The given key isn't in the metadata
	if (status) {
	    *status = false;
	} else {
	    psError(PS_ERR_IO, true, "Couldn't find %s in the metadata.\n");
	}
    } else if (item->type != PS_META_STR) {
	// The value at the key isn't of the desired type
	if (status) {
	    *status = false;
	} else {
	    psLogMsg(__func__, PS_LOG_WARN, "%s isn't of type PS_META_STR, as expected.\n");
	}
	value = NULL;
    } else {
	// We have the requested metadata
	if (status) {
	    *status = true;
	}
	value = item->data.V; // The requested metadata
    }
    return value;
}


void psMetadataPrint(psMetadata *md, int level)
{
    psMetadataIterator *iter = psMetadataIteratorAlloc(md, PS_LIST_HEAD, NULL);	// Iterator
    psMetadataItem *item = NULL;	// Item from metadata
    while (item = psMetadataGetAndIncrement(iter)) {
	// Indent...
	for (int i = 0; i < level; i++) {
	    printf(" ");
	}
	printf("%s", item->name);
	if (item->comment && strlen(item->comment) > 0) {
	    printf(" (%s)", item->comment);
	}
	printf(": ");
	switch (item->type) {
	  case PS_META_STR:
	    printf("%s", item->data.V);
	    break;
	  case PS_META_BOOL:
	    if (item->data.B) {
		printf("True");
	    } else {
		printf("False");
	    }
	    break;
	  case PS_META_S32:
	    printf("%d", item->data.S32);
	    break;
	  case PS_META_F32:
	    printf("%f", item->data.F32);
	    break;
	  case PS_META_F64:
	    printf("%f", item->data.F64);
	    break;
	  case PS_META_META:
	    printf("\n");
	    psMetadataPrint(item->data.V, level + 1);
	    break;
	  default:
	    psError(PS_ERR_IO, false, "Non-printable metadata type: %x\n", item->type);
	}
	printf("\n");
    }
    psFree(iter);

    return;
}


// Set verbosity level
int psArgumentVerbosity(int *argc, char **argv)
{
    int logLevel = 2;			// Default log level
    int argnum = 0;			// Argument number

    // set in order, so that -vvv overrides -vv overrides -v
    if (argnum = psArgumentGet(*argc, argv, "-v")) {
        psArgumentRemove(argnum, argc, argv);
        logLevel = 3;
    }
    if (argnum = psArgumentGet(*argc, argv, "-vv")) {
        psArgumentRemove(argnum, argc, argv);
        logLevel = 4;
    }
    if (argnum = psArgumentGet(*argc, argv, "-vvv")) {
        psArgumentRemove(argnum, argc, argv);
        logLevel = 5;
    }
    psLogSetLevel (logLevel);		// XXX: This function should return an error if the log level is invalid

    if (argnum = psArgumentGet(*argc, argv, "-logfmt")) {
        if (*argc < argnum + 2) {
            psError(PS_ERR_IO, true, "-logfmt switch specified without a format.");
        } else {
	    psArgumentRemove(argnum, argc, argv);
	    psLogSetFormat(argv[argnum]); // XXX EAM : this function should return an error if the log format is invalid
	    psArgumentRemove(argnum, argc, argv);
	}
    }

    // Now the trace stuff
    // argument format is: -trace (facil) (level)
    while (argnum = psArgumentGet(*argc, argv, "-trace")) {
        if (*argc < argnum + 3) {
            psError(PS_ERR_IO, true, "-trace switch specified without facility and level.");
        }
        psArgumentRemove(argnum, argc, argv);
        psTraceSetLevel(argv[argnum], atoi(argv[argnum+1])); // XXX: This function should return an error if the trace level is invalid
        psArgumentRemove(argnum, argc, argv);
        psArgumentRemove(argnum, argc, argv);
    }
    if ((argnum = psArgumentGet(*argc, argv, "-trace-levels"))) {
        psTracePrintLevels();
        exit(2);
    }

    return logLevel;
}
 
// Find the location of the specified argument
int psArgumentGet(int argc, char **argv, const char *arg)
{
    for (int i = 1; i < argc; i++) {
        if (!strcmp(argv[i], arg))
            return i;
    }
   
    return 0;
}

// Remove the specified argument (by location)
bool psArgumentRemove(int argnum, int *argc, char **argv)
{
    if (argnum > 0) {
        (*argc)--;
        for (int i = argnum; i < *argc; i++) {
            argv[i] = argv[i+1];
        }
    } else {
	return false;
    }
    
    return true;
}


static psMetadataItem *argumentRead(psMetadataItem *item, // Item to read into
				    int argnum,	// Argument number
				    int *argc,	// Number of arguments in total
				    char **argv	// The arguments
				    )
{
    psMetadataItem *newItem = NULL;
    switch(item->type) {
	// Only doing a representative set of types
      case PS_META_S32:
	newItem = psMetadataItemAlloc(item->name, item->type, item->comment, atoi(argv[argnum]));
	psArgumentRemove(argnum, argc, argv);
	break;
      case PS_META_F32:
	newItem = psMetadataItemAlloc(item->name, item->type, item->comment, atof(argv[argnum]));
	psArgumentRemove(argnum, argc, argv);
	break;
      case PS_META_BOOL:
	// Turn option on; no optional argument to remove
	newItem = psMetadataItemAlloc(item->name, item->type, item->comment, true);
	break;
	// XXX: Include the other numerical types
      case PS_META_STR:
	{
	    //psString string = psStringCopy(argv[argnum]);	// Get the argument into PS memory management
	    //psFree(string);
	    newItem = psMetadataItemAlloc(item->name, item->type, item->comment, argv[argnum]);
	    psArgumentRemove(argnum, argc, argv);
	}
	break;
      default:
	psError(PS_ERR_IO, true, "Argument type (%x) is not supported --- argument %s ignored\n",
		item->type, item->name);
	psFree(newItem);
	return NULL;
    }

    return newItem;
}


// XXX: There is a memory leak in the MULTI section.  I think it might have something to do with reference
// counting between lists and MD, in the second section of the code (copy newArgs into arguments), but I'm not
// entirely sure.
bool psArgumentParse(psMetadata *arguments, int *argc, char **argv)
{
    // We need to do a bit of mucking around in order to preserve the arguments metadata until the last
    // minute --- if there is a bad argument, we need to return the old "arguments", since they contain
    // the default values, which we probably want to output in a "help" message (we don't want to print
    // the changed values and have the user think that they are default values).

    psMetadata *newArgs = psMetadataAlloc(); // Place to read arguments into
    psList *changed = psListAlloc(NULL);// List of keys that have changed

    for (int i = 1; i < *argc; i++) {
	psTrace(__func__, 7, "Looking at %s\n", argv[i]);
	psMetadataItem *argItem = psMetadataLookup(arguments, argv[i]);
	if (argItem) {
	    psArgumentRemove(i, argc, argv); // Remove the switch
	    if (argItem->type != PS_META_MULTI) {
		if (argItem->type != PS_META_BOOL && *argc < i + 1) {
		    psError(PS_ERR_IO, true, "Required argument for %s is missing.\n", argItem->name);
		    // XXX: Cleanup before returning
		    psFree(newArgs);
		    return false;
		}
		psMetadataItem *newItem = argumentRead(argItem, i, argc, argv);
		psMetadataAddItem(newArgs, newItem, PS_LIST_TAIL, PS_META_REPLACE);
		psFree(newItem);
	    } else {
		// Go through the MULTI
		psList *multi = argItem->data.V; // The list of MULTI psMetadataItems
		if (*argc < i + multi->size) {
		    psError(PS_ERR_IO, true, "Not enough arguments for %s.\n", argItem->name);
		    // Remove the arguments --- they will be ignored
		    for (int j = i; i < *argc; i++) {
			psArgumentRemove(i, argc, argv);
		    }
		    // XXX: Cleanup before returning
		    psFree(newArgs);
		    return false;
		}

		// Remove any prior existence in the newArgs --- this is important because we specify
		// adding the new items as DUPLICATE_OK, so if some idiot specifies it twice, we'd end
		// up with two copies of everything.
		psMetadataItem *checkItem = psMetadataLookup(newArgs, argItem->name);
		if (checkItem) {
		    (void)psMetadataRemove(newArgs, 0, argItem->name);
		    (void)psListRemoveData(changed, argItem->name);
		}

		psListIterator *multiIter = psListIteratorAlloc(multi, PS_LIST_HEAD, true);
		psMetadataItem *nextItem = NULL; // Item from list
		while (nextItem = psListGetAndIncrement(multiIter)) {
		    psMetadataItem *newItem = argumentRead(nextItem, i, argc, argv);
		    psMetadataAddItem(newArgs, newItem, PS_LIST_TAIL, PS_META_DUPLICATE_OK);
		    //psFree(newItem);
		}
		psFree(multiIter);
	    }

	    // Some book-keeping
	    //	    psString name = psStringCopy(argItem->name);
	    psListAdd(changed, PS_LIST_TAIL, argItem->name);
	    i--;

	} else if (strncmp(argv[i], "-", 1) == 0 || strncmp(argv[i], "+", 1) == 0) {
	    // Someone's specified a bad option
	    psError(PS_ERR_IO, true, "Unknown option: %s\n", argv[i]);
	    psFree(newArgs);
	    return false;
	}
    }

    // All the arguments are good, so now we can copy the newArgs over
    psListIterator *changedIter = psListIteratorAlloc(changed, PS_LIST_HEAD, false); // Iterator
    psString name = NULL;		// Item from iteration
    while (name = psListGetAndIncrement(changedIter)) {
	printf("Updating %s\n", name);
	psMetadataItem *oldItem = psMetadataLookup(arguments, name);
	psMetadataItem *newItem = psMetadataLookup(newArgs, name);
	if (oldItem->type != newItem->type) {
	    psAbort(__func__, "Shouldn't reach here!\n");
	}
	switch (oldItem->type) {
	    // Only doing a representative set of types
	  case PS_META_S32:
	    oldItem->data.S32 = newItem->data.S32;
	    break;
	  case PS_META_F32:
	    oldItem->data.F32 = newItem->data.F32;
	    break;
	  case PS_META_BOOL:
	    oldItem->data.B = newItem->data.B;
	    break;
	    // XXX: Include the other numerical types
	  case PS_META_STR:
	    psFree(oldItem->data.V);
	    oldItem->data.V = psMemIncrRefCounter(newItem->data.V);
	    break;
	  case PS_META_MULTI:
	    {
		psList *newMulti = psMemIncrRefCounter(newItem->data.V);	// The new list of MULTI
		psList *oldMulti = oldItem->data.V;	// The old list of MULTI
		psListIterator *newMultiIter = psListIteratorAlloc(newMulti, PS_LIST_HEAD, false);
		psListIterator *oldMultiIter = psListIteratorAlloc(oldMulti, PS_LIST_HEAD, true);
		psMetadataItem *newMultiItem = NULL; // Item from iterator
		while (newMultiItem = psListGetAndIncrement(newMultiIter)) {
		    psMetadataItem *oldMultiItem = psListGetAndIncrement(oldMultiIter);
		    if (! oldMultiItem) {
			psAbort(__func__, 
				"Something went very wrong here!  The lists SHOULD be of the same length!\n");
		    }
		    switch (oldMultiItem->type) {
			// Only doing a representative set of types
		      case PS_META_S32:
			oldItem->data.S32 = newItem->data.S32;
			break;
		      case PS_META_F32:
			oldItem->data.F32 = newItem->data.F32;
			break;
			// XXX: Include the other numerical types
		      case PS_META_STR:
			psFree(oldItem->data.V);
			oldItem->data.V = psMemIncrRefCounter(newItem->data.V);
			break;
		      default:
			psAbort(__func__, "Should never ever get here, ever.\n");
		    }
		    psFree(oldMultiItem);
		    psFree(newMultiItem);
		}
		psFree(newMultiIter);
		psFree(oldMultiIter);
	    }
	    break;
	  default:
	    psAbort(__func__, "Should never ever ever get here.\n");
	}
    }
    psFree(changedIter);
    psFree(changed);

    // Now, blow away the newArgs and we're done.
    psFree(newArgs);
    return true;
}


static int argLength(psMetadataItem *arg)
{
    switch (arg->type) {
	// Only doing a representative set of types
      case PS_META_S32:
	return arg->data.S32 >= 0 ? (int)log10f((float)arg->data.S32) + 1 :
	    (int)log10f(-(float)arg->data.S32) + 2;
	// XXX: Other numerical types
      case PS_META_F32:
	return arg->data.F32 >= 0 ? 12 : 13; // -d.dddddde?dd
      case PS_META_F64:
	return arg->data.F64 >= 0 ? 12 : 13; // -d.dddddde?dd
      case PS_META_BOOL:
	return arg->data.B ? 4 : 5;
      case PS_META_STR:
	return strlen(arg->data.V);
      default:
	psAbort(__func__, "Argument type (%x) is not supported.\n", arg->type);
    }
 
    return 0;
}

#define NUM_SPACES 4			// Number of spaces between

void psArgumentHelp(psMetadata *arguments)
{
    printf("Optional arguments, with default values:\n");
    psMetadataIterator *argIter = psMetadataIteratorAlloc(arguments, PS_LIST_HEAD, NULL);
    psMetadataItem *argItem = NULL;	// Item from iterator
    int maxName = 4;			// Maximum length of a name
    int maxValue = 4;			// Maximum length of a value

    // First pass to get the sizes
    while (argItem = psMetadataGetAndIncrement(argIter)) {
	if (strlen(argItem->name) > maxName) {
	    maxName = strlen(argItem->name);
	}
	int valLength = argLength(argItem);
	if (valLength > maxValue) {
	    maxValue = valLength;
	}
    }

    // Second pass to print
    psMetadataIteratorSet(argIter, PS_LIST_HEAD);
    psString lastName = NULL;		// Last name we printed
    while (argItem = psMetadataGetAndIncrement(argIter)) {
	// Initial indent
	for (int i = 0; i < NUM_SPACES; i++) {
	    printf(" ");
	}

	// Print the name if required
	int position = 0;	// Number of spaces in
	if (! lastName || strcmp(lastName, argItem->name) != 0) {
	    // A new name
	    printf("%s", argItem->name);
	    position += strlen(argItem->name);
	    lastName = argItem->name;
	}
	for (int i = position; i < maxName + NUM_SPACES; i++) {
	    printf(" ");
	}

	// Print the value
	printf("(");
	switch (argItem->type) {
	    // Only doing a representative set of types
	  case PS_META_S32:
	    printf("%d", argItem->data.S32);
	    break;
	    // XXX: Other numerical types
	  case PS_META_F32:
	    printf("%.6e", argItem->data.F32);
	    break;
	  case PS_META_F64:
	    printf("%.6e", argItem->data.F64);
	    break;
	  case PS_META_BOOL:
	    if (argItem->data.B) {
		printf("TRUE");
	    } else {
		printf("FALSE");
	    }
	    break;
	  case PS_META_STR:
	    printf("%s", argItem->data.V);
	    break;
	  default:
	    psAbort(__func__, "Argument type (%x) is not supported.\n", argItem->type);
	}
	printf(")");
	for (int i = argLength(argItem); i < maxValue + NUM_SPACES; i++) {
	    printf(" ");
	}

	// Print the comment
	if (argItem->comment) {
	    printf("%s", argItem->comment);
	}
	printf("\n");
    }

    psFree(argIter);
}
