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

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

#include <pslib.h>

#include "pmConfig.h"
#include "pmFPALevel.h"
#include "pmFPA.h"
#include "pmFPAview.h"
#include "pmFPAfile.h"
#include "pmConfigCamera.h"

#include "pmConfigDump.h"

// Cull entries in the metadata, ignoring the ones listed.
static bool configCull(psMetadata *md,  // Configuration metadata from which to cull
                       const psArray *list // List of items NOT to cull
    )
{
    PS_ASSERT_METADATA_NON_NULL(md, false);
    PS_ASSERT_ARRAY_NON_NULL(list, false);

    psHash *keep = psHashAlloc(list->n); // Hash with strings to keep
    for (int i = 0; i < keep->n; i++) {
        psHashAdd(keep, list->data[i], list->data[i]);
    }

    psMetadataIterator *iter = psMetadataIteratorAlloc(md, PS_LIST_HEAD, NULL); // Iterator
    psMetadataItem *item;               // Item from iteration
    while ((item = psMetadataGetAndIncrement(iter))) {
        if (!psHashLookup(keep, item->name)) {
            psMetadataRemoveKey(md, item->name);
        }
    }
    psFree(iter);

    psFree(keep);

    return true;
}

bool pmConfigRecipesCull(pmConfig *config, const char *save)
{
    PS_ASSERT_PTR_NON_NULL(config, false);

    if (!save || strlen(save) == 0) {
        return true;
    }

    psArray *keep = psStringSplitArray(save, " ,;", false); // List of items to keep

    bool result = configCull(config->recipes, keep); // Result of culling
    psFree(keep);

    return result;
}

bool pmConfigCamerasCull(pmConfig *config, const char *additional)
{
      PS_ASSERT_PTR_NON_NULL(config, false);

      psMetadata *cameras = psMetadataLookupMetadata(NULL, config->system, "CAMERAS"); // Known cameras
      if (!cameras) {
          psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find CAMERAS in the system configuration.\n");
          return NULL;
      }

      psArray *keep = NULL;             // List of cameras to keep
      if (additional) {
          keep = psStringSplitArray(additional, " ,;", false);
      } else {
          keep = psArrayAllocEmpty(1);
      }
      psArrayAdd(keep, 1, config->cameraName);

      int numKeep = keep->n;            // Number of cameras to keep
      for (int i = 0; i < numKeep; i++) {
          psString orig = keep->data[i];// Original name
          psString root = pmConfigCameraRootName(orig); // Camera root name
          psString chip = pmConfigCameraChipName(config->cameraName); // Chip-mosaicked name
          psString fpa  = pmConfigCameraFPAName(config->cameraName); // FPA-mosaicked name
          psString sky  = pmConfigCameraSkycellName(config->cameraName); // Skycell name

          // Just in case we weren't given the root name
          psFree(keep->data[i]);
          keep->data[i] = root;

          psArrayAdd(keep, 1, chip);
          psArrayAdd(keep, 1, fpa);
          psArrayAdd(keep, 1, sky);

          psFree(chip);
          psFree(fpa);
          psFree(sky);
      }

      bool result = configCull(cameras, keep); // Result of culling
      psFree(keep);

      return result;
}


bool pmConfigDump(const pmConfig *config, const pmFPA *source, const char *outroot)
{
    PS_ASSERT_PTR_NON_NULL(config, false);
    PS_ASSERT_STRING_NON_EMPTY(outroot, false);

    pmFPAview *view = source ? pmFPAviewTop(source) : pmFPAviewAlloc(0);// View to top level
    if (!view) {
        psError(PS_ERR_UNKNOWN, false, "Unable to determine top view for FPA.");
        return false;
    }

    pmFPALevel level = pmFPAviewLevel(view); // Level for view
    const char *levelString;    // String for level
    switch (level) {
      case PM_FPA_LEVEL_FPA:
        levelString = "FPA";
        break;
      case PM_FPA_LEVEL_CHIP:
        levelString = "CHIP";
        break;
      case PM_FPA_LEVEL_CELL:
        levelString = "CELL";
        break;
      default:
        psError(PS_ERR_UNKNOWN, false, "Bad FPA level: %x", level);
        psFree(view);
        return false;
    }

    psMetadata *dumpRules = psMetadataLookupMetadata(NULL, config->system, "CONFIG.DUMP"); // Name rules
    if (!dumpRules) {
        psError(PS_ERR_UNKNOWN, false, "Unable to find CONFIG.DUMP in system configuration");
        psFree(view);
        return false;
    }

    const char *rule = psMetadataLookupStr(NULL, dumpRules, levelString); // Rule for output file name
    if (!rule) {
        psError(PS_ERR_UNKNOWN, false, "Unable to find %s in CONFIG.DUMP in system configuration",
                levelString);
        psFree(view);
        return false;
    }

    psString ruleCopy = psStringCopy(rule); // Copy of rule, so we can have our way with it

    psStringSubstitute(&ruleCopy, outroot, "{OUTPUT}");

    if (strstr(ruleCopy, "{PROGRAM}")) {
        // basename() is allowed to muck with the values, so need to copy
        psString program = psStringCopy(config->program); // Copy of program name
        const char *progName = basename(program); // Basename of program
        psStringSubstitute(&ruleCopy, progName, "{PROGRAM}");
        psFree(program);
    }

    if (strstr(ruleCopy, "{TIMESTAMP}")) {
        psTime *time = psTimeGetNow(PS_TIME_UTC); // Current time
        psString timeString = psTimeToISO(time); // ISO-formatted time
        psFree(time);
        psStringSubstitute(&ruleCopy, timeString, "{TIMESTAMP}");
        psFree(timeString);
    }

    psString filename = pmFPANameFromRule(ruleCopy, source, view); // Filename for dump
    psFree(ruleCopy);
    psFree(view);
    if (!filename) {
        psError(PS_ERR_UNKNOWN, false, "Unable to determine dump filename");
        return false;
    }

    psString resolved = pmConfigConvertFilename(filename, config, true, false); // Resolved filename

    if (!psMetadataConfigWrite(config->user, resolved)) {
        psError(PS_ERR_IO, false, "Unable to dump configuration to %s", filename);
        psFree(filename);
        psFree(resolved);
        return false;
    }

    psFree(filename);
    psFree(resolved);

    return true;
}
