/*------------------------------------------------------------------------------
 src/lib/djImage/imageIoDpxScanline.cxx
 
 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include "djImageIoDpx.h"

namespace dj {
namespace ImageIoDpx {

int dpxScanlineSize(
  int width, int channels, int bitDepth, bool packing, int linePadding
) {
  DJ_DBG_FNC("dj::ImageIoDpx::dpxScanlineSize");
  DJ_DBG_PRN("width = " << width);
  DJ_DBG_PRN("channels = " << channels);
  DJ_DBG_PRN("bit depth = " << bitDepth);
  DJ_DBG_PRN("packing = " << packing);
  DJ_DBG_PRN("line padding = " << linePadding);
  
  int r = 0;
  
  switch (bitDepth) {

    case 8:
      
      // XXX Padded to 32-bit alignment?
      //
      r = ceil((width * channels * bitDepth) / 32.0) * 4;
      //r = width * channels;
      break;

    case 10:
      if (packing) {
        r = ceil((width * channels * bitDepth) / 32.0) * 4;
      } else {
        r = ceil((width * channels) / 3.0) * 4;
      }
      break;

    case 12:
      if (packing) {
        r = ceil((width * channels * bitDepth) / 32.0) * 4;
      } else {
      
        // XXX 12-bit data in 32 or 16 bit words?
        //
        //r = ceil((width * channels) / 2.0) * 4;
        r = width * channels * 2;
      }
      break;

    case 16:
      r = width * channels * 2;
      break;
  }

  return r + linePadding;
}

#define _PACK_8(TYPE, OP) \
  const TYPE * inP = reinterpret_cast<const TYPE *>(in); \
  const TYPE * const end = inP + size; \
  uint8_t * outP = reinterpret_cast<uint8_t *>(out); \
  const PixelU8 * lutP = reinterpret_cast<const PixelU8 *>(lut); \
  while (inP < end) \
    *outP++ = lutP[*inP++];

#define _PACK_16(TYPE, OP) \
  const TYPE * inP = reinterpret_cast<const TYPE *>(in); \
  const TYPE * const end = inP + size; \
  uint16_t * outP = reinterpret_cast<uint16_t *>(out); \
  const PixelU16 * lutP = reinterpret_cast<const PixelU16 *>(lut); \
  if (endian) \
    for (; inP < end; ++inP) \
      *outP++ = ((lutP[*inP] & 255) << 8) | (lutP[*inP] >> 8 & 255); \
  else \
    while (inP < end) \
      *outP++ = lutP[*inP++];

#define _PACK_OP(IN) (lutP[IN] >> shift & mask)

#define _PACK(TYPE, OP) \
  const int shift = 16 - bitDepth; \
  const int mask = (1 << bitDepth) - 1; \
  const TYPE * inP = reinterpret_cast<const TYPE *>(in); \
  for (int i = 0; i < size; ++i, ++inP) { \
    if ((offset + bitDepth) < 32) { \
      *outP = *outP | OP(*inP) << offset; \
      offset += bitDepth; \
    } else { \
      const int bits1 = 32 - offset; \
      const int bits2 = bitDepth - bits1; \
      *outP = *outP | (OP(*inP) & ((1 << bits1) - 1)) << offset; \
      if (endian) dj::endian(outP, 1, 4); \
      ++outP; \
      *outP = OP(*inP) >> bits1; \
      offset = bits2; \
    } \
  }

void dpxScanlinePack(
  const uint8_t * in, uint8_t * out, const uint8_t * lut,
  int size, PIXEL_TYPE type, int bitDepth, bool endian
) {
  DJ_DBG_FNC("dj::ImageIoDpx::dpxScanlinePack");
  DJ_DBG_PRN("size = " << size);
  DJ_DBG_PRN("type = " << type);
  DJ_DBG_PRN("bit depth = " << bitDepth);
  DJ_DBG_PRN("endian = " << endian);
  
  switch (bitDepth) {
  
    case 8: {
      switch (type) {
        case PIXEL_U8: { _PACK_8(PixelU8, _PACK_OP) } break;
        case PIXEL_U16: { _PACK_8(PixelU16, _PACK_OP) } break;
        case PIXEL_F32: break;
      }
    } break;
    
    case 16: {
      switch (type) {
        case PIXEL_U8: { _PACK_16(PixelU8, _PACK_OP) } break;
        case PIXEL_U16: { _PACK_16(PixelU16, _PACK_OP) } break;
        case PIXEL_F32: break;
      }
    } break;
    
    case 10:
    case 12: {
      int offset = 0;
      uint32_t * outP = reinterpret_cast<uint32_t *>(out);
      const PixelU16 * lutP = reinterpret_cast<const PixelU16 *>(lut);
      if (size) *outP = 0;  
      switch (type) {
        case PIXEL_U8: { _PACK(PixelU8, _PACK_OP) } break;
        case PIXEL_U16: { _PACK(PixelU16, _PACK_OP) } break;
        case PIXEL_F32: break;
      }
    } break;
  }
}

#define _UNPACK_8(TYPE) \
  const uint8_t * const end = in + size; \
  TYPE * outP = reinterpret_cast<TYPE *>(out); \
  const TYPE * lutP = reinterpret_cast<const TYPE *>(lut); \
  while (in < end) \
    *outP++ = lutP[*in++];

#define _UNPACK_16(TYPE) \
  const uint16_t * inP = reinterpret_cast<const uint16_t *>(in); \
  const uint16_t * const end = inP + size; \
  TYPE * outP = reinterpret_cast<TYPE *>(out); \
  const TYPE * lutP = reinterpret_cast<const TYPE *>(lut); \
  if (endian) \
    for (; inP < end; ++inP) \
      *outP++ = lutP[((*inP & 255) << 8) | (*inP >> 8 & 255)]; \
  else \
    while (inP < end) \
      *outP++ = lutP[*inP++];

#define _UNPACK(TYPE) \
  TYPE * outP = reinterpret_cast<TYPE *>(out); \
  const TYPE * lutP = reinterpret_cast<const TYPE *>(lut); \
  for (int i = 0; i < size; ++i) { \
    if ((offset + bitDepth) < 32) { \
      *outP++ = lutP[tmp >> offset & mask]; \
      offset += bitDepth; \
    } else { \
      const int bits1 = 32 - offset; \
      const int bits2 = bitDepth - bits1; \
      inP++; \
      tmp2 = *inP; \
      if (endian) dj::endian(&tmp2, 1, 4); \
      *outP++ = lutP[ \
        (tmp >> offset) & ((1 << bits1) - 1) | \
        ((tmp2 & ((1 << bits2) - 1)) << bits1) \
      ]; \
      tmp = tmp2; \
      offset = bits2; \
    } \
  }
  
void dpxScanlineUnpack(
  const uint8_t * in, uint8_t * out, const uint8_t * lut,
  int size, PIXEL_TYPE type, int bitDepth, bool endian
) {
  DJ_DBG_FNC("dj::ImageIoDpx::dpxScanlineUnpack");
  DJ_DBG_PRN("size = " << size);
  DJ_DBG_PRN("type = " << type);
  DJ_DBG_PRN("bit depth = " << bitDepth);
  DJ_DBG_PRN("endian = " << endian);
  
  switch (bitDepth) {
  
    case 8: {
      switch (type) {
        case PIXEL_U8: { _UNPACK_8(PixelU8) } break;
        case PIXEL_U16: { _UNPACK_8(PixelU16) } break;
        case PIXEL_F32: { _UNPACK_8(PixelF32) } break;
      }
    } return;
    
    case 16: {
      switch (type) {
        case PIXEL_U8: { _UNPACK_16(PixelU8) } break;
        case PIXEL_U16: { _UNPACK_16(PixelU16) } break;
        case PIXEL_F32: { _UNPACK_16(PixelF32) } break;
      }
    } return;
    
    case 10:
    case 12: {
      const int mask = (1 << bitDepth) - 1;
      int offset = 0;
      uint32_t tmp = 0, tmp2 = 0;
      const uint32_t * inP = reinterpret_cast<const uint32_t *>(in);
      if (size) {
        tmp = *inP;
        if (endian) dj::endian(&tmp, 1, 4);
      }
      switch (type) {
        case PIXEL_U8: { _UNPACK(PixelU8) } break;
        case PIXEL_U16: { _UNPACK(PixelU16) } break;
        case PIXEL_F32: { _UNPACK(PixelF32) } break;
      }
    }
  }
}

#define _FILL_OP(IN) (lutP[IN] >> shift & mask)

#define _FILL_10(TYPE, OP) \
  const TYPE * inP = reinterpret_cast<const TYPE *>(in); \
  if (endian) \
    for (; p < end; inP += 3) \
      *p++ = \
        (((OP(inP[0]) >> 2)) & 0xff) | \
        (((OP(inP[1]) >> 4) | (OP(inP[0]) << 6)) & 0xff) << 8 | \
        (((OP(inP[2]) >> 6) | (OP(inP[1]) << 4)) & 0xff) << 16 | \
                            (((OP(inP[2]) << 2)) & 0xff) << 24; \
  else \
    for (; p < end; inP += 3) \
      *p++ = \
        (((OP(inP[0]) >> 2)) & 0xff) << 24 | \
        (((OP(inP[1]) >> 4) | (OP(inP[0]) << 6)) & 0xff) << 16 | \
        (((OP(inP[2]) >> 6) | (OP(inP[1]) << 4)) & 0xff) << 8 | \
                            (((OP(inP[2]) << 2)) & 0xff); \
  if (endian) \
    switch (size % 3) { \
      case 1: \
        *p++ = \
          (((OP(inP[0]) >> 2)) & 0xff) | \
            (((OP(inP[0]) << 6)) & 0xff) << 8; \
        break; \
      case 2: \
        *p++ = \
          (((OP(inP[0]) >> 2)) & 0xff) | \
          (((OP(inP[1]) >> 4) | (OP(inP[0]) << 6)) & 0xff) << 8 | \
                              (((OP(inP[1]) << 4)) & 0xff) << 16; \
        break; \
    } \
  else \
    switch (size % 3) { \
      case 1: \
        *p++ = \
          (((OP(inP[0]) >> 2)) & 0xff) << 24 | \
            (((OP(inP[0]) << 6)) & 0xff) << 16; \
        break; \
      case 2: \
        *p++ = \
          (((OP(inP[0]) >> 2)) & 0xff) << 24 | \
          (((OP(inP[1]) >> 4) | (OP(inP[0]) << 6)) & 0xff) << 16 | \
                              (((OP(inP[1]) << 4)) & 0xff) << 8; \
        break; \
    }

#define _FILL_12(TYPE, OP) \
  const TYPE * inP = reinterpret_cast<const TYPE *>(in); \
  if (endian) \
    for (; p < end; ++inP) \
      *p++ = \
        ((OP(*inP) >> 4) & 0xff) | \
        ((OP(*inP) << 4) & 0xff) << 8; \
  else \
    for (; p < end; ++inP) \
      *p++ = \
        ((OP(*inP) >> 4) & 0xff) << 8 | \
        ((OP(*inP) << 4) & 0xff);

void dpxScanlineFill(
  const uint8_t * in, uint8_t * out, const uint8_t * lut,
  int size, PIXEL_TYPE type, int bitDepth, bool endian
) {
  DJ_DBG_FNC("dj::ImageIoDpx::dpxScanlineFill");
  DJ_DBG_PRN("size = " << size);
  DJ_DBG_PRN("type = " << type);
  DJ_DBG_PRN("bit depth = " << bitDepth);
  DJ_DBG_PRN("endian = " << endian);
  
  const int shift = 16 - bitDepth;
  const int mask = (1 << bitDepth) - 1;
  switch (bitDepth) {
  
    case 10: {
      uint32_t * p = reinterpret_cast<uint32_t *>(out);
      const uint32_t * const end = p + size / 3;
      const PixelU16 * lutP = reinterpret_cast<const PixelU16 *>(lut);
      switch (type) {
        case PIXEL_U8: { _FILL_10(PixelU8, _FILL_OP) } break;
        case PIXEL_U16: { _FILL_10(PixelU16, _FILL_OP) } break;
        case PIXEL_F32: break;
      }
    } break;
    
    case 12: {
      uint16_t * p = reinterpret_cast<uint16_t *>(out);
      const uint16_t * const end = p + size;
      const PixelU16 * lutP = reinterpret_cast<const PixelU16 *>(lut);
      switch (type) {
        case PIXEL_U8: { _FILL_12(PixelU8, _FILL_OP) } break;
        case PIXEL_U16: { _FILL_12(PixelU16, _FILL_OP) } break;
        case PIXEL_F32: break;
      }
      
    } break;
  }
}

#define _UNFILL_10_LSB \
  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]; \
  };

#define _UNFILL_10_MSB \
  union Data10A { \
    struct Data { \
      unsigned int a:10; \
      unsigned int b:10; \
      unsigned int c:10; \
      unsigned int pad:2; \
    } data; \
    uint8_t bytes [4]; \
  };

#define _UNFILL_10(TYPE, ENDIAN) \
  ENDIAN \
  const Data10A * p = reinterpret_cast<const Data10A *>(in); \
  const Data10A * const end = p + size / 3; \
  Data10A tmp; \
  TYPE * outP = reinterpret_cast<TYPE *>(out); \
  const TYPE * const lutP = reinterpret_cast<const TYPE *>(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; \
    }

/*#define _UNFILL_10_OP(IN, BYTE_1, SHIFT_1, BYTE_2, SHIFT_2) \
  lutP[ \
    (((IN >> BYTE_1 & 0xff) >> SHIFT_1) | \
     ((IN >> BYTE_2 & 0xff) << SHIFT_2)) & mask \
  ]

#define _UNFILL_10(TYPE) \
  TYPE * outP = reinterpret_cast<TYPE *>(out); \
  const TYPE * lutP = reinterpret_cast<const TYPE *>(lut); \
  if (endian) \
    for (; p < end; ++p, outP += 3) { \
      outP[0] = _UNFILL_10_OP(*p, 8, 6, 0, 2); \
      outP[1] = _UNFILL_10_OP(*p, 16, 4, 8, 4); \
      outP[2] = _UNFILL_10_OP(*p, 24, 2, 16, 6); \
    } \
  else \
    for (; p < end; ++p, outP += 3) { \
      outP[0] = _UNFILL_10_OP(*p, 16, 6, 24, 2); \
      outP[1] = _UNFILL_10_OP(*p, 8, 4, 16, 4); \
      outP[2] = _UNFILL_10_OP(*p, 0, 2, 8, 6); \
    } \
  if (endian) \
    switch (size % 3) { \
      case 1: \
        outP[0] = _UNFILL_10_OP(*p, 8, 6, 0, 2); \
        break; \
      case 2: \
        outP[0] = _UNFILL_10_OP(*p, 8, 6, 0, 2); \
        outP[1] = _UNFILL_10_OP(*p, 16, 4, 8, 4); \
        break; \
    } \
  else \
    switch (size % 3) { \
      case 1: \
        outP[0] = _UNFILL_10_OP(*p, 16, 6, 24, 2); \
        break; \
      case 2: \
        outP[0] = _UNFILL_10_OP(*p, 16, 6, 24, 2); \
        outP[1] = _UNFILL_10_OP(*p, 8, 4, 16, 4); \
        break; \
    }*/

#define _UNFILL_12(TYPE) \
  TYPE * outP = reinterpret_cast<TYPE *>(out); \
  const TYPE * lutP = reinterpret_cast<const TYPE *>(lut); \
  if (endian) \
    for (; p < end; ++p) \
      *outP++ = lutP[(((*p >> 8) << 12) | (*p << 4)) & mask]; \
  else \
    for (; p < end; ++p) \
      *outP++ = lutP[((*p << 12) | ((*p >> 8) << 4)) & mask];

void dpxScanlineUnfill(
  const uint8_t * in, uint8_t * out, const uint8_t * lut,
  int size, PIXEL_TYPE type, int bitDepth, bool endian
) {
  DJ_DBG_FNC("dj::ImageIoDpx::dpxScanlineUnfill");
  DJ_DBG_PRN("size = " << size);
  DJ_DBG_PRN("type = " << type);
  DJ_DBG_PRN("bit depth = " << bitDepth);
  DJ_DBG_PRN("endian = " << endian);

  const int mask = (1 << bitDepth) - 1;
  switch (bitDepth) {
  
    case 10: {
    
      /*const uint32_t * p = reinterpret_cast<const uint32_t *>(in);
      const uint32_t * const end = p + size / 3;
      switch (type) {
        case PIXEL_U8: { _UNFILL_10(PixelU8) } break;
        case PIXEL_U16: { _UNFILL_10(PixelU16) } break;
        case PIXEL_F32: { _UNFILL_10(PixelF32) } break;
      }*/
      
      // XXX Fast, but portable?
      //
      if (MSB == dj::endian()) {
        switch (type) {
          case PIXEL_U8: { _UNFILL_10(PixelU8, _UNFILL_10_MSB) } break;
          case PIXEL_U16: { _UNFILL_10(PixelU16, _UNFILL_10_MSB) } break;
          case PIXEL_F32: { _UNFILL_10(PixelF32, _UNFILL_10_MSB) } break;
        }
      } else {
        switch (type) {
          case PIXEL_U8: { _UNFILL_10(PixelU8, _UNFILL_10_LSB) } break;
          case PIXEL_U16: { _UNFILL_10(PixelU16, _UNFILL_10_LSB) } break;
          case PIXEL_F32: { _UNFILL_10(PixelF32, _UNFILL_10_LSB) } break;
        }
      }
      
    } break;
    
    case 12: {
      const uint16_t * p = reinterpret_cast<const uint16_t *>(in);
      const uint16_t * const end = p + size;
      switch (type) {
        case PIXEL_U8: { _UNFILL_12(PixelU8) } break;
        case PIXEL_U16: { _UNFILL_12(PixelU16) } break;
        case PIXEL_F32: { _UNFILL_12(PixelF32) } break;
      }
    } break;
  }
}

}
}
