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

#include "amdmsFits.h"

amdmsBOOL amdmsGetColumnIndex(amdmsFITS *file, char *name, int *index)
{
  int status = 0;

  *index = -1;
  if (fits_get_colnum(file->fits, CASEINSEN, name, index, &status) != 0) {
    amdmsDebug(__FILE__, __LINE__,
	      "amdmsGetColumnIndex(%s, %s, ..) -> column not found!",
	      file->fileName, name);
    if (status != COL_NOT_FOUND) {
      amdmsReportFitsError(file, status,__LINE__, NULL);
    }
    return amdmsFALSE;
  }
  amdmsDebug(__FILE__, __LINE__, "amdmsGetColumnIndex(%s, ..) -> %d", name, *index);
  return amdmsTRUE;
}

amdmsCOMPL amdmsReadElements(amdmsFITS *file, int dataType, int iCol, long iRow, long nEls, void *values)
{
  int   status = 0;

  if (fits_read_col(file->fits, dataType, iCol, iRow, 1L, nEls, NULL, values, NULL, &status) != 0) {
    amdmsError(__FILE__, __LINE__,
	      "amdmsReadElements(%s, %d, %d, %d, %d, ...)",
	      file->fileName, dataType, iCol, iRow, nEls);
    amdmsReportFitsError(file, status, __LINE__, NULL);
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsWriteElementLong(amdmsFITS *file, int iCol, long iRow, long value, long offset)
{
    int  status = 0;

    if (fits_write_col(file->fits, TLONG, iCol, iRow, offset + 1, 1L, &value, &status) != 0)
    {
	amdmsReportFitsError(file, status, __LINE__, NULL);
	return amdmsFAILURE;
    }
    return amdmsSUCCESS;
}

/*
amdmsCOMPL amdmsWriteElementFloat(amdmsFITS *file, int iCol, int iRow, float value, int offset)
{
    int  status = 0;

    if (fits_write_col(file->fits, TFLOAT, iCol, iRow, offset + 1, 1, &value, &status) != 0)
    {
	amdmsReportFitsError(file, status, __LINE__, NULL);
	return amdmsFAILURE;
    }
    return amdmsSUCCESS;
}
*/

amdmsCOMPL amdmsWriteElementDouble(amdmsFITS *file, int iCol, long iRow, double value, long offset)
{
    int  status = 0;

    if (fits_write_col(file->fits, TDOUBLE, iCol, iRow, offset + 1, 1L, &value, &status) != 0)
    {
	amdmsReportFitsError(file, status, __LINE__, NULL);
	return amdmsFAILURE;
    }
    return amdmsSUCCESS;
}

amdmsCOMPL amdmsWriteElementString(amdmsFITS *file, int iCol, long iRow, char *value, long offset)
{
    int  status = 0;

    if (fits_write_col(file->fits, TSTRING, iCol, iRow, offset + 1, 1L, &value, &status) != 0)
    {
	amdmsReportFitsError(file, status, __LINE__, NULL);
	return amdmsFAILURE;
    }
    return amdmsSUCCESS;
}

amdmsCOMPL amdmsWriteElements(amdmsFITS *file, int dataType, int iCol, long iRow, long nEls, void *values)
{
  int   status = 0;

  amdmsDebug(__FILE__, __LINE__,
	    "WriteElements(..., %d, %d, %d, %d, ...)", dataType, iCol, iRow, nEls);
  if (fits_write_col(file->fits, dataType, iCol, iRow, 1L, nEls, values, &status) != 0) {
    amdmsReportFitsError(file, status, __LINE__, NULL);
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateTable(amdmsFITS *file, char *extName, int indexColType, int regionColType, int nReads)
{
  int status = 0;
  int iCol;
  int iRow;
  int iReg;

  if (file->currStateFile != amdmsFILE_CREATED_STATE) {
    /* amdmsVBF(amdmsVERBOSE_ERROR, (stderr, "ERROR: amdmsFITS::createTable(%s, ..), no open file!\n", extName)); */
    return amdmsFAILURE;
  }
  /* amdmsVB(amdmsVERBOSE_DEBUG_CALL, ("amdmsFITS::createTable(%s, %d, %d, %d)\n", extName, indexColType, regionColType, nReads)); */
  if ((file->hdrTable != NULL) || (file->hdrKeys != NULL)) {
    if(amdmsCreateEmptyImageCube(file) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
  }
  if (amdmsCreateImagingDetectorTable(file) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsMoveToLastHDU(file) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  file->tableExt = extName;
  file->indexColType = indexColType;
  file->regionColType = regionColType;
  file->outNCols = 0;
  if (amdmsAddColumn(file, indexColType, 1, file->indexColName, -1, NULL) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  for (iRow = 0; iRow < file->nRows; iRow++) {
    for (iCol = 0; iCol < file->nCols; iCol++) {
      iReg = iRow*file->nCols + iCol;
      if (file->data.data[iReg] != NULL) {
	free(file->data.data[iReg]);
	file->data.data[iReg] = NULL;
      }
      file->data.data[iReg] = (float*)calloc((size_t)(file->regions[iCol][iRow].size), sizeof(float));
      if (file->data.data[iReg] == NULL) {
	/* amdmsVBF(amdmsVERBOSE_OFF, (stderr, "FATAL: amdmsCreateTable(%s, ..), memory allocation failure (file->data.data[%d])!\n", extName, iReg)); */
	file->currStateFile = amdmsERROR_STATE;
	return amdmsFAILURE;
      }
      if (amdmsAddColumn(file, regionColType, file->regions[iCol][iRow].size, file->regionColName, iReg + 1, NULL) != amdmsSUCCESS) {
	return amdmsFAILURE;
      }
    }
  }
  if (fits_create_tbl(file->fits, BINARY_TBL, 0, file->outNCols, file->outColType, file->outColForm, file->outColUnit, extName, &status) !=0 ) {
    amdmsReportFitsError(file, status, __LINE__, extName);
    return amdmsFAILURE;
  }
  if (amdmsMoveToLastHDU(file) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  file->currStateHDU = amdmsTABLE_CREATED_STATE;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsOpenTable(amdmsFITS *file, char *extName, int nReads)
{
  int       status = 0;
  char      name[32];
  long      nRs;
  double    *idxCol = NULL;
  int       iImage;
  int       iCol;
  int       iRow;
  int       iReg;
  int       nReg = file->nRows*file->nCols;
  amdmsIMAGING_DETECTOR  *det;
  amdmsIMAGING_DATA      *dat;

  /* amdmsVB(amdmsVERBOSE_DEBUG_CALL, ("amdmsFITS::OpenTable(%s, %d)\n", extName, nReads)); */
  if (file->currStateFile != amdmsFILE_OPENED_STATE) {
    /* amdmsVBF(amdmsVERBOSE_ERROR, (stderr, "ERROR: amdmsFITS::OpenTable(%s, ..), no open file!\n", extName)); */
    return amdmsFAILURE;
  }
  file->tableExt = extName;
  file->nReads = nReads;
  /* it is mandatory that the requested extension exists */
  if (amdmsMoveToExtension(file, extName, BINARY_TBL, amdmsTRUE) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  det = &(file->detector);
  dat = &(file->data);
  /* read keywords from current HDU */
  dat->maxInsFlag = amdmsReadKeywordInt(file, "MAXINS", &(dat->maxIns), NULL);
  dat->maxStepFlag = amdmsReadKeywordInt(file, "MAXSTEP", &(dat->maxStep), NULL);
  /* request column indices */
  dat->frameFlag = amdmsGetColumnIndex(file, "FRAME", &(dat->frameColNr));  /* no standard column! */
  dat->timeFlag = amdmsGetColumnIndex(file, "TIME", &(dat->timeColNr));
  if (!dat->timeFlag) {
    dat->timeFlag = amdmsGetColumnIndex(file, "INDEX", &(dat->timeColNr));
  }
  dat->exptimeFlag = amdmsGetColumnIndex(file, "EXPTIME", &(dat->exptimeColNr)); /* no standard column! */
  dat->optTrainFlag = amdmsGetColumnIndex(file, "OPT_TRAIN", &(dat->optTrainColNr));
  dat->insTrainFlag = amdmsGetColumnIndex(file, "INS_TRAIN", &(dat->insTrainColNr));
  dat->referenceFlag = amdmsGetColumnIndex(file, "REFERENCE", &(dat->referenceColNr));
  dat->opdFlag = amdmsGetColumnIndex(file, "OPD", &(dat->opdColNr));
  dat->localOpdFlag = amdmsGetColumnIndex(file, "LOCALOPD", &(dat->localOpdColNr));
  dat->offsetFlag = amdmsGetColumnIndex(file, "OFFSET", &(dat->offsetColNr));
  dat->rotationFlag = amdmsGetColumnIndex(file, "ROTATION", &(dat->rotationColNr));
  dat->steppingPhaseFlag = amdmsGetColumnIndex(file, "STEPPING_PHASE", &(dat->steppingPhaseColNr));
  for (iReg = 0; iReg < nReg; iReg++) {
    dat->dataFlag = 0;
    sprintf(name, "TARGET%d", iReg + 1);
    dat->targetFlag = amdmsGetColumnIndex(file, name, &(dat->targetColNr[iReg]));
    sprintf(name, "TARTYP%d", iReg + 1);
    dat->tarTypFlag = amdmsGetColumnIndex(file, name, &(dat->tarTypColNr[iReg]));
    if ((det->regName[iReg][0] != '\0') && (det->regName[iReg][0] != ' ')) {
      dat->dataFlag = amdmsGetColumnIndex(file, det->regName[iReg], &(dat->dataColNr[iReg]));
    }
    if (!dat->dataFlag) {
      sprintf(name, "DATA%d", iReg + 1);
      dat->dataFlag = amdmsGetColumnIndex(file, name, &(dat->dataColNr[iReg]));
    }
    if (!dat->dataFlag) {
      sprintf(name, "Subwindow_%d", iReg + 1);
      dat->dataFlag = amdmsGetColumnIndex(file, name, &(dat->dataColNr[iReg]));
    }
  }
  if (fits_get_num_rows(file->fits, &nRs, &status ) != 0) {
    amdmsReportFitsError(file, status, __LINE__, NULL);
    return amdmsFAILURE;
  }
  file->nImages = nRs/nReads;
  if (file->rowIndex != NULL) {
    free(file->rowIndex);
    file->rowIndex = NULL;
  }
  file->rowIndex = (int*)calloc((size_t)(file->nImages), sizeof(int));
  if (file->rowIndex == NULL) {
    /* amdmsVBF(amdmsVERBOSE_OFF, (stderr, "FATAL: amdmsFITS::OpenTable(%s, %d), memory allocation failure (rowIndex)!\n", extName, nReads)); */
    file->currStateFile = amdmsERROR_STATE;
    return 0;
  }
  for (iImage = 0; iImage < file->nImages; iImage++) {
    file->rowIndex[iImage] = iImage*nReads + 1;
  }
  idxCol = (double*)calloc((size_t)(nRs), sizeof(double));
  if (idxCol == NULL) {
    /* amdmsVBF(amdmsVERBOSE_OFF, (stderr, "FATAL: amdmsFITS::OpenTable(%s, %d), memory allocation failure (idxCol)!\n", extName, nReads)); */
    file->currStateFile = amdmsERROR_STATE;
    return 0;
  }
  if (amdmsReadElements(file, TDOUBLE, dat->timeColNr, 1L, nRs, idxCol) != amdmsSUCCESS) {
    /* amdmsVBF(amdmsVERBOSE_ERROR, (stderr, "ERROR: amdmsFITS::OpenTable(%s, ..), reading index column!\n", extName)); */
  } else {
    iImage = 0;
    for (iRow = 0; iRow < nRs; iRow += nReads) {
      if (idxCol[iRow] != 0.0) {
	file->rowIndex[iImage++] = iRow + 1;
      }
    }
    file->nImages = iImage;
  }
  free(idxCol);
  idxCol = NULL;
  for (iRow = 0; iRow < file->nRows; iRow++) {
    for (iCol = 0; iCol < file->nCols; iCol++) {
      iReg = iRow*file->nCols + iCol;
      if (file->data.data[iReg] != NULL) {
	free(file->data.data[iReg]);
	file->data.data[iReg] = NULL;
      }
      file->data.data[iReg] = (float*)calloc((size_t)(file->regions[iCol][iRow].size), sizeof(float));
      if (file->data.data[iReg] == NULL) {
	/* amdmsVBF(amdmsVERBOSE_OFF, (stderr, "FATAL: amdmsCreateTable(%s, ..), memory allocation failure (file->data.data[%d])!\n", extName, iReg)); */
	file->currStateFile = amdmsERROR_STATE;
	return amdmsFAILURE;
      }
    }
  }
  file->currStateHDU = amdmsTABLE_OPENED_STATE;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsReadRow(amdmsFITS *file, amdmsDATA *data, int iImage, int iRead)
{
  int                     iCol;
  int                     iRow;
  long                    iIndex;
  int                     iReg;
  amdmsIMAGING_DETECTOR  *det;
  amdmsIMAGING_DATA      *dat;
  int                     xOff;    /* x offset of the upper left corner in the source image */
  int                     yOff;    /* y offset of the upper left corner in the source image */
  int                     y;       /* relative y coordinate of a subwindow */
  int                     width;   /* width of a subwindow */
  int                     height;  /* height of a subwindow */
  amdmsPIXEL             *srcData;
  char                   *str;

  /* amdmsVB(amdmsVERBOSE_DEBUG_CALL, ("amdmsFITS::ReadRow(,,%d, %d)\n", iImage, iRead)); */
  if (file->currStateHDU != amdmsTABLE_OPENED_STATE) {
    /* amdmsVBF(amdmsVERBOSE_ERROR, (stderr, "ERROR: amdmsFITS::ReadRow(,, %d, %d), no open table!\n", iImage, iRead)); */
    return amdmsFAILURE;
  }
  if (file->rowIndex == NULL) {
    iIndex = (long)(iImage*file->nReads + iRead + 1);
  } else {
    iIndex = (long)(file->rowIndex[iImage] + iRead);
  }
  if ((data->data == NULL) || (data->nx*data->ny < file->nPixels)) {
    if (data->data != NULL) {
      free(data->data);
      data->data = NULL;
    }
    data->data = (amdmsPIXEL*)calloc((size_t)(file->nPixels), sizeof(amdmsPIXEL));
    if (data->data == NULL) {
      return amdmsFAILURE;
    }
  }
  data->nx = file->nx;
  data->ny = file->ny;
  det = &(file->detector);
  dat = &(file->data);
  if (dat->frameFlag) {
    dat->frameFlag = amdmsReadElements(file, TINT, dat->frameColNr, iIndex, 1L, &(dat->frame));
  }
  if (dat->timeFlag) {
    dat->timeFlag = amdmsReadElements(file, TDOUBLE, dat->timeColNr, iIndex, 1L, &(dat->time));
    data->index = dat->time;
  }
  if (dat->exptimeFlag) {
    dat->exptimeFlag = amdmsReadElements(file, TFLOAT, dat->exptimeColNr, iIndex, 1L, &(dat->exptime));
  }
  if (dat->optTrainFlag) {
    dat->optTrainFlag = amdmsReadElements(file, TSHORT, dat->optTrainColNr, iIndex, (long)(det->maxTel), &(dat->optTrain));
  }
  if (dat->insTrainFlag) {
    dat->insTrainFlag = amdmsReadElements(file, TSHORT, dat->insTrainColNr, iIndex, (long)(dat->maxIns), &(dat->insTrain));
  }
  if (dat->referenceFlag) {
    dat->referenceFlag = amdmsReadElements(file, TSHORT, dat->referenceColNr, iIndex, 1L, &(dat->reference));
  }
  if (dat->opdFlag) {
    dat->opdFlag = amdmsReadElements(file, TDOUBLE, dat->opdColNr, iIndex, (long)(det->maxTel), &(dat->opd));
  }
  if (dat->localOpdFlag) {
    dat->localOpdFlag = amdmsReadElements(file, TDOUBLE, dat->localOpdColNr, iIndex, (long)(det->maxTel), &(dat->localOpd));
  }
  if (dat->offsetFlag) {
    dat->offsetFlag = amdmsReadElements(file, TFLOAT, dat->offsetColNr, iIndex, 2L, &(dat->offset));
  }
  if (dat->rotationFlag) {
    dat->rotationFlag = amdmsReadElements(file, TFLOAT, dat->rotationColNr, iIndex, 1L, &(dat->rotation));
  }
  if (dat->steppingPhaseFlag) {
    dat->steppingPhaseFlag = amdmsReadElements(file, TSHORT, dat->steppingPhaseColNr, iIndex, 1L, &(dat->steppingPhase));
  }
  for (iRow = 0; iRow < file->nRows; iRow++) {
    for (iCol = 0; iCol < file->nCols; iCol++) {
      iReg = iRow*file->nCols + iCol;
      if (dat->targetFlag) {
	dat->targetFlag = amdmsReadElements(file, TSHORT, dat->targetColNr[iReg], iIndex, 1L, &(dat->target[iReg]));
      }
      if (dat->tarTypFlag) {
	str = dat->tarTyp[iReg];
	dat->tarTypFlag = amdmsReadElements(file, TSTRING, dat->tarTypColNr[iReg], iIndex, 1L, &str);
      }
      if (dat->dataFlag) {
	dat->dataFlag = amdmsReadElements(file, TFLOAT, dat->dataColNr[iReg], iIndex, (long)(file->regions[iCol][iRow].size), dat->data[iReg]);
      }
    }
  }
  if ((file->nCols == 1) && (file->nRows == 1)) {
    memcpy(data->data, dat->data[0], file->nPixels*sizeof(amdmsPIXEL));
  } else {
    yOff = 0;
    for (iRow = 0; iRow < file->nRows; iRow++) {
      xOff = 0;
      height = file->regions[0][iRow].height;
      for (iCol = 0; iCol < file->nCols; iCol++) {
	iReg = iRow*file->nCols + iCol;
	width = file->regions[iCol][0].width;
	srcData = dat->data[iReg];
	for (y = 0; y < height; y++) {
	  memcpy(data->data + (yOff + y)*file->nx + xOff, srcData, width*sizeof(amdmsPIXEL));
	  srcData += width;
	}
	xOff += width;
      }
      yOff += height;
    }
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsWriteRow(amdmsFITS *file, amdmsDATA *data, int iImage, int iRead)
{
  int                 iCol;
  int                 iRow;
  int                 iReg;
  long                iIndex;
  int                 xOff;    /* x offset of the upper left corner in the source image */
  int                 yOff;    /* y offset of the upper left corner in the source image */
  int                 y;       /* relative y coordinate of a subwindow */
  int                 width;   /* width of a subwindow */
  int                 height;  /* height of a subwindow */
  amdmsIMAGING_DATA   *dat;
  amdmsPIXEL          *dstData;

  /* amdmsVB(amdmsVERBOSE_DEBUG_CALL, ("amdmsFITS::WriteRow(,,%d, %d)\n", iImage, iRead)); */
  if (file->currStateHDU != amdmsTABLE_CREATED_STATE) {
    /* amdmsVBF(amdmsVERBOSE_ERROR, (stderr, "ERROR: amdmsFITS::WriteRow(,, %d, %d), no open table!\n", iImage, iRead)); */
    return amdmsFAILURE;
  }
  iIndex = (long)(iImage*file->nReads + iRead + 1);
  dat = &(file->data);
  if (amdmsWriteElements(file, TDOUBLE, 1, iIndex, 1L, &(data->index)) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if ((file->nCols == 1) && (file->nRows == 1)) {
    memcpy(dat->data[0], data->data, file->nPixels*sizeof(amdmsPIXEL));
  } else {
    yOff = 0;
    for (iRow = 0; iRow < file->nRows; iRow++) {
      xOff = 0;
      height = file->regions[0][iRow].height;
      for (iCol = 0; iCol < file->nCols; iCol++) {
	iReg = iRow*file->nCols + iCol;
	width = file->regions[iCol][0].width;
	dstData = dat->data[iReg];
	for (y = 0; y < height; y++) {
	  memcpy(dstData, data->data + (yOff + y)*file->nx + xOff, width*sizeof(amdmsPIXEL));
	  dstData += width;
	}
	xOff += width;
      }
      yOff += height;
    }
  }
  for (iRow = 0; iRow < file->nRows; iRow++) {
    for (iCol = 0; iCol < file->nCols; iCol++) {
      iReg = iRow*file->nCols + iCol;
      if (amdmsWriteElements(file, TFLOAT, iRow*file->nCols + iCol + 2, iIndex, (long)(file->regions[iCol][iRow].size), dat->data[iReg]) != amdmsSUCCESS) {
	return amdmsFAILURE;
      }
    }
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsAddColumn(amdmsFITS *file, int colType, int nEls, char *colName, int colNameExt, char *colUnit)
{
  int    iCol = file->outNCols;
  char   fch;

  if (iCol >= amdmsMAX_BTBL_COLS) {
    return amdmsFAILURE;
  }
  if (file->outColType[iCol] == NULL) {
    file->outColType[iCol] = (char*)calloc(32L, sizeof(char));
    if (file->outColType[iCol] == NULL) {
      /* amdmsVBF(amdmsVERBOSE_OFF, (stderr, "FATAL: amdmsFITS::formatCol(%d, %d, %d, %s, %d), memory allocation failure (outColType[iCol])!\n", iCol, colType, nEls, colName, colNameExt)); */
      file->currStateFile = amdmsERROR_STATE;
      return amdmsFAILURE;
    }
  }
  if (file->outColForm[iCol] == NULL) {
    file->outColForm[iCol] = (char*)calloc(32L, sizeof(char));
    if (file->outColForm[iCol] == NULL) {
      /* amdmsVBF(amdmsVERBOSE_OFF, (stderr, "FATAL: amdmsFITS::formatCol(%d, %d, %d, %s, %d), memory allocation failure (outColForm[iCol])!\n", iCol, colType, nEls, colName, colNameExt)); */
      file->currStateFile = amdmsERROR_STATE;
      return amdmsFAILURE;
    }
  }
  if (file->outColUnit[iCol] == NULL) {
    file->outColUnit[iCol] = (char*)calloc(32L, sizeof(char));
    if (file->outColUnit[iCol] == NULL) {
      /* amdmsVBF(amdmsVERBOSE_OFF, (stderr, "FATAL: amdmsFITS::formatCol(%d, %d, %d, %s, %d), memory allocation failure (outColUnit[iCol])!\n", iCol, colType, nEls, colName, colNameExt)); */
      file->currStateFile = amdmsERROR_STATE;
      return 0;
    }
  }
  if (colNameExt != -1) {
    sprintf(file->outColType[iCol], "%s%d", colName, colNameExt);
  } else {
    strcpy(file->outColType[iCol], colName);
  }
  switch (colType) {
  case TBYTE: fch = 'B'; break;
  case TSHORT: fch = 'I'; break;
  case TINT: fch = 'J'; break;
  case TLONG: fch = 'J'; break;
  case TFLOAT: fch = 'E'; break;
  case TDOUBLE: fch = 'D'; break;
  case TSTRING: fch = 'a'; break;
  default:
    return amdmsFAILURE;
  }
  if (nEls == 1) {
    file->outColForm[iCol][0] = fch;
    file->outColForm[iCol][1] = '\0';
  } else {
    sprintf(file->outColForm[iCol], "%d%c", nEls, fch);
  }
  if (colUnit != NULL) {
    strcpy(file->outColUnit[iCol], colUnit);
  }
  /* amdmsVB(amdmsVERBOSE_DEBUG, ("formatCol(%d, ..), outColType=%s, outColForm=%s, outColUnit=%s\n", iCol, outColType[iCol], outColForm[iCol], outColUnit[iCol])); */
  file->outNCols++;
  return amdmsSUCCESS;

}

