/** \file
 * \brief RAS - Sun Raster File
 *
 * See Copyright Notice in im_lib.h
 */

#include "im_format.h"
#include "im_format_all.h"
#include "im_util.h"
#include "im_counter.h"

#include "im_binfile.h"

#include <stdlib.h>
#include <string.h>
#include <memory.h>

/*  File Header Structure. */
/*  4  Magic;      magic number */
/*  4  BufferSize;      width (pixels) of image */
/*  4  Height;     height (pixels) of image */
/*  4  Depth;      depth (1, 8, 24, or 32) of pixel */
/*  4  Length;     length (bytes) of image */
/*  4  Type;       type of file; see RT_OLD below */
/*  4  MapType;    type of colormap; see RAS_NONE below */
/*  4  MapLength;  length (bytes) of following map */
/*  32  */

#define  RAS_ID  0x59A66A95

/* Sun supported ras_type's */
#define RAS_OLD    0  /* Raw pixrect image in 68000 byte order */
#define RAS_STANDARD  1  /* Raw pixrect image in 68000 byte order */
#define RAS_BYTE_ENCODED  2  /* Run-length compression of bytes */
#define RAS_EXPERIMENTAL 0xffff  /* Reserved for testing */

#define RAS_ESCAPE  0x80

/* Sun supported ras_maptype's */
#define RAS_NONE  0      /* ras_maplength is expected to be 0 */
#define RAS_EQUAL_RGB  1  /* red[ras_maplength/3],green[],blue[] */
#define RAS_RAW    2     /* Sun registered ras_maptype's */


/* NOTES:
 *   Each line of the image is rounded out to a multiple of 16 bits.
 *   This corresponds to the rounding convention used by the memory pixrect
 *   package (/usr/include/pixrect/memvar.h) of the SunWindows system.
 *  The ras_encoding field (always set to 0 by Sun's supported software)
 *   was renamed to ras_length in release 2.0.  As a result, rasterfiles
 *   of type 0 generated by the old software claim to have 0 length; for
 *   compatibility, code reading rasterfiles must be prepared to compute the
 *   true length from the width, height, and depth fields. */

static int iRASDecodeScanLine(imBinFile* handle, unsigned char* DecodedBuffer, int BufferSize)
{
  int index = 0;
  unsigned char count = 0;
  unsigned char value = 0;

  while (index < BufferSize)
  {
    imBinFileRead(handle, &value, 1, 1);

    if (value == RAS_ESCAPE)
    {
      imBinFileRead(handle, &count, 1, 1);

      if (count != 0)
      {
        imBinFileRead(handle, &value, 1, 1);

        count++;
        while (count-- && index < BufferSize)
        {
          *DecodedBuffer++ = value;
          index++;
        }
      }
      else
      {
        *DecodedBuffer++ = RAS_ESCAPE;
        index++;
      }
    }
    else
    {
      *DecodedBuffer++ = value;
      index++;                         
    }

    if (imBinFileError(handle))
      return IM_ERR_ACCESS;
  }

  return IM_ERR_NONE;
}

static int iRASEncodeScanLine(imbyte* EncodedBuffer, const imbyte* DecodedBuffer, int BufferSize)
{
  int index = 0;        /* Index into uncompressed data buffer  */
  int scanindex = 0;    /* Index into compressed data buffer    */
  int runcount;                  /* Length of encoded pixel run          */
  unsigned char runvalue;                  /* Value of encoded pixel run           */

  while (index < BufferSize)
  {
    for (runcount = 1, runvalue = DecodedBuffer[index]; 
         index + runcount < BufferSize && runvalue == DecodedBuffer[index + runcount] && runcount < 256; 
         runcount++);

    if (runcount > 2)                   /* Multiple pixel run */
    {
      EncodedBuffer[scanindex++] = RAS_ESCAPE;
      EncodedBuffer[scanindex++] = (imbyte)(runcount-1);
      EncodedBuffer[scanindex++] = runvalue;
    }
    else if (runcount == 2)
    {
      if (runvalue == RAS_ESCAPE)      /* Two Escapes */
      {
        EncodedBuffer[scanindex++] = RAS_ESCAPE;
        EncodedBuffer[scanindex++] = 1;            
        EncodedBuffer[scanindex++] = RAS_ESCAPE;
      }
      else                             /* Two Single runs */
      {
        EncodedBuffer[scanindex++] = runvalue;
        EncodedBuffer[scanindex++] = runvalue;
      }
    }
    else                               /* Single run   */
    {
      if (runvalue == RAS_ESCAPE)
      {
        EncodedBuffer[scanindex++] = RAS_ESCAPE;
        EncodedBuffer[scanindex++] = 0;
      }
      else
        EncodedBuffer[scanindex++] = runvalue;
    }

    index += runcount;  /* Jump ahead to next pixel run value */
  }

  return scanindex;      /* Return the number of unsigned chars written to buffer */
}

static const char* iRASCompTable[2] = 
{
  "NONE",
  "RLE"
};

class imFileFormatRAS: public imFileFormatBase
{
  imBinFile* handle;          /* the binary file handle */
  unsigned int bpp,          /* number of bits per pixel */
               comp_type,    /* ras compression information */
               map_type,     /* palette information */
               line_raw_size;    /* line buffer size */

  int ReadPalette();
  int WritePalette();
  void FixRGB();

public:
  imFileFormatRAS(const imFormat* _iformat): imFileFormatBase(_iformat) {}
  ~imFileFormatRAS() {}

  int Open(const char* file_name);
  int New(const char* file_name);
  void Close();
  void* Handle(int index);
  int ReadImageInfo(int index);
  int ReadImageData(void* data);
  int WriteImageInfo();
  int WriteImageData(void* data);
};

class imFormatRAS: public imFormat
{
public:
  imFormatRAS()
    :imFormat("RAS", 
              "Sun Raster File", 
              "*.ras;", 
              iRASCompTable, 
              2, 
              0)
    {}
  ~imFormatRAS() {}

  imFileFormatBase* Create(void) const { return new imFileFormatRAS(this); }
  int CanWrite(const char* compression, int color_mode, int data_type) const;
};


void imFormatRegisterRAS(void)
{
  imFormatRegister(new imFormatRAS());
}

int imFileFormatRAS::Open(const char* file_name)
{
  unsigned int dword_value;

  /* opens the binary file for reading with motorola byte order */
  handle = imBinFileOpen(file_name);
  if (!handle)
    return IM_ERR_OPEN;

  imBinFileByteOrder(handle, IM_BIGENDIAN); 

  /* reads the RAS format identifier */
  imBinFileRead(handle, &dword_value, 1, 4);
  if (imBinFileError(handle))
  {
    imBinFileClose(handle);
    return IM_ERR_ACCESS;
  }

  if (dword_value != RAS_ID)
  {
    imBinFileClose(handle);
    return IM_ERR_FORMAT;
  }

  /* reads the compression information */
  imBinFileSeekOffset(handle, 16);

  imBinFileRead(handle, &this->comp_type, 1, 4);
  if (this->comp_type == RAS_BYTE_ENCODED)
    strcpy(this->compression, "RLE");
  else if (this->comp_type == RAS_OLD || this->comp_type == RAS_STANDARD)
    strcpy(this->compression, "NONE");
  else
  {
    imBinFileClose(handle);
    return IM_ERR_COMPRESS;
  }

  imBinFileSeekOffset(handle, -20);

  this->image_count = 1;

  return IM_ERR_NONE;
}

int imFileFormatRAS::New(const char* file_name)
{
  /* opens the binary file for writing with motorola byte order */
  handle = imBinFileNew(file_name);
  if (!handle)
    return IM_ERR_OPEN;

  imBinFileByteOrder(handle, IM_BIGENDIAN); 

  this->image_count = 1;

  return IM_ERR_NONE;
}

void imFileFormatRAS::Close()
{
  imBinFileClose(handle);
}

void* imFileFormatRAS::Handle(int index)
{
  if (index == 0)
    return (void*)this->handle;
  else
    return NULL;
}

int imFileFormatRAS::ReadImageInfo(int index)
{
  (void)index;
  unsigned int dword_value;

  this->file_data_type = IM_BYTE;

  /* reads the image width */
  imBinFileRead(handle, &dword_value, 1, 4);
  this->width = (int)dword_value;

  /* reads the image height */
  imBinFileRead(handle, &dword_value, 1, 4);
  this->height = (int)dword_value;

  /* reads the number of bits per pixel */
  imBinFileRead(handle, &this->bpp, 1, 4);

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  // sanity check
  if (this->bpp != 1 && this->bpp != 8 && 
      this->bpp != 24 && this->bpp != 32)
    return IM_ERR_DATA;

  if (this->bpp > 8)
  {
    this->file_color_mode = IM_RGB;
    this->file_color_mode |= IM_PACKED;

    if (this->bpp == 32)
      this->file_color_mode |= IM_ALPHA;
  }
  else
  {
    this->file_color_mode = IM_MAP;

    if (this->bpp == 1)
    {
      this->convert_bpp = 1;
      this->palette_count = 2;
    }
  }

  this->file_color_mode |= IM_TOPDOWN;

  this->line_raw_size = imFileLineSizeAligned(this->width, this->bpp, 2);
  this->line_buffer_extra = 2; // room enough for padding

  /* jump 8 bytes (Length+Compression) */
  imBinFileSeekOffset(handle, 8);

  /* reads the palette information */
  imBinFileRead(handle, &this->map_type, 1, 4);

  /* reads the palette size */
  imBinFileRead(handle, &dword_value, 1, 4);

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  /* updates the pal_size based on the palette size */
  if (this->bpp <= 8 && this->map_type != RAS_NONE)
  {
    this->palette_count = dword_value / 3;
    return ReadPalette();
  }

  if (this->bpp <= 8 && this->map_type == RAS_NONE)
  {
    if (this->bpp == 1)
      this->file_color_mode = IM_BINARY;
    else
      this->file_color_mode = IM_GRAY;

    this->file_color_mode |= IM_TOPDOWN;
  }

  return IM_ERR_NONE;
}

int imFileFormatRAS::WriteImageInfo()
{
  this->file_data_type = IM_BYTE;
  this->file_color_mode = imColorModeSpace(this->user_color_mode);

  if (imStrEqual(this->compression, "RLE"))
    this->comp_type = RAS_BYTE_ENCODED;
  else
    this->comp_type = RAS_STANDARD;

  // Force the palette, even for Binary and Gray.
  this->map_type = RAS_EQUAL_RGB;

  if (this->file_color_mode == IM_BINARY)
  {
    this->bpp = 1;
    this->convert_bpp = 1;
  }
  else if (this->file_color_mode == IM_RGB)
  {
    this->file_color_mode |= IM_PACKED;
    this->map_type = RAS_NONE;
    this->bpp = 24;

    if (imColorModeHasAlpha(this->user_color_mode))
    {
      this->file_color_mode |= IM_ALPHA;
      this->bpp = 32;
    }
  }
  else
    this->bpp = 8;

  this->file_color_mode |= IM_TOPDOWN;

  this->line_raw_size = imFileLineSizeAligned(this->width, this->bpp, 2);
  this->line_buffer_extra = 2; // room enough for padding

  if (this->comp_type == RAS_BYTE_ENCODED)
  {
    // allocates more than enough since compression algoritm can be ineficient
    this->line_buffer_extra += 2*this->line_raw_size;
  }

  /* writes the RAS file header */

  unsigned int dword_value = RAS_ID;
  imBinFileWrite(handle, &dword_value, 1, 4); /* identifier */
  dword_value = this->width;
  imBinFileWrite(handle, &dword_value, 1, 4); /* image width */
  dword_value = this->height;
  imBinFileWrite(handle, &dword_value, 1, 4); /* image height */
  dword_value = this->bpp;
  imBinFileWrite(handle, &dword_value, 1, 4); /* bits per pixel */
  dword_value = this->height * this->line_raw_size;
  imBinFileWrite(handle, &dword_value, 1, 4); /* image lenght */
  dword_value = this->comp_type;
  imBinFileWrite(handle, &dword_value, 1, 4); /* compression information */
  dword_value = this->map_type;
  imBinFileWrite(handle, &dword_value, 1, 4); /* palette information */
  dword_value = (this->map_type == RAS_NONE)? 0: this->palette_count * 3;
  imBinFileWrite(handle, &dword_value, 1, 4); /* palette lenght */

  /* tests if everything was ok */
  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  if (this->map_type != RAS_NONE)
    return WritePalette();

  return IM_ERR_NONE;
}

int imFileFormatRAS::ReadPalette()
{
  unsigned char ras_colors[256 * 3];

  /* reads the color palette */
  imBinFileRead(handle, ras_colors, this->palette_count * 3, 1);

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  /* convert the color map to the IM format */
  for (int c = 0; c < this->palette_count; c++)
  {
    if (this->map_type == RAS_RAW)
    {
      int i = c * 3;
      this->palette[c] = imColorEncode(ras_colors[i], 
                                       ras_colors[i+1], 
                                       ras_colors[i+2]);
    }
    else
    {
      this->palette[c] = imColorEncode(ras_colors[c], 
                                       ras_colors[c+this->palette_count], 
                                       ras_colors[c+2*this->palette_count]);
    }
  }

  return IM_ERR_NONE;
}

int imFileFormatRAS::WritePalette()
{
  int c;
  unsigned char ras_colors[256 * 3];

  /* convert the color map to the IM format */
  for (c = 0; c < this->palette_count; c++)
  {
    imColorDecode(&ras_colors[c], &ras_colors[c+this->palette_count], &ras_colors[c+2*this->palette_count], this->palette[c]);
  }

  /* writes the color palette */
  imBinFileWrite(handle, ras_colors, this->palette_count * 3, 1);

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  return IM_ERR_NONE;
}

void imFileFormatRAS::FixRGB()
{
  int x;
  imbyte* byte_data = (imbyte*)this->line_buffer;

  switch (this->bpp)
  {
  case 32:
    {
      // convert ABGR <-> RGBA
      for (x = 0; x < this->width; x++)
      {
        int c = x*4;
        imbyte temp = byte_data[c];     // swap R and A
        byte_data[c] = byte_data[c+3];
        byte_data[c+3] = temp;

        temp = byte_data[c+1];     // swap G and B
        byte_data[c+1] = byte_data[c+2];
        byte_data[c+2] = temp;
      }
    }
    break;
  default: // 24
    {
      // convert BGR <-> RGB
      for (x = 0; x < this->width; x++)
      {
        int c = x*3;
        imbyte temp = byte_data[c];     // swap R and B
        byte_data[c] = byte_data[c+2];
        byte_data[c+2] = temp;
      }
    }
    break;
  }
}

int imFileFormatRAS::ReadImageData(void* data)
{
  imCounterTotal(this->counter, this->height, "Reading RAS...");

  for (int lin = 0; lin < this->height; lin++)
  {
    /* read and decompress the data */
    if (this->comp_type != RAS_BYTE_ENCODED)
    {
      imBinFileRead(handle, this->line_buffer, this->line_raw_size, 1);

      if (imBinFileError(handle))
        return IM_ERR_ACCESS;     
    }
    else
    {                                               
      if (iRASDecodeScanLine(handle, (imbyte*)this->line_buffer, this->line_raw_size) == IM_ERR_ACCESS)
        return IM_ERR_ACCESS;     
    }

    if (this->bpp > 8)
      FixRGB();

    imFileLineBufferRead(this, data, lin, 0);

    if (!imCounterInc(this->counter))
      return IM_ERR_COUNTER;
  }

  return IM_ERR_NONE;
}

int imFileFormatRAS::WriteImageData(void* data)
{
  imCounterTotal(this->counter, this->height, "Writing RAS...");

  imbyte* compressed_buffer = NULL;
  if (this->comp_type == RAS_BYTE_ENCODED)  // point to the extra buffer
    compressed_buffer = (imbyte*)this->line_buffer + this->line_buffer_size+2;

  for (int lin = 0; lin < this->height; lin++)
  {
    imFileLineBufferWrite(this, data, lin, 0);

    if (this->bpp > 8)
      FixRGB();

    if (this->comp_type == RAS_BYTE_ENCODED)
    {
      int compressed_size = iRASEncodeScanLine(compressed_buffer, (imbyte*)this->line_buffer, this->line_raw_size);
      imBinFileWrite(handle, compressed_buffer, compressed_size, 1);
    }
    else
    {
      imBinFileWrite(handle, this->line_buffer, this->line_raw_size, 1);
    }

    if (imBinFileError(handle))
      return IM_ERR_ACCESS;     

    if (!imCounterInc(this->counter))
      return IM_ERR_COUNTER;
  }

  return IM_ERR_NONE;
}

int imFormatRAS::CanWrite(const char* compression, int color_mode, int data_type) const
{
  int color_space = imColorModeSpace(color_mode);

  if (color_space == IM_YCBCR || color_space == IM_LAB || 
      color_space == IM_LUV || color_space == IM_XYZ ||
      color_space == IM_CMYK)
    return IM_ERR_DATA;                       
                                              
  if (data_type != IM_BYTE)
    return IM_ERR_DATA;

  if (!compression || compression[0] == 0)
    return IM_ERR_NONE;

  if (!imStrEqual(compression, "NONE") && !imStrEqual(compression, "RLE"))
    return IM_ERR_COMPRESS;

  return IM_ERR_NONE;
}
