/*
  Video file format class according the specification of the
  "SER format description version 3" found at
  https://free-astro.org/images/5/51/SER_Doc_V3b.pdf
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include "ser.h"
#include "videodev.h"


/*
 * Convert v4l2 pixelformat to the corresponding ser format.
 * Also return depth per channel.
 */
int ser_convert_type_to_ser (int v4l2_fmt, int *pixeldepth, int *serformat) {
	if (pixeldepth == NULL || serformat == NULL) return 0;

	switch (v4l2_fmt) {
	case (V4L2_PIX_FMT_RGB24):
		*serformat = SER_COLORID_RGB;
		*pixeldepth = 8;
		break;
	case (V4L2_PIX_FMT_BGR24):
		*serformat = SER_COLORID_BGR;
		*pixeldepth = 8;
		break;
	case (V4L2_PIX_FMT_SGRBG8):
		*serformat = SER_COLORID_BAYER_GRBG;
		*pixeldepth = 8;
		break;
	case (V4L2_PIX_FMT_SGRBG16):
		*serformat = SER_COLORID_BAYER_GRBG;
		*pixeldepth = 16;
		break;
	default: return 0;
	}

	return 1;
}


SER::SER() {
  /* clear header */
  memset(&header, 0, sizeof(header));

  /* fill some initial data */
  memcpy(header.FileID, "LUCAM-RECORDER", strlen("LUCAM-RECORDER"));
  header.LuID = 0;
  header.ColorID = SER_COLORID_RGB;
  header.LittleEndian = 0;  // opposite meaning of the specification
  header.ImageWidth = 0;
  header.ImageHeight = 0;
  header.PixelDepthPerPlane = 8;
  header.FrameCount = 0;
  header.DateTime = 0;
  header.DateTimeUTC = 0;

  FileDesc = NULL;
  Frame = NULL;
  FramePointer = 0;
  FrameSize = 0;
  NumberOfPlanes = 0;
  BytesPerPixel = 0;
  Reading = 1;
  NumberOfTimeStamps = 0;
  TimeStamps = NULL;
}

/*
  Class destructor.  Free resources.
*/
SER::~SER() {

  if(FileDesc) {

    /* Update header if necessary */
    if(!Reading) {

      int err;
      int64_t offset = (int64_t)&header.FrameCount - (int64_t)&header;
      header.FrameCount = FramePointer;
      
      /* goto file beginning */
      if((err = fseek(FileDesc, offset, SEEK_SET)) == -1) {
	fprintf(stderr, "Error: failed seek SER file FrameCount (%d, %s)\n", err, strerror(errno));
      }
      else {
	/* write framecount in header data */
	if((err = fwrite(&header.FrameCount, sizeof(header.FrameCount), 1, FileDesc)) != 1) {
	  fprintf(stderr, "Error: failed writing SER file FrameCount (%d, %s)\n",
		  err, strerror(errno));
	}
      }

      /* goto file end */
      if((err = fseek(FileDesc, 0, SEEK_END)) == -1) {
	fprintf(stderr, "Error: failed seek SER file end (%d, %s)\n", err, strerror(errno));
      }
      else {
	/* write timestamp data */
	if((err = fwrite(TimeStamps, sizeof(header.DateTime), FramePointer, FileDesc)) != FramePointer) {
	  fprintf(stderr, "Error: failed writing SER file TimeStamps (%d, %s)\n",
		  err, strerror(errno));
	}
      }
    }

    /* Close file if necessary */
    fclose(FileDesc);
  }

  /* Free timestamps */
  if(TimeStamps) {
    free(TimeStamps);
  }
  
  /* Free frame memory if necessary */
  if(Frame) {
    free(Frame);
  }
}

/*
  Sets the name of the observer. E.g. "Steffen Pohle"
  40 ASCII characters {32...126 dec.}, fill unused characters with 0 dec.
*/
int SER::setObserver(char *name) {
  strncpy(header.Observer, name, sizeof(header.Observer));
  return 0;
}

/*
  Sets the name of the used camera. E.g. "Svbony SV305"
  40 ASCII characters {32...126 dec.}, fill unused characters with 0 dec.
*/
int SER::setInstrument(char *name) {
  strncpy(header.Instrument, name, sizeof(header.Instrument));
  return 0;
}

/*
  Sets the name of the used telescope. E.g. "Sky-Watcher EVOSTAR 102"
  40 ASCII characters {32...126 dec.}, fill unused characters with 0 dec.
*/
int SER::setTelescope(char *name) {
  strncpy(header.Telescope, name, sizeof(header.Telescope));
  return 0;
}

/*
  Set the file name for input/output operations.  If file exists, we open it
  for reading, otherwise we try to create the file.
*/
int SER::setFile(char *name) {

  FILE *file;

  /* try opening file for reading */
  if((file = fopen(name, "rb")) == NULL) {

    /* file does probably not exist */
    if((file = fopen(name, "wb")) == NULL) {

      /* failed to create file */
      fprintf(stderr, "Error: failed to create file '%s' (%s)\n", name, strerror(errno));
      return -1;
    }
    else {
      fprintf(stdout, "Debug: created file '%s' for writing SER file\n", name);
      FileDesc = file;
      Reading = 0;
      return 0;
    }
  }

  fprintf(stdout, "Debug: opened file '%s' for reading SER file\n", name);
  FileDesc = file;
  Reading = 1;
  return 0;
}

/*
  The function writes the file header of the SER file.
 */
int SER::writeHeader(void) {

  int err;

  /* adjust time stamps for header */
  setDateTime();

  if(FileDesc == NULL) {
    fprintf(stderr, "Error: writing header, SER file not yet specified\n");
    return -1;
  }
  
  /* goto file beginning */
  if((err = fseek(FileDesc, 0, SEEK_SET)) == -1) {
    fprintf(stderr, "Error: failed seek SER file beginning (%d, %s)\n", err, strerror(errno));
    return -1;
  }

  /* write header data */
  if((err = fwrite(&header, sizeof(header), 1, FileDesc)) != 1) {
    fprintf(stderr, "Error: failed writing SER header (%d, %s)\n", err, strerror(errno));
    return -1;
  }

  /* reset space for timestamps */
  NumberOfTimeStamps = 16;
  if(TimeStamps) {
    free(TimeStamps);
  }
  /* allocate some memory for the upcoming timestamps */
  if((TimeStamps = (int64_t *)malloc(sizeof(header.DateTime) * NumberOfTimeStamps)) == NULL) {
    fprintf(stderr, "Error: failed to allocate %llu bytes for timestamps\n",
    	(long long unsigned int) sizeof(header.DateTime) * (long)NumberOfTimeStamps);
    return -1;
  }

  FramePointer = 0;
  return 0;
}

/*
  The function reads the file header of the SER file.
 */
int SER::readHeader(void) {

  int err;

  if(FileDesc == NULL) {
    fprintf(stderr, "Error: reading header, SER file not yet specified\n");
    return -1;
  }
  
  /* goto file beginning */
  if((err = fseek(FileDesc, 0, SEEK_SET)) == -1) {
    fprintf(stderr, "Error: failed seek SER file beginning (%d, %s)\n", err, strerror(errno));
    return -1;
  }

  /* read header data */
  if((err = fread(&header, sizeof(header), 1, FileDesc)) != 1) {
    fprintf(stderr, "Error: failed reading SER header (%d, %s)\n", err, strerror(errno));
    return -1;
  }

  FramePointer = 0;
  return 0;
}

/*
  The function apppends a single frame at the current file position.
  Frame size is determined by internal header data.
 */
int SER::appendFrame(void *data) {

  if(FileDesc == NULL) {
    fprintf(stderr, "Error: appending frame, SER file not yet specified\n");
    return -1;
  }

  /* write frame data */
  if(fwrite(data, FrameSize, 1, FileDesc) != 1) {
    fprintf(stderr, "Error: failed write SER frame (%s)\n", strerror(errno));
    return -1;
  }

  /* ensure we have enough memory allocated for timestamps */
  if(FramePointer >= NumberOfTimeStamps) {
    while(FramePointer >= NumberOfTimeStamps)
      NumberOfTimeStamps *= 2;
    if((TimeStamps = (int64_t *)realloc(TimeStamps, sizeof(header.DateTime) * NumberOfTimeStamps)) == NULL) { 
      fprintf(stderr, "Error: failed to re-allocate %llu bytes for timestamps\n",
    		  (long long unsigned int) sizeof(header.DateTime) * (long)NumberOfTimeStamps);
      return -1;
    }
  }
  
  /* save timestamp for this frame */
  TimeStamps[FramePointer++] = currentDateTimeUTC();
  return 0;
}

/*
  Updates the internal data according changes in the header.
 */
void SER::updateHeaderData(void) {
  NumberOfPlanes = header.ColorID < SER_COLORID_RGB ? 1 : 3;
  BytesPerPixel = NumberOfPlanes * (header.PixelDepthPerPlane <= 8 ? 1 : 2);
  FrameSize = header.ImageWidth * header.ImageHeight * BytesPerPixel;
}

/*
  Sets the pixel depth in bits.
 */
int SER::setPixelDepth(int pixeldepth) {
  header.PixelDepthPerPlane = pixeldepth;
  updateHeaderData();
  return 0;
}

/*
  Returns the pixel depth in bits.
 */
int SER::getPixelDepth(void) {
  return header.PixelDepthPerPlane;
}

/*
  Returns the number of bytes in an allocated frame.
 */
size_t SER::getFrameSize(void) {
  updateHeaderData();
  return FrameSize;
}

/*
  Sets the image width.
 */
int SER::setWidth(int width) {
  header.ImageWidth = width;
  updateHeaderData();
  return 0;
}

/*
  Gets the image width.
 */
int SER::getWidth(void) {
  return header.ImageWidth;
}

/*
  Sets the image height.
 */
int SER::setHeight(int height) {
  header.ImageHeight = height;
  updateHeaderData();
  return 0;
}

/*
  Gets the image height.
 */
int SER::getHeight(void) {
  return header.ImageHeight;
}

/*
  Sets the color ID.
 */
int SER::setColorID(int id) {
  header.ColorID = id;
  updateHeaderData();
  return 0;
}

/*
  Allocates memory for a single frame.  Reading/writing could be done
  on this memory block.
 */
void * SER::allocFrame(void) {

  /* Drop old frame data. */
  if(Frame) {
    free(Frame);
    Frame = NULL;
  }
  
  /* Try allocating new frame data. */
  if((Frame = malloc(FrameSize)) == NULL) {
    fprintf(stderr, "Error: failed to allocate %lu bytes for frame\n", (long)FrameSize);
    return NULL;
  }
  return Frame;
}



/*
  The function reads a single frame at the current file position.
  Frame size is determined by internal header data.
 */
int SER::readFrame(void *data) {

  int err;

  /* read frame data */
  if((err = fread(data, FrameSize, 1, FileDesc)) != 1) {
    fprintf(stderr, "Error: failed reading SER frame (%d, %s)\n", err, strerror(errno));
    return -1;
  }

  /* goto next frame */
  FramePointer++;

  /* reached end of file? */
  if(FramePointer >= getNumberOfFrames()) {

    /* goto first frame beginning */
    if((err = fseek(FileDesc, sizeof(header), SEEK_SET)) == -1) {
      fprintf(stderr, "Error: failed seeking SER file frame #%d (%d, %s)\n",
	      0, err, strerror(errno));
      return -1;
    }

    /* start over at beginning */
    FramePointer = 0;
  }
  
  return 0;
}

/*
  The function reads a single frame at the given file position.
  Frame size is determined by internal header data.
 */
int SER::readFrame(void *data, int frame) {

  int err;

  /* goto frame beginning */
  if((err = fseek(FileDesc, sizeof(header) + frame * FrameSize, SEEK_SET)) == -1) {
    fprintf(stderr, "Error: failed seeking SER file frame #%d (%d, %s)\n",
	    frame, err, strerror(errno));
    return -1;
  }

  /* read frame data */
  if((err = fread(data, FrameSize, 1, FileDesc)) != 1) {
    fprintf(stderr, "Error: failed reading SER frame (%d, %s)\n", err, strerror(errno));
    return -1;
  }

  FramePointer = frame + 1;
  return 0;
}

/*
  Sets the number of frames in the header.
*/
void SER::setNumberOfFrames(int32_t frames) {
  header.FrameCount = frames;
}

/*
  Returns the number of frames in the header.
*/
int32_t SER::getNumberOfFrames(void) {
  return header.FrameCount;
}

/*
  Sets the current timestamps in the header.
 */
int64_t SER::setDateTime(void) {

  int64_t hundred_nano_seconds = currentDateTimeUTC();

  header.DateTimeUTC = hundred_nano_seconds;
  header.DateTime = hundred_nano_seconds + differenceLocalUTC();
  return header.DateTime;
}

/*
  Timestamps in SER
  -----------------
  Holds IEEE 64-bit (8-byte) values that represent dates ranging from
  January 1 of the year 0001 through December 31 of the year 9999, and
  times from 12:00:00 AM (midnight) through 11:59:59.9999999 PM. Each
  increment represents 100 nanoseconds of elapsed time since the
  beginning of January 1 of the year 1 in the Gregorian calendar. The
  maximum value represents 100 nanoseconds before the beginning of
  January 1 of the year 10000.
*/
int64_t SER::currentDateTimeUTC(void) {

  int64_t hundred_nano_seconds;
  struct timeval current_time;

  /* get time since 01.01.1970 00:00 */
  gettimeofday (&current_time, NULL);
  hundred_nano_seconds = (62135596800L + current_time.tv_sec) * 10000000L +
    current_time.tv_usec * 10L;
  return hundred_nano_seconds;
}

/*
  Determines the difference between local time and UTC time.
 */
int64_t SER::differenceLocalUTC(void) {

  time_t abs_ts, loc_ts, gmt_ts;
  struct tm loc_time_info, gmt_time_info;

  /* Absolute time stamp.*/
  time (&abs_ts);

  /* Now get once the local time for this time stamp,
     and once the GMT (UTC without summer time) time stamp.*/
  localtime_r (&abs_ts, &loc_time_info);
  gmtime_r (&abs_ts, &gmt_time_info);

  /* Convert them back.*/
  loc_ts = mktime (&loc_time_info);
  gmt_ts = mktime (&gmt_time_info);

  /* Unfortunately, GMT still has summer time. Get rid of it:*/
  if (gmt_time_info.tm_isdst == 1) gmt_ts -= 3600;

  fprintf(stdout, "Debug: difference between local time and UTC is %lld hours\n",
	  (long long int)(loc_ts - gmt_ts) / 3600);
  return ((int64_t)loc_ts - (int64_t)gmt_ts) * 10000000L;
}
