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

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

namespace dj {
namespace ImageIoTga {

bool Load::open(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("dj::ImageIoTga::Load::open");
  DJ_DBG_PRN("file name = " << fileName);
  
  _fileInfo.set(fileName);
  
  if (
    ! imageOpen(
      _fileInfo.get(_fileInfo.isSeq() ? _fileInfo.start() : -1), info
    )
  ) return false;
  
  if (_fileInfo.isSeq()) info->time(_fileInfo.seq());
  
  return true;
}

bool Load::imageOpen(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("dj::ImageIoTga::Load::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);
  
  // Open file.
  //
  _f.endian(endian() != LSB);
  if (! _f.open(fileName, FileIo::READ)) return false;
  
  // Read header.
  //
  if (! _f.getU8(&_header.idSize)) return false;
  if (! _f.getU8(&_header.cmapType)) return false;
  if (! _f.getU8(&_header.imageType)) return false;
  if (! _f.getU16(&_header.cmapStart)) return false;
  if (! _f.getU16(&_header.cmapSize)) return false;
  if (! _f.getU8(&_header.cmapDepth)) return false;
  if (! _f.getU16(&_header.x)) return false;
  if (! _f.getU16(&_header.y)) return false;
  if (! _f.getU16(&_header.width)) return false;
  if (! _f.getU16(&_header.height)) return false;
  if (! _f.getU8(&_header.pixelDepth)) return false;
  if (! _f.getU8(&_header.descriptor)) return false;
  if (! _f.seek(_header.idSize, FileIo::CURRENT)) return false;
  //dbg(_header);
  
  // Setup information.
  //
  info->size(Vec2i(_header.width, _header.height));
  
  int format = 0;
  switch (_header.imageType) {
    case 3:
    case 11: format = PIXEL_L; break;
    case 2:
    case 10: format = PIXEL_RGB; break;
    default: return false;
  }
  const int alphaBits = _header.descriptor & 15;
  switch (alphaBits) {
    case 0: break;
    case 8: ++format; break;
    default: return false;
  }
  _format = PIXEL_FORMAT(format);
  switch (_header.pixelDepth / pixelSize(_format)) {
    case 8: break;
    default: return false;
  }
  info->pixel(Pixel(_format, PIXEL_U8));
  
  return true;
}

bool Load::imageBegin(int frame, ImageIoInfo * info)
{
  DJ_DBG_FNC("dj::ImageIoTga::Load::imageBegin");
  DJ_DBG_PRN("frame = " << frame);
  
  if (
    ! imageOpen(_fileInfo.get(frame == -1 ? _fileInfo.start() : frame), info)
  ) return false;
  
  return true;
}

static bool _scanlineRle(FileIo * f, int w, int channels, uint8_t * out)
{
  PixelU8 * p = reinterpret_cast<PixelU8 *>(out);
  int count = 0;
  int i = 0;
  for (; i < w; i += count, p += count * channels) {
  
    uint8_t tmp;
    if (! f->getU8(&tmp)) break;
    
    count = (tmp & 127) + 1;
    //DJ_DBG_PRN("count = " << count);
    if (! count || count > (w - i)) break;
    
    const bool run = tmp >> 7;
    //DJ_DBG_PRN("run = " << run);
    if (! f->get(p, (run ? 1 : count) * channels)) break;
    if (run) {
      PixelU8 * runP = p + channels;
      for (int j = 1; j < count; ++j, runP += channels)
        for (int c = 0; c < channels; ++c)
          runP[c] = p[c];
    }
  }
  return w == i;
}

bool Load::imageGet(PixelData * out, int proxyScale)
{
  DJ_DBG_FNC("dj::ImageIoTga::Load::imageGet");
  DJ_DBG_PRN("out = " << *out);
  DJ_DBG_PRN("proxy = " << proxyScale);

  bool flipX = false, flipY = false;
  switch ((_header.descriptor >> 4) & 3) {
    case 1: flipX = true; break;
    case 2: flipY = true; break;
    case 3: flipX = flipY = true; break;
  }
  const int channels = pixelSize(_format);
  const bool compress = 10 == _header.imageType || 11 == _header.imageType;

  // Read.
  //
  PixelData scanline(Vec2i(_header.width, 1), Pixel(_format, PIXEL_U8));
  int y = 0, fileY = 0;
  for (; y < out->height(); ++y, fileY += proxyScale) {
    //DJ_DBG_PRN("y = " << y << " (" << fileY << ")");
    
    if (! compress) {
      if (! _f.get(scanline.data(), _header.width * channels)) break;
    } else {
      if (! _scanlineRle(&_f, _header.width, channels, scanline.data())) break;
    }

    // Convert.
    //
    if (PIXEL_RGB == _format || PIXEL_RGBA == _format) {
      PixelU8 * p = reinterpret_cast<PixelU8 *>(scanline.data());
      PixelU8 tmp;
      for (int i = 0; i < _header.width; ++i, p += channels) {
        tmp = p[0];
        p[0] = p[2];
        p[2] = tmp;
      }
    }
    pixelCopy(
      scanline.data(), out->data(0, flipY ? (out->height() - 1 - y) : y),
      out->width(), scanline.pixel(), out->pixel(),
      proxyScale, flipX
    );
    
    // Skip scanlines.
    //
    const int skip = min(proxyScale - 1, _header.height - (fileY + 1));
    if (skip) {
      if (! compress) {
        if (! _f.seek(skip * _header.width * channels, FileIo::CURRENT))
          break;
      } else {
        int i = 0;
        for (i = 0; i < skip; ++i)
          if (! _scanlineRle(&_f, _header.width, channels, scanline.data()))
            break;
        if (i < skip) break;
      }
    }
  }
  
  _f.close();
  return out->height() == y;
}

}
}
