#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include "pmConfigMask.h"

psMaskType pmConfigMaskGet(const char *masks, const pmConfig *config)
{
    assert (config);
    PS_ASSERT_STRING_NON_EMPTY(masks, 0);

    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
    if (!recipe) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
        return 0;
    }

    psMaskType mask = 0;                // Mask value, to return

    psArray *names = psStringSplitArray(masks, " ,;", false); // Array of symbolic names
    for (int i = 0; i < names->n; i++) {
        const char *name = names->data[i]; // Symbolic name of interest
        bool mdok;                      // Status of MD lookup
        psMaskType value = psMetadataLookupU8(&mdok, recipe, name);
        if (!mdok) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Unable to find mask value for %s in MASK recipe", name);
            psFree(names);
            return 0;
        }
        mask |= value;
    }
    psFree(names);

    return mask;
}

bool pmConfigMaskSet(const pmConfig *config, const char *maskName, psMaskType maskValue)
{
    assert (config);
    PS_ASSERT_STRING_NON_EMPTY(maskName, false);

    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
    if (!recipe) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
        return false;
    }

    psMetadataAddU8 (recipe, PS_LIST_TAIL, maskName, PS_META_REPLACE, "user-defined mask", maskValue);

    return true;
}

// replace the named masks in the recipe with values in the header:
// replace only the names in the header in the recipe
bool pmConfigMaskReadHeader (pmConfig *config, psMetadata *header) {

    bool status = false;
    char namekey[80];
    char valuekey[80];

    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
    if (!recipe) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
        return false;
    }

    int nMask = psMetadataLookupS32 (&status, header, "MSKNUM");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "Unable to find MSKNUM in header.");
        return false;
    }

    for (int i = 0; i < nMask; i++) {

	snprintf (namekey,  64, "MSKNAM%02d", i);
	snprintf (valuekey, 64, "MSKVAL%02d", i);

	char *name = psMetadataLookupStr (&status, header, namekey);
	psU8 bit = psMetadataLookupU8 (&status, header, valuekey);

	// XXX validate that bit is a 2^n value?

	psMetadataItem *item = psMetadataLookup (header, name);
	if (!item) {
	    psWarning("mask recipe entry %s not in recipe\n", name);
	    psMetadataAddU8 (recipe, PS_LIST_TAIL, name, 0, "Bitmask bit value", bit);
	} else {
	    item->data.U8 = bit;
	}
    }
    
    return true;
}

// write the named mask bits to the header
bool pmConfigMaskWriteHeader (pmConfig *config, psMetadata *header) {

    char namekey[80];
    char valuekey[80];

    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
    if (!recipe) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
        return false;
    }

    int nMask = 0;

    psMetadataIterator *iter = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, NULL); // Iterator

    psMetadataItem *item;               // Item from iteration
    while ((item = psMetadataGetAndIncrement(iter))) {

	if (item->type != PS_DATA_U8) {
	    psWarning("mask recipe entry %s is not a bit value\n", item->name);
	    continue;
	}

	snprintf (namekey,  64, "MSKNAM%02d", nMask);
	snprintf (valuekey, 64, "MSKVAL%02d", nMask);

	psMetadataAddStr (header, PS_LIST_TAIL, namekey, 0, "Bitmask bit name", item->name);
	psMetadataAddU8 (header, PS_LIST_TAIL, valuekey, 0, "Bitmask bit value", item->data.U8);
	nMask ++;
    }
    
    psMetadataAddS32 (header, PS_LIST_TAIL, "MSKNUM", 0, "Bitmask bit count", nMask);
    return true;
}

// examine named mask values in mask recipe and set the bits for maskValue and markValue
// this function sets an appropriate value for the following required named mask concepts: 
// FLAT (used to mark out-of-range corrections in the flat-fielding)
// BLANK (used to mark non-existent pixels)
// SAT (used to mark pixels with values out-of-range on the high end)
// BAD (used to mark pixels with values out-of-range on the low end)
// If there is no explicit value for the above, the 'DETECTOR' and 'RANGE' bits are used
// If these latter do not exist, the value 0x01 is used.
// The values actually used for these names are written back to the config file
bool pmConfigMaskSetBits (psMaskType *outMaskValue, psMaskType *outMarkValue, pmConfig *config) {

    psMaskType maskValue = 0;

    // mask for generic detector defect
    psMaskType detectorMask = pmConfigMaskGet("DETECTOR", config); 
    maskValue |= detectorMask;

    // mask for dark structures
    psMaskType darkMask = pmConfigMaskGet("DARK", config);
    maskValue |= darkMask;

    // mask for non-linear flat regions (default to DETECTOR if not defined)
    psMaskType flatMask = pmConfigMaskGet("FLAT", config); 
    if (!flatMask) {
	flatMask = detectorMask;
	pmConfigMaskSet (config, "FLAT", flatMask);
    }
    if (!flatMask) {
	flatMask = 0x01;
	pmConfigMaskSet (config, "FLAT", flatMask);
    }
    maskValue |= flatMask;

    // mask for non-existent data  (default to DETECTOR if not defined)
    psMaskType blankMask = pmConfigMaskGet("BLANK", config); 
    if (!blankMask) {
	blankMask = detectorMask;
	pmConfigMaskSet (config, "BLANK", blankMask);
    }
    if (!blankMask) {
	blankMask = 0x01;
	pmConfigMaskSet (config, "BLANK", blankMask);
    }
    maskValue |= blankMask;

    // mask for generic data range errors
    psMaskType rangeMask = pmConfigMaskGet("RANGE", config);
    maskValue |= rangeMask;

    // mask for saturated data  (default to RANGE if not defined)
    psMaskType satMask = pmConfigMaskGet("SAT", config);
    if (!satMask) {
	satMask = rangeMask;
	pmConfigMaskSet (config, "SAT", satMask);
    }
    if (!satMask) {
	satMask = 0x01;
	pmConfigMaskSet (config, "SAT", satMask);
    }
    maskValue |= satMask;
    
    // mask for below-range data  (default to RANGE if not defined)
    psMaskType badMask = pmConfigMaskGet("BAD", config);
    if (!badMask) {
	badMask = rangeMask;
	pmConfigMaskSet (config, "BAD", badMask);
    }
    if (!badMask) {
	badMask = 0x01;
	pmConfigMaskSet (config, "BAD", badMask);
    }
    maskValue |= badMask;

    // XXX not sure what to do with these
    psMaskType crMask = pmConfigMaskGet("CR", config);
    maskValue |= crMask;

    psMaskType ghostMask = pmConfigMaskGet("GHOST", config);
    maskValue |= ghostMask;

    // search for an unset bit to use for MARK:
    psMaskType markValue = 0x80;

    int nBits = sizeof(psMaskType) * 8;
    for (int i = 0; !markValue && (i < nBits); i++) {
	if (maskValue & markValue) {
	    markValue >>= 1;
	} else {
	    markValue = markValue;
	}
    }
    if (!markValue) {
	psError (PS_ERR_UNKNOWN, true, "Unable to define the MARK bit mask: all bits taken!");
	return false;
    }

    // update the config table
    pmConfigMaskSet (config, "MASK.VALUE", maskValue);
    pmConfigMaskSet (config, "MARK.VALUE", markValue);

    *outMaskValue = maskValue;
    *outMarkValue = markValue;

    return true;
}
