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

psMetadata *pap_psMetadataCopy(psMetadata *out,
                               const psMetadata *in)
{
    PS_ASSERT_PTR_NON_NULL(in,NULL);
    if (out ==  NULL) {
        out = psMetadataAlloc();
    }
    psMetadataItem *inItem = NULL;
    psMetadataIterator *iter = psMetadataIteratorAlloc(*(psMetadata**)&in, PS_LIST_HEAD, NULL);
    unsigned long numPointers = 0;      // Number of pointers we were forced to copy
    while ((inItem = psMetadataGetAndIncrement(iter))) {
        // Need to look for MULTI, which won't be picked up using the iterator.
        psMetadataItem *multiCheckItem = psMetadataLookup(in, inItem->name);
        unsigned int flag = PS_META_REPLACE; // Flag to indicate MULTI; otherwise, replace
        if (multiCheckItem->type == PS_DATA_METADATA_MULTI) {
            psTrace(__func__, 10, "MULTI: %s (%s)\n", inItem->name, inItem->comment);
            flag = PS_DATA_METADATA_MULTI;
        }

        psTrace(__func__, 5, "Copying %s (%s)...\n", inItem->name, inItem->comment);

        #define PS_METADATA_COPY_CASE(NAME,TYPE) \
    case PS_TYPE_##NAME: \
        if (! psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_TYPE_##NAME | flag, inItem->comment, \
                            inItem->data.TYPE)) { \
            psErrorStackPrint(stderr, "Error copying %s (%s) in the metadata\n", inItem->name, \
                              inItem->comment); \
        } \
        break;

        switch (inItem->type) {
            // Numerical types
            PS_METADATA_COPY_CASE(BOOL,B);
            PS_METADATA_COPY_CASE(S8,S8);
            PS_METADATA_COPY_CASE(S16,S16);
            PS_METADATA_COPY_CASE(S32,S32);
            PS_METADATA_COPY_CASE(U8,U8);
            PS_METADATA_COPY_CASE(U16,U16);
            PS_METADATA_COPY_CASE(U32,U32);
            PS_METADATA_COPY_CASE(F32,F32);
            PS_METADATA_COPY_CASE(F64,F64);

            // String: relying on the fact that this will copy the string, not point at it.
        case PS_DATA_STRING:
            psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_DATA_STRING | flag, inItem->comment,
                          inItem->data.V);
            break;

            // Metadata: copy the next level and stuff that in too
        case PS_DATA_METADATA: {
                psMetadata *metadata = pap_psMetadataCopy(NULL, inItem->data.md);
                psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_DATA_METADATA | flag, inItem->comment,
                              metadata);
                break;
            }
            // Other kinds of pointers
        default:
            numPointers++;
            psTrace(__func__, 10, "Copying a pointer in the metadata: %x\n", inItem->type);
            psMetadataAdd(out, PS_LIST_TAIL, inItem->name, inItem->type | flag, inItem->comment,
                          inItem->data.V);
            break;
        }
    }
    psFree(iter);

    if (numPointers > 0) {
        psLogMsg(__func__, PS_LOG_WARN, "Forced to copy %d pointers when copying metadata.  Updating the "
                 "copied psMetadata will affect the original!\n", numPointers);
    }
    return out;
}

// may need to extend this to change the keyname in the copy
bool psMetadataItemTransfer (psMetadata *out, psMetadata *in, char *key)
{

    psMetadataItem *item = psMetadataLookup (in, key);
    if (item == NULL)
        return false;

    psMetadataAddItem (out, item, PS_LIST_TAIL, PS_META_REPLACE);
    return true;
}

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_DATA_STRING:
            printf("%s", (char*)item->data.V);
            break;
        case PS_DATA_BOOL:
            if (item->data.B) {
                printf("True");
            } else {
                printf("False");
            }
            break;
        case PS_DATA_S32:
            printf("%d", item->data.S32);
            break;
        case PS_DATA_F32:
            printf("%f", item->data.F32);
            break;
        case PS_DATA_F64:
            printf("%f", item->data.F64);
            break;
        case PS_DATA_METADATA:
            printf("\n");
            psMetadataPrint(item->data.V, level + 1);
            break;
        default:
            printf("\n");
            psError(PS_ERR_IO, false, "Non-printable metadata type: %x\n", item->type);
        }
        printf("\n");
    }
    psFree(iter);

    return;
}

// XXX: This should probably be implemented using strpbrk
psList *psStringSplit(const char *string,
                      const char *splitters)
{
    psList *values = psListAlloc(NULL); // The list of values to return
    if (string == NULL)
        return values;  // NULL string is not an error, just an empty list

    unsigned int length = strlen(string); // The length of the string
    unsigned int numSplitters = strlen(splitters); // Number of characters that might split
    unsigned int start = 0;             // The position of the start of a word
    for (int i = 1; i < length; i++) {
        bool split = false;             // Is this character a splitter?
        for (int j = 0; j < numSplitters && ! split; j++) {
            if (string[i] == splitters[j]) {
                split = true;
            }
        }
        if (split) {
            if (i == start) {
                // Some idiot put in two spaces, or two commas or something
                start++;
            } else {
                // We're at the end of the word
                psString word = psStringNCopy(&string[start], i - start);
                (void)psListAdd(values, PS_LIST_TAIL, word);
                start = i + 1;
                psFree(word);
            }
        }
    }
    if (start < length) {
        // Copy the last word
        psString word = psStringNCopy(&string[start], length - start);
        (void)psListAdd(values, PS_LIST_TAIL, word);
        psFree(word);
    }

    return values;
}

psArray *psStringSplitArray (const char *string, const char *splitters)
{

    psList *list = psStringSplit (string, splitters);
    psArray *array = psListToArray (list);
    psFree (list);
    return array;
}

#ifndef whitespace
#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
#endif

/* Strip whitespace from the start and end of STRING. */
int psStringStrip (char *string)
{

    int i;

    if (string == (char *) NULL)
        return (FALSE);

    for (i = 0; whitespace (string[i]); i++)
        ;
    if (i)
        memmove (string, string + i, strlen(string+i)+1);
    for (i = strlen (string) - 1; (i > 0) && whitespace (string[i]); i--)
        ;
    string[++i] = 0;
    return (i);

}

bool psFitsWriteHeaderNotImage (psFits *fits, psMetadata *header)
{

    int status = 0;
    char fitsErr[1024];

    psMetadataItem *mdi = NULL;
    mdi = psMetadataLookup (header, "NAXIS");
    if (mdi != NULL) {
        mdi->data.S32 = 0;
        mdi->type = PS_DATA_S32;
    }

    fits_create_img (fits->fd, SHORT_IMG, 0, NULL, &status);

    if (status != 0) {
        fits_get_errstatus (status, fitsErr);
        psError (PS_ERR_IO, true, PS_ERRORTEXT_psFits_WRITE_FAILED, fitsErr);
        exit (2);
    }

    psFitsWriteHeader (header, fits);

    return true;
}
