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

#include <stdio.h>
#include <string.h>
#include <strings.h>            /* for strn?casecmp */
#include <pslib.h>

#include "pmConfig.h"
#include "pmDetrendDB.h"

#include "pmHDU.h"
#include "pmFPA.h"
#include "pmFPALevel.h"
#include "pmFPAview.h"
#include "pmFPAfile.h"
#include "pmFPACopy.h"
#include "pmConcepts.h"

static void pmFPAfileFree(pmFPAfile *file)
{
  if (!file) {
    return;
  }

  psTrace ("pmFPAfileFree", 5, "freeing %s\n", file->name);
  psFree (file->fpa);
  psFree (file->src);
  psFree (file->readout);
  psFree (file->names);

  psFree (file->camera);
  psFree (file->cameraName);
  psFree (file->format);
  psFree (file->formatName);
  psFree (file->name);

  if (file->fits != NULL) {
    psFitsClose (file->fits);
  }
  psFree(file->compression);
  psFree(file->options);

  psFree (file->filerule);

  psFree (file->filesrc);
  psFree (file->detrend);

  psFree (file->filename);
  psFree (file->extname);

  return;
}

pmFPAfile *pmFPAfileAlloc()
{
  pmFPAfile *file = psAlloc(sizeof(pmFPAfile));
  psMemSetDeallocator(file, (psFreeFunc) pmFPAfileFree);

  file->wrote_phu = false;
  file->readout = NULL;
  file->header = NULL;

  file->fileLevel = PM_FPA_LEVEL_NONE;
  file->dataLevel = PM_FPA_LEVEL_NONE;
  file->freeLevel = PM_FPA_LEVEL_NONE;
  file->mosaicLevel = PM_FPA_LEVEL_NONE;

  file->type = PM_FPA_FILE_NONE;
  file->mode = PM_FPA_MODE_NONE;
  file->state = PM_FPA_STATE_CLOSED;

  file->fpa = NULL;
  file->fits = NULL;
  file->compression = NULL;
  file->options = NULL;
  file->names = psMetadataAlloc();

  file->camera = NULL;
  file->cameraName = NULL;
  file->format = NULL;
  file->formatName = NULL;
  file->name = NULL;

  file->filerule = NULL;

  file->filename = NULL;
  file->extname  = NULL;

  file->filesrc = NULL;
  file->detrend = NULL;

  file->xBin = 1;
  file->yBin = 1;
  file->src = NULL;

  file->save = false;

  return file;
}

// select the readout from the named pmFPAfile; if the named file does not exist,
pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name)
{
  PS_ASSERT_PTR_NON_NULL(files, false);
  PS_ASSERT_PTR_NON_NULL(view, false);
  PS_ASSERT_PTR_NON_NULL(name, false);
  PS_ASSERT_INT_POSITIVE(strlen(name), false);

  bool status;

  pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
  if (file == NULL) {
    return NULL;
  }

  // internal files have the readout as a separate element:
  if (file->mode == PM_FPA_MODE_INTERNAL) {
    return file->readout;
  }

  pmReadout *readout = pmFPAviewThisReadout (view, file->fpa);
  return readout;
}

// select the cell from the named pmFPAfile; if the named file does not exist,
pmCell *pmFPAfileThisCell (psMetadata *files, const pmFPAview *view, const char *name)
{
  PS_ASSERT_PTR_NON_NULL(files, false);
  PS_ASSERT_PTR_NON_NULL(view, false);
  PS_ASSERT_PTR_NON_NULL(name, false);
  PS_ASSERT_INT_POSITIVE(strlen(name), false);

  bool status;

  pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
  if (file == NULL) {
    return NULL;
  }

  // internal files have the readout as a separate element:
  if (file->mode == PM_FPA_MODE_INTERNAL) {
    return NULL;
  }

  pmCell *cell = pmFPAviewThisCell(view, file->fpa);
  return cell;
}

// select the readout from the named pmFPAfile; if the named file does not exist,
pmChip *pmFPAfileThisChip (psMetadata *files, const pmFPAview *view, const char *name)
{
  PS_ASSERT_PTR_NON_NULL(files, false);
  PS_ASSERT_PTR_NON_NULL(view, false);
  PS_ASSERT_PTR_NON_NULL(name, false);
  PS_ASSERT_INT_POSITIVE(strlen(name), false);

  bool status;

  pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
  if (file == NULL) {
    return NULL;
  }

  // internal files have the readout as a separate element:
  if (file->mode == PM_FPA_MODE_INTERNAL) {
    return NULL;
  }

  pmChip *chip = pmFPAviewThisChip (view, file->fpa);
  return chip;
}

psString pmFPANameFromRule(const char *rule, const pmFPA *fpa, const pmFPAview *view)
{
  PS_ASSERT_STRING_NON_EMPTY(rule, NULL);
  PS_ASSERT_PTR_NON_NULL(view, NULL);

  psString newName = NULL;            // New name, to be returned
  newName = psStringCopy(rule);

  if (strstr (newName, "{FPA.NAME}") != NULL) {
    char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.NAME");
    if (name != NULL) {
      psStringSubstitute(&newName, "fpa", "{FPA.NAME}");
    }
  }
  if (strstr (newName, "{CHIP.NAME}") != NULL) {
    pmChip *chip = pmFPAviewThisChip (view, fpa);
    if (chip != NULL) {
      char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
      if (name != NULL) {
        psStringSubstitute(&newName, name, "{CHIP.NAME}");
      }
    }
  }
  if (strstr (newName, "{CHIP.ID}") != NULL) {
    pmChip *chip = pmFPAviewThisChip (view, fpa);
    if (chip != NULL) {
      char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.ID");
      if (name != NULL) {
        psStringSubstitute(&newName, name, "{CHIP.ID}");
      }
    }
  }
  if (strstr (newName, "{CHIP.N}") != NULL) {
    char *name = NULL;
    if (view->chip < 0) {
      psStringAppend (&name, "XX");
    } else {
      psStringAppend (&name, "%02d", view->chip);
    }
    psStringSubstitute(&newName, name, "{CHIP.N}");
    psFree (name);
  }
  if (strstr (newName, "{CELL.NAME}") != NULL) {
    pmCell *cell = pmFPAviewThisCell (view, fpa);
    if (cell != NULL) {
      char *name = psMetadataLookupStr (NULL, cell->concepts, "CELL.NAME");
      if (name != NULL) {
        psStringSubstitute(&newName, name, "{CELL.NAME}");
      }
    }
  }
  if (strstr (newName, "{CELL.N}") != NULL) {
    char *name = NULL;
    if (view->cell < 0) {
      psStringAppend (&name, "XX");
    } else {
      psStringAppend (&name, "%02d", view->cell);
    }
    psStringSubstitute(&newName, name, "{CELL.N}");
  }
  if (strstr (newName, "{EXTNAME}") != NULL) {
    pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
    if (hdu->extname && *hdu->extname) {
      psStringSubstitute(&newName, hdu->extname, "{EXTNAME}");
    }
  }
  if (strstr (newName, "{FILTER}") != NULL) {
    if (fpa != NULL) {
      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.FILTER");
      if (name && *name) {
        psStringSubstitute(&newName, name, "{FILTER}");
      }
    }
  }
  if (strstr (newName, "{FILTER.ID}") != NULL) {
    if (fpa != NULL) {
      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.FILTERID");
      if (name && *name) {
        psStringSubstitute(&newName, name, "{FILTER.ID}");
      }
    }
  }
  if (strstr (newName, "{CAMERA}") != NULL) {
    if (fpa != NULL) {
      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.INSTRUMENT");
      if (name && *name) {
        psStringSubstitute(&newName, name, "{CAMERA}");
      }
    }
  }
  if (strstr (newName, "{INSTRUMENT}") != NULL) {
    if (fpa != NULL) {
      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.INSTRUMENT");
      if (name && *name) {
        psStringSubstitute(&newName, name, "{INSTRUMENT}");
      }
    }
  }
  if (strstr (newName, "{DETECTOR}") != NULL) {
    if (fpa != NULL) {
      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.DETECTOR");
      if (name && *name) {
        psStringSubstitute(&newName, name, "{DETECTOR}");
      }
    }
  }
  if (strstr (newName, "{TELESCOPE}") != NULL) {
    if (fpa != NULL) {
      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.TELESCOPE");
      if (name && *name) {
        psStringSubstitute(&newName, name, "{TELESCOPE}");
      }
    }
  }
  return newName;
}

// select the rule from the camera configuration, perform substitutions as needed
psString pmFPAfileNameFromRule(const char *rule, const pmFPAfile *file, const pmFPAview *view)
{
  PS_ASSERT_PTR_NON_NULL(rule, NULL);
  PS_ASSERT_INT_POSITIVE(strlen(rule), NULL);
  PS_ASSERT_PTR_NON_NULL(file, NULL);
  PS_ASSERT_PTR_NON_NULL(view, NULL);

  psString newRule = NULL;            // Rule to pass on to pmFPANameFromRule
  newRule = psStringCopy(rule);

  if (strstr(newRule, "{OUTPUT}") != NULL) {
    char *name = psMetadataLookupStr(NULL, file->names, "OUTPUT");
    if (name) {
      psStringSubstitute(&newRule, name, "{OUTPUT}");
    }
  }

  psString newName = pmFPANameFromRule(newRule, file->fpa, view); // New name, to be returned
  psFree(newRule);

  return newName;
}

// given an already-opened fits file, write the components corresponding
// to the specified view
bool pmFPAfileCopyView (pmFPA *out, pmFPA *in, const pmFPAview *view)
{
  PS_ASSERT_PTR_NON_NULL(out, false);
  PS_ASSERT_PTR_NON_NULL(in, false);
  PS_ASSERT_PTR_NON_NULL(view, false);

  // pmFPAWrite takes care of all PHUs as needed
  if (view->chip == -1) {
    pmFPACopy (out, in);
    return true;
  }
  if (view->chip >= in->chips->n) {
    psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
    return false;
  }
  pmChip *inChip = in->chips->data[view->chip];
  pmChip *outChip = out->chips->data[view->chip];

  if (view->cell == -1) {
    pmChipCopy (outChip, inChip);
    return true;
  }
  if (view->cell >= inChip->cells->n) {
    psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
            view->cell, inChip->cells->n);
    return false;
  }
  pmCell *inCell = inChip->cells->data[view->cell];
  pmCell *outCell = outChip->cells->data[view->cell];

  if (view->readout == -1) {
    pmCellCopy (outCell, inCell);
    return true;
  }
  psError(PS_ERR_UNKNOWN, true, "Returning false");
  return false;

  // XXX add readout / segment equivalents
}

// given an already-opened fits file, write the components corresponding
// to the specified view
bool pmFPAfileCopyStructureView (pmFPA *out, const pmFPA *in, int xBin, int yBin, const pmFPAview *view)
{
  bool status;
  PS_ASSERT_PTR_NON_NULL(out, false);
  PS_ASSERT_PTR_NON_NULL(in, false);
  PS_ASSERT_PTR_NON_NULL(view, false);

  // XXX this should be smarter (ie, only copy concepts from the current chips)
  // but such a call is needed, so re-copy stuff rather than no copy
  pmFPACopyConcepts (out, in);

  // pmFPAWrite takes care of all PHUs as needed
  if (view->chip == -1) {
    status = pmFPACopyStructure (out, in, xBin, yBin);
    return status;
  }
  if (view->chip >= in->chips->n) {
    psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
    return false;
  }
  pmChip *inChip = in->chips->data[view->chip];
  pmChip *outChip = out->chips->data[view->chip];

  if (view->cell == -1) {
    status = pmChipCopyStructure (outChip, inChip, xBin, yBin);
    return status;
  }
  if (view->cell >= inChip->cells->n) {
    psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
            view->cell, inChip->cells->n);
    return false;
  }
  pmCell *inCell = inChip->cells->data[view->cell];
  pmCell *outCell = outChip->cells->data[view->cell];

  status = pmCellCopyStructure (outCell, inCell, xBin, yBin);
  return status;
}

pmFPAfileType pmFPAfileTypeFromString(const char *type)
{
  PS_ASSERT_STRING_NON_EMPTY(type, PM_FPA_FILE_NONE);

  if (!strcasecmp (type, "SX"))     {
    return PM_FPA_FILE_SX;
  }
  if (!strcasecmp (type, "OBJ"))     {
    return PM_FPA_FILE_OBJ;
  }
  if (!strcasecmp (type, "CMP"))     {
    return PM_FPA_FILE_CMP;
  }
  if (!strcasecmp (type, "CMF"))     {
    return PM_FPA_FILE_CMF;
  }
  if (!strcasecmp (type, "RAW"))     {
    return PM_FPA_FILE_RAW;
  }
  if (!strcasecmp (type, "IMAGE"))     {
    return PM_FPA_FILE_IMAGE;
  }
  if (!strcasecmp (type, "PSF"))     {
    return PM_FPA_FILE_PSF;
  }
  if (!strcasecmp (type, "JPEG"))     {
    return PM_FPA_FILE_JPEG;
  }
  if (!strcasecmp (type, "KAPA"))     {
    return PM_FPA_FILE_KAPA;
  }
  if (!strcasecmp (type, "MASK"))     {
    return PM_FPA_FILE_MASK;
  }
  if (!strcasecmp (type, "WEIGHT"))     {
    return PM_FPA_FILE_WEIGHT;
  }
  if (!strcasecmp (type, "FRINGE")) {
    return PM_FPA_FILE_FRINGE;
  }
  if (!strcasecmp (type, "DARK"))     {
    return PM_FPA_FILE_DARK;
  }
  if (!strcasecmp (type, "HEADER"))     {
    return PM_FPA_FILE_HEADER;
  }
  // deprecate this?
  if (!strcasecmp (type, "ASTROM"))     {
    return PM_FPA_FILE_ASTROM_MODEL;
  }
  if (!strcasecmp (type, "ASTROM.MODEL"))     {
    return PM_FPA_FILE_ASTROM_MODEL;
  }
  if (!strcasecmp (type, "ASTROM.REFSTARS"))     {
    return PM_FPA_FILE_ASTROM_REFSTARS;
  }

  return PM_FPA_FILE_NONE;
}

const char *pmFPAfileStringFromType(pmFPAfileType type)
{
  switch (type) {
    case PM_FPA_FILE_SX:
      return ("SX");
    case PM_FPA_FILE_OBJ:
      return ("OBJ");
    case PM_FPA_FILE_CMP:
      return ("CMP");
    case PM_FPA_FILE_CMF:
      return ("CMF");
    case PM_FPA_FILE_RAW:
      return ("RAW");
    case PM_FPA_FILE_IMAGE:
      return ("IMAGE");
    case PM_FPA_FILE_PSF:
      return ("PSF");
    case PM_FPA_FILE_JPEG:
      return ("JPEG");
    case PM_FPA_FILE_KAPA:
      return ("KAPA");
    case PM_FPA_FILE_MASK:
      return ("MASK");
    case PM_FPA_FILE_WEIGHT:
      return ("WEIGHT");
    case PM_FPA_FILE_FRINGE:
      return ("FRINGE");
    case PM_FPA_FILE_DARK:
      return("DARK");
    case PM_FPA_FILE_HEADER:
      return ("HEADER");
    case PM_FPA_FILE_ASTROM_MODEL:
      return ("ASTROM.MODEL");
    case PM_FPA_FILE_ASTROM_REFSTARS:
      return ("ASTROM.REFSTARS");
    default:
      return ("NONE");
  }
  return ("NONE");
}


psArray *pmFPAfileSelect(psMetadata *files, const char *name)
{
    PS_ASSERT_PTR_NON_NULL(files, NULL);

    psList *list = psListAlloc(NULL);   // List of files selected

    psString regex = NULL;              // Regular expression
    if (name) {
        if (!psMetadataLookup(files, name)) {
            psFree (list);
            return NULL;
        }
        psStringAppend(&regex, "^%s$", name);
    }
    psMetadataIterator *iter = psMetadataIteratorAlloc(files, PS_LIST_HEAD, regex); // Iterator
    psFree(regex);
    psMetadataItem *item;               // Item from iteration
    while ((item = psMetadataGetAndIncrement(iter))) {
        pmFPAfile *file = item->data.V; // File of iterest
        psListAdd(list, PS_LIST_TAIL, file);
    }
    psFree(iter);

    psArray *array = psListToArray(list); // Array generated from list
    psFree(list);

    return array;
}

pmFPAfile *pmFPAfileSelectSingle(psMetadata *files, const char *name, int num)
{
    PS_ASSERT_PTR_NON_NULL(files, NULL);
    PS_ASSERT_INT_NONNEGATIVE(num, NULL);

    psString regex = NULL;              // Regular expression
    if (name) {
        if (!psMetadataLookup(files, name)) {
            // No files
            return NULL;
        }
        psStringAppend(&regex, "^%s$", name);
    }

    psMetadataIterator *iter = psMetadataIteratorAlloc(files, PS_LIST_HEAD, regex); // Iterator
    psFree(regex);
    psMetadataItem *item;               // Item from iteration
    int i = 0;                          // Counter
    while ((item = psMetadataGetAndIncrement(iter))) {
        if (i++ == num) {
            psFree(iter);
            return item->data.V;
        }
    }
    psFree(iter);

    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find instance %d of file %s", num, name);
    return NULL;
}
