#define DJ_DBG
#include "dpx.h"
#include <FL/Fl.H>

DpxLoad::DpxLoad()
{
  DJ_DBG_FNC("DpxLoad::DpxLoad");
  
  _lut = new PixelU8 [1024];
  for (int i = 0; i < 1024; ++i) {
    _lut[i] = i >> 2;
    //DJ_DBG_PRN("lut " << i << " = " << _lut[i]);
  }
}

bool DpxLoad::open(const Str & in)
{
  //DJ_DBG_FNC("DpxLoad::open");
  //DJ_DBG_PRN("in = " << in);
  
  if (! _f.open(in, FileIo::READ)) return false;
  
  char magic [] = "    ";
  if (! _f.get(magic, 4)) return false;
  if (0 == memCompare(magic, "SDPX", 4)) _f.endian(endian() != MSB);
  else if (0 == memCompare(magic, "XPDS", 4)) _f.endian(endian() != LSB);
  uint32_t imageOffset;
  if (! _f.getU32(&imageOffset)) return false;
  //DJ_DBG_PRN("image offset = " << int(imageOffset));
  
  if (! _f.seek(768, FileIo::SET)) return false;
  uint16_t orient;
  uint16_t elemSize;
  uint32_t size [2];
  if (! _f.getU16(&orient)) return false;
  if (! _f.getU16(&elemSize)) return false;
  if (! _f.getU32(size, 2)) return false;
  _size.set(size[0], size[1]);
  //DJ_DBG_PRN("size = " << _size);
  
  if (! _f.seek(imageOffset, FileIo::SET)) return false;
  
  return true;
}

inline static void dpxScanlineUnfill(
  const uint8_t * in, PixelU8 * out,
  int size, int bitDepth, bool endian,
  PixelU8 * lut
) {
  switch (bitDepth) {
  
    case 10: {
    
      union Data10A {
        struct Data {
          unsigned int pad:2;
          unsigned int c:10;
          unsigned int b:10;
          unsigned int a:10;
        } data;
        uint8_t bytes [4];
      };
      const Data10A * p = reinterpret_cast<const Data10A *>(in);
      const Data10A * const end = p + size / 3;
      Data10A tmp;
    
      PixelU8 * outP = reinterpret_cast<PixelU8 *>(out);
      const PixelU8 * lutP = reinterpret_cast<const PixelU8 *>(lut);
      if (endian) {
        for (; p < end; ++p, outP += 3) {
          tmp.bytes[0] = p->bytes[3];
          tmp.bytes[1] = p->bytes[2];
          tmp.bytes[2] = p->bytes[1];
          tmp.bytes[3] = p->bytes[0];
          outP[0] = lutP[tmp.data.a];
          outP[1] = lutP[tmp.data.b];
          outP[2] = lutP[tmp.data.c];
        }
      } else {
        for (; p < end; ++p, outP += 3) {
          outP[0] = lutP[p->data.a];
          outP[1] = lutP[p->data.b];
          outP[2] = lutP[p->data.c];
        }
      }
      if (endian) {
        if (size % 3) {
          tmp.bytes[0] = p->bytes[3];
          tmp.bytes[1] = p->bytes[2];
          tmp.bytes[2] = p->bytes[1];
          tmp.bytes[3] = p->bytes[0];
        }
        switch (size % 3) {
          case 1:
            outP[0] = lutP[tmp.data.a];
            break;
          case 2:
            outP[0] = lutP[tmp.data.a];
            outP[1] = lutP[tmp.data.b];
            break;
        }
      } else
        switch (size % 3) {
          case 1:
            outP[0] = lutP[p->data.a];
            break;
          case 2:
            outP[0] = lutP[p->data.a];
            outP[1] = lutP[p->data.b];
            break;
        }

    } break;
  }
}

bool DpxLoad::load(Image * out)
{
  //DJ_DBG_FNC("DpxLoad::load");
  
  const int w = _size.x(), h = _size.y();

  const int scanlineSize = ceil((w * 3) / 3.0) * 4;
  uint8_t * scanline = new uint8_t [scanlineSize];
  //DJ_DBG_PRN("scanline size = " << scanlineSize);
  
  for (int y = 0; y < h; ++y) {
    //if (! _f.get(scanline, scanlineSize)) return false;
    dpxScanlineUnfill(
      _f.mmapP(), reinterpret_cast<PixelU8 *>(out->data(0, h - 1 - y)),
      w * 3, 10, true,
      _lut
    );
    _f.seek(scanlineSize, FileIo::CURRENT);
  }
  
  delete [] scanline;
  
  return true;
}

myApp::myApp(int & argc, char ** argv) :
  App("dpx", argc, argv),
  _frame(0)
{
  for (int i = 1; i < argc; ++i)
    _input += argv[i];
  
  _window = new Window;
  _widget = new ImageViewWidget;
  VLayout * layout = new VLayout(_window);
  layout->margin(0);
  layout->add(_widget);
  
  _window->size(Vec2i(1200, 800));
  _window->show();
  
  Fl::add_idle(timerCallback, this);
  
  valid(true);
}

void myApp::timerCallback()
{
  DJ_DBG_FNC("myApp::timerCallback");
  
  if (! _load.open(_input[_frame])) return;
  _image.set(_load.size(), Pixel(PIXEL_RGB, PIXEL_U8));
  DJ_DBG_PRN("image = " << _image);
  
  if (! _load.load(&_image)) return;
  _widget->set(&_image);
  _widget->redraw();
  
  ++_frame;
  if (_frame >= _input.size()) _frame = 0;
  
  //Fl::remove_idle(timerCallback, this);
}

int main(int argc, char ** argv)
{
  DJ_DBG_LOG();
  return myApp(argc, argv).exec();
}
