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

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

#include "ppStats.h"
#include "ppStatsData.h"
#include "ppStatsSetupFromRecipe.h"

// Strings in a recipe may be defined multiply (with MULTI) or listed on a single line
static void listFromRecipe(psMetadata *recipe, // Recipe to search
                           const char *name, // Name for item within recipe
                           psList *target // The target list
    )
{
    // If the list already has entries, don't read anything else
    if (psListLength(target) > 0) {
        return;
    }

    // First check that at least one item of interest exists
    psMetadataItem *checkItem = psMetadataLookup(recipe, name);
    if (!checkItem) {
        // Nothing to see here
        return;
    }

    psString regex = NULL;              // Regular expression for the flag
    psStringAppend(&regex, "^%s$", name);
    psMetadataIterator *iterator = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, regex);
    psFree(regex);
    psMetadataItem *item;
    int numItem = 0; // Occurrence of the item in the recipe; to help the user in case of trouble
    while ((item = psMetadataGetAndIncrement(iterator))) {
        numItem++;
        if (item->type != PS_DATA_STRING) {
            psLogMsg(__func__, PS_LOG_WARN, "Occurrence %d of %s in the recipe is "
                     "not of type STRING (%x) --- ignored.\n", numItem, name, item->type);
            continue;
        }
        // Parse into a list of independent values
        psList *values = psStringSplit(item->data.V, " ,;", false);
        // Copy into the target
        psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
        psString valueString;
        while ((valueString = psListGetAndIncrement(valuesIter))) {
            // Remove the default _UNDEF entries in the recipe, which come from the limitation of the metadata
            // config language to have an empty MULTI, combined with the constraint that higher-level recipes
            // aren't allowed to add new values but only change what already exists.
            if (strcmp(valueString, "_UNDEF") != 0) {
                psListAdd(target, PS_LIST_TAIL, valueString);
            }
        }
        psFree(valuesIter);
        psFree(values);
    }
    psFree(iterator);

    return;
}


ppStatsData *ppStatsSetupFromRecipe(ppStatsData *data, // Data for running ppStats
                                    pmConfig *config // Configuration
    )
{
    if (!data) {
        data = ppStatsDataAlloc();
    }

    // Determine recipe parameters
    bool mdok;                          // Status of MD lookup
    psMetadata *recipe = psMetadataLookupMetadata(&mdok, config->recipes, PPSTATS_RECIPE);
    if (!mdok || !recipe) {
        psLogMsg(__func__, PS_LOG_WARN, "Unable to find recipe %s.\n", PPSTATS_RECIPE);
        return data;
    }

    listFromRecipe(recipe, "CHIP", data->chips);
    listFromRecipe(recipe, "CELL", data->cells);
    listFromRecipe(recipe, "HEADER", data->headers);
    listFromRecipe(recipe, "CONCEPT", data->concepts);

    // Parse the statistics options
    psList *recipeStats = psListAlloc(NULL); // List of statistics options
    listFromRecipe(recipe, "STAT", recipeStats);
    if (psListLength(recipeStats) > 0) {
        psListIterator *iterator = psListIteratorAlloc(recipeStats, PS_LIST_HEAD, false);
        psString statString;            // Statistic string, from iteration
        while ((statString = psListGetAndIncrement(iterator))) {
            psStatsOptions stat = psStatsOptionFromString(statString);
            if (stat == 0) {
                psLogMsg(__func__, PS_LOG_WARN, "Can't interpret STATS entry in recipe: "
                         "%s --- ignored.\n", statString);
                continue;
            }
            data->stats->options |= stat;
            data->doStats = true;
        }
    }
    psFree(recipeStats);

    // Clipping options
    if (data->stats->clipIter == 0 && isnan(data->stats->clipSigma)) {
        int iter = psMetadataLookupS32(&mdok, recipe, "ITER"); // Number of clipping iterations
        if (mdok && iter > 0) {
            data->stats->clipIter = iter;
        } else {
            psLogMsg(__func__, PS_LOG_WARN, "ITER in recipe is not of type S32 and positive --- "
                     "retaining default.\n");
        }
        float rej = psMetadataLookupF32(&mdok, recipe, "REJ"); // Clipping level
        if (mdok && rej > 0) {
            data->stats->clipSigma = rej;
        } else {
            psLogMsg(__func__, PS_LOG_WARN, "REJ in recipe is not of type F32 and positive --- "
                     "retaining default.\n");
        }
    }

    if (data->sample == 0) {
        float sample = psMetadataLookupF32(&mdok, recipe, "SAMPLE"); // Sample fraction
        if (mdok && sample > 0) {
            data->sample = sample;
        } else {
            psLogMsg(__func__, PS_LOG_WARN, "SAMPLE in recipe is not of type F32 and positive --- "
                     "retaining default.\n");
        }
    }

    if (data->maskVal == 0) {
        psMaskType maskVal = psMetadataLookupU8(&mdok, recipe, "MASKVAL"); // Mask value
        if (mdok) {
            data->maskVal = maskVal;
        } else {
            psLogMsg(__func__, PS_LOG_WARN, "ITER in recipe is not of type U8 --- retaining default.\n");
        }
    }

    return data;
}
