/*
 *   Dpx image file format library routines.
 *
 *   Copyright 1999 - 2002 David Hodson <hodsond@acm.org>
 *
 *   This program is free software; you can redistribute it and/or modify it
 *   under the terms of the GNU General Public License as published by the Free
 *   Software Foundation; either version 2 of the License, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful, but
 *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *   for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

#include "dpxfile.h"
#include "dpxlib.h"

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>        /* strftime() */
#include <sys/types.h>
#ifdef WIN32
#include <winsock.h>
#else
#include <netinet/in.h>  /* htonl() */
#endif
#include <string.h>      /* memset */
#include "../../lib/wire/iodebug.h"

static void
fillDpxChannelInfo(DpxFile* dpx, DpxChannelInformation* chan, int des) {

  chan->signage = 0;
  chan->ref_low_data = htonl(0);
  chan->ref_low_quantity = htonf(0.0);
  chan->ref_high_data = htonl(1023);
  chan->ref_high_quantity = htonf(2.046);
  chan->designator1 = des;
  chan->transfer_characteristics = 0;
  chan->colourimetry = 0;
  chan->bits_per_pixel = 10;
  chan->packing = htons(1);
  chan->encoding = 0;
  chan->data_offset = 0;
  chan->line_padding = htonl(0);
  chan->channel_padding = htonl(0);
  chan->description[0] = 0;
}

static void
dumpDpxChannelInfo(DpxChannelInformation* chan) {
  d_printf("  Signage %ld", ntohl(chan->signage));
  d_printf("  Ref low data %ld\n", ntohl(chan->ref_low_data));
  d_printf("  Ref low quantity %f\n", ntohf(chan->ref_low_quantity));
  d_printf("  Ref high data %ld\n", ntohl(chan->ref_high_data));
  d_printf("  Ref high quantity %f\n", ntohf(chan->ref_high_quantity));
  d_printf("  Designator1: %d,", chan->designator1);
  d_printf("  Bits per pixel %d\n", chan->bits_per_pixel);
  d_printf("  Packing: %d,", ntohs(chan->packing));
  d_printf("  Data Offset: %ld,", ntohl(chan->data_offset));
}

static void
fillDpxFileInfo(
  DpxFile* dpx, DpxFileInformation* fileInfo, const char* filename) {

  time_t fileClock;
  struct tm* fileTime;

  /* Note: always write files in network order */
  /* By the spec, it shouldn't matter, but ... */

  fileInfo->magic_num = htonl(DPX_FILE_MAGIC);
  fileInfo->offset = htonl(dpx->imageOffset);
  strcpy(fileInfo->vers, "v1.0");
  fileInfo->file_size = htonl(dpx->imageOffset +
    pixelsToLongs(dpx->height * dpx->width * dpx->depth) * 4);
  fileInfo->ditto_key = 0;
  fileInfo->gen_hdr_size = htonl(
    sizeof(DpxFileInformation) +
    sizeof(DpxImageInformation) +
    sizeof(DpxOriginationInformation));
  fileInfo->ind_hdr_size = htonl(sizeof(DpxMPIInformation));
  fileInfo->user_data_size = 0;
  strncpy(fileInfo->file_name, filename, 99);
  fileInfo->file_name[99] = 0;

  fileClock = time(0);
  fileTime = localtime(&fileClock);
  strftime(fileInfo->create_date, 24, "%Y:%m:%d:%H:%M:%S%Z", fileTime);
  /* Question: is %Z in strftime guaranteed to return 3 chars? */
  fileInfo->create_date[23] = 0;

  strcpy(fileInfo->creator, "David's DPX writer");
  fileInfo->project[0] = 0;
  fileInfo->copyright[0] = 0;
  fileInfo->key = 0xFFFFFFFF; /* same in any byte order */
}

static void
dumpDpxFileInfo(DpxFileInformation* fileInfo) {
  d_printf("\n--File Information--\n");
  d_printf("Magic: %8.8lX\n", ntohl(fileInfo->magic_num));
  d_printf("Image Offset %ld\n", ntohl(fileInfo->offset));
  d_printf("Version \"%s\"\n", fileInfo->vers);
  d_printf("File size %ld\n", ntohl(fileInfo->file_size));
  d_printf("Ditto key %ld\n", ntohl(fileInfo->ditto_key));
  d_printf("Generic Header size %ld\n", ntohl(fileInfo->gen_hdr_size));
  d_printf("Industry Header size %ld\n", ntohl(fileInfo->ind_hdr_size));
  d_printf("User Data size %ld\n", ntohl(fileInfo->user_data_size));
  d_printf("File name \"%s\"\n", fileInfo->file_name);
  d_printf("Creation date \"%s\"\n", fileInfo->create_date);
  d_printf("Creator \"%s\"\n", fileInfo->creator);
  d_printf("Project \"%s\"\n", fileInfo->project);
  d_printf("Copyright \"%s\"\n", fileInfo->copyright);
  d_printf("Key %ld\n", ntohl(fileInfo->key));
}

static void
fillDpxImageInfo(
  DpxFile* dpx, DpxImageInformation* imageInfo) {
  imageInfo->orientation = 0;
  imageInfo->channels_per_image = htons(1);
  imageInfo->pixels_per_line = htonl(dpx->width);
  imageInfo->lines_per_image = htonl(dpx->height);

  if (dpx->depth == 1) {
    fillDpxChannelInfo(dpx, &imageInfo->channel[0], 0);

  } else if (dpx->depth == 3) {
    fillDpxChannelInfo(dpx, &imageInfo->channel[0], 50);
  }
}

static void
dumpDpxImageInfo(DpxImageInformation* imageInfo) {

  int n;
  int i;
  d_printf("\n--Image Information--\n");
  d_printf("Image orientation %d,", ntohs(imageInfo->orientation));
  n = ntohs(imageInfo->channels_per_image);
  d_printf("Channels %d\n", n);
  d_printf("Pixels per line %ld\n", ntohl(imageInfo->pixels_per_line));
  d_printf("Lines per image %ld\n", ntohl(imageInfo->lines_per_image));
  for (i = 0; i < n; ++i) {
    d_printf("  --Channel %d--\n", i);
    dumpDpxChannelInfo(&imageInfo->channel[i]);
  }
}

static void
fillDpxOriginationInfo(
  DpxFile* dpx, DpxOriginationInformation* originInfo, DpxFileInformation* fileInfo) {
}

static void
dumpDpxOriginationInfo(DpxOriginationInformation* originInfo) {
  d_printf("\n--Origination Information--\n");
  d_printf("X offset %ld\n", ntohl(originInfo->x_offset));
  d_printf("Y offset %ld\n", ntohl(originInfo->y_offset));
  d_printf("X centre %f\n", ntohf(originInfo->x_centre));
  d_printf("Y centre %f\n", ntohf(originInfo->y_centre));
  d_printf("Original X %ld\n", ntohl(originInfo->x_original_size));
  d_printf("Original Y %ld\n", ntohl(originInfo->y_original_size));
  d_printf("File name \"%s\"\n", originInfo->file_name);
  d_printf("Creation time \"%s\"\n", originInfo->creation_time);
  d_printf("Input device \"%s\"\n", originInfo->input_device);
  d_printf("Serial number \"%s\"\n", originInfo->input_serial_number);
}

static void
initDpxMainHeader(DpxFile* dpx, DpxMainHeader* header, const char* shortFilename) {
  memset(header, 0, sizeof(DpxMainHeader));
  fillDpxFileInfo(dpx, &header->fileInfo, shortFilename);
  fillDpxImageInfo(dpx, &header->imageInfo);
  fillDpxOriginationInfo(dpx, &header->originInfo, &header->fileInfo);
#if 0
  fillDpxMPIInfo(dpx, &header->filmHeader);
#endif
}

static void
dumpDpxMainHeader(DpxMainHeader* header) {
  dumpDpxFileInfo(&header->fileInfo);
  dumpDpxImageInfo(&header->imageInfo);
  dumpDpxOriginationInfo(&header->originInfo);
#if 0
  dumpDpxMPIInformation(&header->filmHeader);
#endif
}

static int verbose = 0;
void
dpxSetVerbose(int verbosity) {
  verbose = verbosity;
}

static void
verboseMe(DpxFile* dpx) {

  d_printf("size %d x %d x %d\n", dpx->width, dpx->height, dpx->depth);
  d_printf("ImageStart %d, lineBufferLength %d, implied length %d\n",
    dpx->imageOffset, dpx->lineBufferLength * 4,
    dpx->imageOffset + pixelsToLongs(dpx->width * dpx->depth * dpx->height) * 4);
}

int
dpxGetRowBytes(DpxFile* dpx, unsigned short* row, int y) {

  /* Note: this code is bizarre because DPX files can wrap */
  /* packed longwords across line boundaries!!!! */

  size_t readLongs;
  unsigned int longIndex;
  int numPixels = dpx->width * dpx->depth;
  int pixelIndex;

  /* only seek if not reading consecutive lines */
  /* this is not quite right yet, need to account for leftovers */
  if (y != dpx->fileYPos) {
    int lineOffset = pixelsToLongs(y * dpx->width * dpx->depth) * 4;
    d_printf("Seek in getRowBytes\n");
    if (fseek(dpx->file, dpx->imageOffset + lineOffset, SEEK_SET) != 0) {
      if (verbose) d_printf("Couldn't seek to line %d at %d\n", y, dpx->imageOffset + lineOffset);
      return 1;
    }
    dpx->fileYPos = y;
  }

  /* read enough longwords */
  readLongs = pixelsToLongs(numPixels - dpx->pixelBufferUsed);
  if (fread(dpx->lineBuffer, 4, readLongs, dpx->file) != readLongs) {
    if (verbose) d_printf("Couldn't read line %d length %d\n", y, readLongs * 4);
    return 1;
  }
  ++dpx->fileYPos;

  /* convert longwords to pixels */
  pixelIndex = dpx->pixelBufferUsed;

  /* this is just strange */
  if (dpx->depth == 1) {
    for (longIndex = 0; longIndex < readLongs; ++longIndex) {
      unsigned int t = ntohl(dpx->lineBuffer[longIndex]);
      dpx->pixelBuffer[pixelIndex] = t & 0x3ff;
      t = t >> 10;
      dpx->pixelBuffer[pixelIndex+1] = t & 0x3ff;
      t = t >> 10;
      dpx->pixelBuffer[pixelIndex+2] = t & 0x3ff;
      pixelIndex += 3;
    }
  } else /* if (dpx->depth == 3) */ {
    for (longIndex = 0; longIndex < readLongs; ++longIndex) {
      unsigned int t = ntohl(dpx->lineBuffer[longIndex]);
      t = t >> 2;
      dpx->pixelBuffer[pixelIndex+2] = t & 0x3ff;
      t = t >> 10;
      dpx->pixelBuffer[pixelIndex+1] = t & 0x3ff;
      t = t >> 10;
      dpx->pixelBuffer[pixelIndex] = t & 0x3ff;
      pixelIndex += 3;
    }
  }
  dpx->pixelBufferUsed = pixelIndex;

  /* extract required pixels */
  for (pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) {
    /* row[pixelIndex] = dpx->lut10[dpx->pixelBuffer[pixelIndex]]; */
    row[pixelIndex] = dpx->pixelBuffer[pixelIndex] << 6;
  }

  /* save remaining pixels */
  while (pixelIndex < dpx->pixelBufferUsed) {
    dpx->pixelBuffer[pixelIndex - numPixels] = dpx->pixelBuffer[pixelIndex];
    ++pixelIndex;
  }
  dpx->pixelBufferUsed -= numPixels;

  /* done! */
  return 0;
}

int
dpxSetRowBytes(DpxFile* dpx, const unsigned short* row, int y) {

  /* Note: this code is bizarre because DPX files can wrap */
  /* packed longwords across line boundaries!!!! */

  size_t writeLongs;
  int longIndex;
  int numPixels = dpx->width * dpx->depth;
  int pixelIndex;
  int pixelIndex2;

  /* only seek if not reading consecutive lines */
  /* this is not quite right yet */
  if (y != dpx->fileYPos) {
    int lineOffset = pixelsToLongs(y * dpx->width * dpx->depth) * 4;
    d_printf("Seek in getRowBytes\n");
    if (fseek(dpx->file, dpx->imageOffset + lineOffset, SEEK_SET) != 0) {
      if (verbose) d_printf("Couldn't seek to line %d at %d\n", y, dpx->imageOffset + lineOffset);
      return 1;
    }
    dpx->fileYPos = y;
  }

  /* put new pixels into pixelBuffer */
  for (pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) {
    /* dpx->pixelBuffer[dpx->pixelBufferUsed + pixelIndex] = dpx->lut8[row[pixelIndex]]; */
    dpx->pixelBuffer[dpx->pixelBufferUsed + pixelIndex] = row[pixelIndex] >> 6;
  }
  dpx->pixelBufferUsed += numPixels;

  /* pack into longwords */
  writeLongs = dpx->pixelBufferUsed / 3;
  /* process whole line at image end */
  if (dpx->fileYPos == (dpx->height - 1)) {
    writeLongs = pixelsToLongs(dpx->pixelBufferUsed);
  }
  pixelIndex = 0;
  if (dpx->depth == 1) {
    for (longIndex = 0; longIndex < writeLongs; ++longIndex) {
      unsigned int t = dpx->pixelBuffer[pixelIndex] |
          (dpx->pixelBuffer[pixelIndex+1] << 10) |
          (dpx->pixelBuffer[pixelIndex+2] << 20);
      dpx->lineBuffer[longIndex] = htonl(t);
      pixelIndex += 3;
    }
  } else {
    for (longIndex = 0; longIndex < writeLongs; ++longIndex) {
      unsigned int t = dpx->pixelBuffer[pixelIndex+2] << 2 |
          (dpx->pixelBuffer[pixelIndex+1] << 12) |
          (dpx->pixelBuffer[pixelIndex] << 22);
      dpx->lineBuffer[longIndex] = htonl(t);
      pixelIndex += 3;
    }
  }

  /* write them */
  if (fwrite(dpx->lineBuffer, 4, writeLongs, dpx->file) != writeLongs) {
    if (verbose) d_printf("Couldn't write line %d length %d\n", y, writeLongs * 4);
    return 1;
  }
  ++dpx->fileYPos;

  /* save remaining pixels */
  pixelIndex2 = 0;
  while (pixelIndex < dpx->pixelBufferUsed) {
    dpx->pixelBuffer[pixelIndex2] = dpx->pixelBuffer[pixelIndex];
    ++pixelIndex;
    ++pixelIndex2;
  }
  dpx->pixelBufferUsed = pixelIndex2;

  return 0;
}


DpxFile* 
dpxOpen(const char* filename) {

  DpxMainHeader header;

  DpxFile* dpx = (DpxFile*)malloc(sizeof(DpxFile));
  if (dpx == 0) {
    if (verbose) d_printf("Failed to malloc dpx file structure.\n");
    return 0;
  }

  /* for close routine */
  dpx->file = 0;
  dpx->lineBuffer = 0;
  dpx->pixelBuffer = 0;

  dpx->file = fopen(filename, "rb");
  if (dpx->file == 0) {
    if (verbose) d_printf("Failed to open file \"%s\".\n", filename);
    dpxClose(dpx);
    return 0;
  }
  dpx->reading = 1;

  if (fread(&header, sizeof(header), 1, dpx->file) == 0) {
    if (verbose) d_printf("Not enough data for header in \"%s\".\n", filename);
    dpxClose(dpx);
    return 0;
  }

  /* let's assume dpx files are always network order */
  if (header.fileInfo.magic_num != ntohl(DPX_FILE_MAGIC)) {
    if (verbose) d_printf("Bad magic number %8.8lX in \"%s\".\n",
      ntohl(header.fileInfo.magic_num), filename);
    dpxClose(dpx);
    return 0;
  }

  if (ntohs(header.imageInfo.channel[0].packing) != 1) {
    if (verbose) d_printf("Unknown packing %d\n", header.imageInfo.channel[0].packing);
    dpxClose(dpx);
    return 0;
  }


  dpx->width = ntohl(header.imageInfo.pixels_per_line);
  dpx->height = ntohl(header.imageInfo.lines_per_image);
  dpx->depth = ntohs(header.imageInfo.channels_per_image);
  /* Another DPX vs Cineon wierdness */
  if (dpx->depth == 1) {
    switch (header.imageInfo.channel[0].designator1) {
    case 50: dpx->depth = 3; break;
    case 51: dpx->depth = 4; break;
    case 52: dpx->depth = 4; break;
    default: break;
    }
  }
  dpx->bitsPerPixel = 10;
  /* dpx->bitsPerPixel = header.imageInfo.channel[0].bits_per_pixel; */
  dpx->imageOffset = ntohl(header.fileInfo.offset);

  dpx->lineBufferLength = pixelsToLongs(dpx->width * dpx->depth);
  dpx->lineBuffer = malloc(dpx->lineBufferLength * 4);
  if (dpx->lineBuffer == 0) {
    if (verbose) d_printf("Couldn't malloc line buffer of size %d\n", dpx->lineBufferLength * 4);
    dpxClose(dpx);
    return 0;
  }

  /* could have 2 pixels left over */
  dpx->pixelBuffer = malloc((dpx->lineBufferLength * 3 + 2) * sizeof(unsigned short));
  if (dpx->pixelBuffer == 0) {
    if (verbose) d_printf("Couldn't malloc pixel buffer of size %d\n",
        (dpx->width * dpx->depth + 2 + 2) * sizeof(unsigned short));
    dpxClose(dpx);
    return 0;
  }
  dpx->pixelBufferUsed = 0;

  if (fseek(dpx->file, dpx->imageOffset, SEEK_SET) != 0) {
    if (verbose) d_printf("Couldn't seek to image data start at %d\n", dpx->imageOffset);
    dpxClose(dpx);
    return 0;
  }
  dpx->fileYPos = 0;

  logImageGetByteConversionDefaults(&dpx->params);
  setupLut(dpx);

  dpx->getRow = &dpxGetRowBytes;
  dpx->setRow = 0;
  dpx->close = &dpxClose;

  if (verbose) {
    verboseMe(dpx);
  }

  return dpx;
}

DpxFile*
dpxCreate(const char* filename, int width, int height, int depth) {

  /* Note: always write files in network order */
  /* By the spec, it shouldn't matter, but ... */

  DpxMainHeader header;
  const char* shortFilename = 0;

  DpxFile* dpx = (DpxFile*)malloc(sizeof(DpxFile));
  if (dpx == 0) {
    if (verbose) d_printf("Failed to malloc dpx file structure.\n");
    return 0;
  }

  memset(&header, 0, sizeof(header));

  /* for close routine */
  dpx->file = 0;
  dpx->lineBuffer = 0;
  dpx->pixelBuffer = 0;

  dpx->file = fopen(filename, "wb");
  if (dpx->file == 0) {
    if (verbose) d_printf("Couldn't open file %s\n", filename);
    dpxClose(dpx);
    return 0;
  }
  dpx->reading = 0;

  dpx->width = width;
  dpx->height = height;
  dpx->depth = depth;
  dpx->bitsPerPixel = 10;
  dpx->imageOffset = sizeof(DpxMainHeader);

  dpx->lineBufferLength = pixelsToLongs(dpx->width * dpx->depth);
  dpx->lineBuffer = malloc(dpx->lineBufferLength * 4);
  if (dpx->lineBuffer == 0) {
    if (verbose) d_printf("Couldn't malloc line buffer of size %d\n", dpx->lineBufferLength * 4);
    dpxClose(dpx);
    return 0;
  }

  dpx->pixelBuffer = malloc((dpx->lineBufferLength * 3 + 2) * sizeof(unsigned short));
  if (dpx->pixelBuffer == 0) {
    if (verbose) d_printf("Couldn't malloc pixel buffer of size %d\n",
        (dpx->width * dpx->depth + 2 + 2) * sizeof(unsigned short));
    dpxClose(dpx);
    return 0;
  }
  dpx->pixelBufferUsed = 0;

  /* find trailing part of filename */
  shortFilename = strrchr(filename, '/');
  if (shortFilename == 0) {
    shortFilename = filename;
  } else {
    ++shortFilename;
  }
  initDpxMainHeader(dpx, &header, shortFilename);

  if (fwrite(&header, sizeof(header), 1, dpx->file) == 0) {
    if (verbose) d_printf("Couldn't write image header\n");
    dpxClose(dpx);
    return 0;
  }
  dpx->fileYPos = 0;

  logImageGetByteConversionDefaults(&dpx->params);
  setupLut(dpx);

  dpx->getRow = 0;
  dpx->setRow = &dpxSetRowBytes;
  dpx->close = &dpxClose;

  return dpx;
}

void
dpxClose(DpxFile* dpx) {

  if (dpx == 0) {
    return;
  }

  if (dpx->file) {
    fclose(dpx->file);
    dpx->file = 0;
  }

  if (dpx->lineBuffer) {
    free(dpx->lineBuffer);
    dpx->lineBuffer = 0;
  }

  if (dpx->pixelBuffer) {
    free(dpx->pixelBuffer);
    dpx->pixelBuffer = 0;
  }

  free(dpx);
}

void
dpxDump(const char* filename) {

  DpxMainHeader header;
  FILE* file;

  file = fopen(filename, "rb");
  if (file == 0) {
    d_printf("Failed to open file \"%s\".\n", filename);
    return;
  }

  if (fread(&header, sizeof(header), 1, file) == 0) {
    d_printf("Not enough data for header in \"%s\".\n", filename);
    fclose(file);
    return;
  }

  fclose(file);
  dumpDpxMainHeader(&header);
}
