#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <math.h>

#include "amdms.h"

#define amdmsGRANULARITY  8

#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) > (y) ? (x) : (y))
#define ABS(x) ((x) > 0 ? (x) : -(x))

amdmsCOMPL amdmsCreateAlgo(amdmsALGO_ENV **env)
{
  amdmsALGO_ENV   *henv = NULL;
  amdmsFITS_FLAGS  flags = {amdmsIMAGING_DATA_CONTENT, amdmsDEFAULT_IN_FORMAT, amdmsFLOAT_TYPE};

  if (*env == NULL) {
    henv = (amdmsALGO_ENV*)calloc(1L, sizeof(amdmsALGO_ENV));
    if (henv == NULL) {
      return amdmsFAILURE;
    }
    henv->allocated = 1;
    *env = henv;
  } else {
    henv = *env;
    henv->allocated = 0;
  }
  henv->detNX = 512;
  henv->detNY = 512;
  henv->nReads = 1;
  henv->q4Flag = amdmsFALSE;
  flags.format = amdmsDEFAULT_IN_FORMAT;
  amdmsInitFileList(&(henv->inFiles), flags);
  flags.format = amdmsDEFAULT_OUT_FORMAT;
  amdmsInitFileList(&(henv->outFiles), flags);
  amdmsInitCalibrationSetup(&(henv->calib));
  amdmsInitStripeSetup(&(henv->stripes));
  amdmsInitDataFilterSetup(&(henv->filter));
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDestroyAlgo(amdmsALGO_ENV **env)
{
  amdmsALGO_ENV   *henv = NULL;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (*env == NULL) {
    return amdmsSUCCESS;
  }
  henv = *env;
  amdmsFreeFileList(&(henv->inFiles));
  amdmsFreeFileList(&(henv->outFiles));
  amdmsFreeCalibrationSetup(&(henv->calib));
  amdmsFreeStripeSetup(&(henv->stripes));
  amdmsFreeDataFilterSetup(&(henv->filter));
  if (henv->allocated) {
    henv->allocated = amdmsFALSE;
    free(henv);
    *env = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsInitFileList(amdmsFILE_LIST *list, amdmsFITS_FLAGS flags)
{
  if (list == NULL) {
    return amdmsFAILURE;
  }
  list->defFlags = flags;
  list->nNames = 0;
  list->names = NULL;
  list->flags = NULL;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFreeFileList(amdmsFILE_LIST *list)
{
  int   i;

  if (list == NULL) {
    return amdmsFAILURE;
  }
  if (list->names != NULL) {
    for (i = 0; i < list->nNames; i++) {
      if (list->names[i] != NULL) {
        free(list->names[i]);
        list->names[i] = NULL;
      }
    }
    free(list->names);
  }
  list->names = NULL;
  list->nNames = 0;
  if (list->flags != NULL) {
    free(list->flags);
    list->flags = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsAddFileToList(amdmsFILE_LIST *list, char *name, amdmsFITS_FLAGS flags)
{
  int                i;
  size_t             len;
  char             **hnames = NULL;
  char              *hname = NULL;
  amdmsFITS_FLAGS   *hflags = NULL;

  amdmsDebug(__FILE__, __LINE__,
	    "amdmsAddFileToList(.., %s, (%d, %d, %d))",
	    name, flags.content, flags.format, flags.type);
  if (list->nNames%amdmsGRANULARITY == 0) {
    hnames = (char **)calloc((size_t)(list->nNames + amdmsGRANULARITY), sizeof(char *));
    if (hnames == NULL) {
      return amdmsFAILURE;
    }
    hflags = (amdmsFITS_FLAGS *)calloc((size_t)(list->nNames + amdmsGRANULARITY), sizeof(amdmsFITS_FLAGS));
    if (hflags == NULL) {
      free(hnames);
      return amdmsFAILURE;
    }
    for (i = 0; i < list->nNames; i++) {
      hnames[i] = list->names[i];
      hflags[i] = list->flags[i];
    }
    free(list->names);
    list->names = hnames;
    free(list->flags);
    list->flags = hflags;
  }
  len = (size_t)(strlen(name) + 1);
  hname = (char*)calloc(len, sizeof(char));
  if (hname == NULL) {
    return amdmsFAILURE;
  }
  memcpy(hname, name, len);
  list->names[list->nNames] = hname;
  list->flags[list->nNames] = flags;
  list->nNames++;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsInitStripeSetup(amdmsSTRIPE_SETUP *setup)
{
  int             iStripe;

  if (setup == NULL) {
    return amdmsFAILURE;
  }
  setup->nHStripes = 0;
  setup->nVStripes = 0;
  for (iStripe = 0; iStripe < amdmsMAX_STRIPES; iStripe++) {
    setup->hStripes[iStripe].pos = 0;
    setup->hStripes[iStripe].size = 0;
    setup->hStripes[iStripe].flags = amdmsUSE_NOTHING;
    setup->vStripes[iStripe].pos = 0;
    setup->vStripes[iStripe].size = 0;
    setup->vStripes[iStripe].flags = amdmsUSE_NOTHING;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFreeStripeSetup(amdmsSTRIPE_SETUP *setup)
{
  int             iStripe;

  if (setup == NULL) {
    return amdmsFAILURE;
  }
  setup->nHStripes = 0;
  setup->nVStripes = 0;
  for (iStripe = 0; iStripe < amdmsMAX_STRIPES; iStripe++) {
    setup->hStripes[iStripe].pos = 0;
    setup->hStripes[iStripe].size = 0;
    setup->hStripes[iStripe].flags = amdmsUSE_NOTHING;
    setup->vStripes[iStripe].pos = 0;
    setup->vStripes[iStripe].size = 0;
    setup->vStripes[iStripe].flags = amdmsUSE_NOTHING;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsRecalcStripes(amdmsSTRIPE_SETUP *setup, int nx, int ny)
{
  int  iHS;
  int  iVS;

  if (setup == NULL) {
    return amdmsFAILURE;
  }
  /* check horizontal stripes */
  setup->hStripes[0].pos = 0;
  for (iHS = 0; iHS < setup->nHStripes; iHS++) {
    if (iHS != 0) {
      /* recalculate stripe position */
      setup->hStripes[iHS].pos = setup->hStripes[iHS - 1].pos + setup->hStripes[iHS - 1].size;
    }
    if ((ny != 0) && (setup->hStripes[iHS].pos + setup->hStripes[iHS].size > ny)) {
      /* recalculate stripe height and discard next stripes */
      setup->hStripes[iHS].size = ny - setup->hStripes[iHS].pos;
      setup->nHStripes = iHS + 1;
    }
  }
  /* check vertical stripes */
  setup->vStripes[0].pos = 0;
  for (iVS = 0; iVS < setup->nVStripes; iVS++) {
    if (iVS != 0) {
      /* recalculate stripe position */
      setup->vStripes[iVS].pos = setup->vStripes[iVS - 1].pos + setup->vStripes[iVS - 1].size;
    }
    if ((nx != 0) && (setup->vStripes[iVS].pos + setup->vStripes[iVS].size > nx)) {
      /* recalculate stripe width and discard next stripes */
      setup->vStripes[iVS].size = nx - setup->vStripes[iVS].pos;
      setup->nVStripes = iVS + 1;
    }
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsInitDataFilterSetup(amdmsDATA_FILTER_SETUP *setup)
{
  if (setup == NULL) {
    return amdmsFAILURE;
  }
  setup->ioiFlag = amdmsFALSE; /* no image range specified */
  setup->ioiFrom = -1;
  setup->ioiTo = -1;
  setup->aoiFlag = amdmsFALSE; /* no image area specified */
  setup->aoiX = -1;
  setup->aoiY = -1;
  setup->aoiWidth = -1;
  setup->aoiHeight = -1;
  setup->poiFlag = amdmsFALSE; /* no pixel position specified */
  setup->poiX = -1;
  setup->poiY = -1;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFreeDataFilterSetup(amdmsDATA_FILTER_SETUP *setup)
{
  if (setup == NULL) {
    return amdmsFAILURE;
  }
  setup->ioiFlag = amdmsFALSE; /* no image range specified */
  setup->ioiFrom = -1;
  setup->ioiTo = -1;
  setup->aoiFlag = amdmsFALSE; /* no image area specified */
  setup->aoiX = -1;
  setup->aoiY = -1;
  setup->aoiWidth = -1;
  setup->aoiHeight = -1;
  setup->poiFlag = amdmsFALSE; /* no pixel position specified */
  setup->poiX = -1;
  setup->poiY = -1;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsInitData(amdmsDATA *data)
{
  if (data == NULL) {
    return amdmsFAILURE;
  }
  data->nx = 0;
  data->ny = 0;
  data->index = 0.0;
  data->data = NULL;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsAllocateData(amdmsDATA *data, int nx, int ny)
{
  if ((data->data == NULL) || (data->nx*data->ny < nx*ny)) {
    if (data->data != NULL) {
      free(data->data);
      data->data = NULL;
    }
    data->data = (amdmsPIXEL*)calloc((size_t)(nx*ny), sizeof(amdmsPIXEL));
    if (data->data == NULL) {
      return amdmsFAILURE;
    }
  }
  data->nx = nx;
  data->ny = ny;
  data->index = 0.0;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFreeData(amdmsDATA *data)
{
  if (data == NULL) {
    return amdmsFAILURE;
  }
  data->nx = 0;
  data->ny = 0;
  data->index = 0.0;
  if (data->data != NULL) {
    free(data->data);
    data->data = NULL;
  }
  return amdmsSUCCESS;  
}

amdmsCOMPL amdmsCopyData(amdmsDATA *dst, amdmsDATA *src)
{
  int          n;

  if ((dst == NULL) || (src == NULL)) {
    return amdmsFAILURE;
  }
  n = src->nx*src->ny;
  if (amdmsAllocateData(dst, src->nx, src->ny) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  dst->index = src->index;
  memcpy(dst->data, src->data, n*sizeof(amdmsPIXEL));
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsSetData(amdmsDATA *data, amdmsPIXEL value)
{
  int           i, n;
  amdmsPIXEL   *d;

  if (data == NULL) {
    return amdmsFAILURE;
  }
  n = data->nx*data->ny;
  d = data->data;
  for (i = 0; i < n; i++) {
    *d++ = value;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsSetDataR(amdmsDATA *data, int rX, int rY, int rW, int rH, amdmsPIXEL value)
{
  int           i, j;
  amdmsPIXEL   *d;

  if (data == NULL) {
    return amdmsFAILURE;
  }
  if (rX < 0) rX = 0;
  if (rX >= data->nx) rX = data->nx - 1;
  if (rX + rW > data->nx) rW = data->nx - rX;
  if (rY < 0) rY = 0;
  if (rY >= data->ny) rY = data->ny - 1;
  if (rY + rH > data->ny) rH = data->ny - rY;
  for (j = 0; j < rH; j++) {
    d = data->data + data->nx*(rY + j) + rX;
    for (i = 0; i < rW; i++) {
      *d++ = value;
    }
  }
  return amdmsSUCCESS;
}

