//
// Stretch example
// Copyright (c) Robbin Bonthond (elemental@chaos.concepts.nl)
// This source code is licensed under the GNU GPL
//

#include <stdlib.h>
#include <fstream.h>
#include "ptc.h"

//
//  Memory handler
//

void * operator new( size_t size )
{
  void *ptr = malloc( size );
  if( ptr == NULL )
    throw Error( "out of memory" );
  return( ptr );
}

void operator delete( void *ptr )
{
  free( ptr );
}

//
//  Tga image loader
//

class Image
{
  public:
    Image( char *filename );
    ~Image();
    tga( char *filename );

    void copy( Surface &surface );
    void copy( Surface &surface, const Area &src, const Area &dst );

    int width() const;
    int height() const;

    const Format& format() const;
    const Palette& palette() const;

    void* lock();
    void unlock();

  private:
    bool m_locked;
    int m_width;
    int m_height;
    void *m_pixels;
    Format m_format;
    Palette m_palette;
};

Image::Image( char *filename )
{
  m_locked = false;

  // recognize file format and load it
  tga( filename );
}

Image::~Image()
{
  if( m_locked ) unlock();
  delete [] m_pixels;
}

void Image::copy( Surface &surface )
{
  surface.load( m_pixels, m_width, m_height, m_format, m_palette );
}

void Image::copy( Surface &surface, const Area &src, const Area &dst )
{
  surface.load( m_pixels, m_width, m_height, src, dst, m_format, m_palette );
}

int Image::width() const
{
  return m_width;
}

int Image::height() const
{
  return m_height;
}

const Format& Image::format() const
{
  return m_format;
}

const Palette& Image::palette() const
{
  return m_palette;
}

void* Image::lock()
{
  if( m_locked ) throw Error("Image is already locked");
  m_locked = true;
  return m_pixels;
}

void Image::unlock()
{
  if( !m_locked ) throw Error("Image is already unlocked");
  m_locked = false;
}

Image::tga( char *filename )
{
  // open image file
  ifstream is( filename, ios::binary );
  if( !is )
    throw Error( "cannot read file" );

  // read header
  char8 *header = new char8 [18];
  is.seekg(0);
  is.read( header, 18 );
  m_width = (header[13]<<8)+header[12];
  m_height = (header[15]<<8)+header[14];

  // setup format
  switch( header[16] ) {
    case  8: m_format = Format( 8 ); break;
    case 16: m_format = Format( 16, 0x7C00, 0x03E0, 0x001F ); break;
    case 24: m_format = Format( 24, 0x00FF0000, 0x0000FF00, 0x000000FF ); break;
    case 32: m_format = Format( 32, 0x00FF0000, 0x0000FF00, 0x000000FF ); break;
    default: throw Error( "unrecognized tga format" );
  }

  // read palette
  m_palette = Palette();
  int p_flag = header[1];
  unsigned p_offset = (header[4]<<8)+header[3];
  unsigned p_length = (header[6]<<8)+header[5];
  unsigned p_bits = header[7];
  unsigned p_size;

  if( p_flag && p_length ) {

    // reject anything > 256 palette entries
    if( p_length > 256 )
      throw Error("tga has wrong palette");

    // setup temp palette buffer
    p_size = p_length * p_bits / 8;
    char8 *temp = new char8 [p_size];
    int32 *data = new int32 [p_length];

    // read into temp buffer
    is.seekg( 18+p_offset );
    is.read( temp, p_size );

    // initialize palette format
    int i;
    switch( p_bits ) {
      case 16:
        for( i=0; i < p_length; i++ ) {
          int32 r = (temp[i*2+1]&0x7c)<<3;
          int32 g = (((temp[i*2+1]&3)<<3)+(temp[i*2]&0xe0)>>5)<<3;
          int32 b = (temp[i*2]&0x1f)<<3;
          data[i] = (r<<16)|(g<<8)|b;
        }
        break;
      case 24:
        for( i=0; i < p_length; i++ ) {
          int32 r = temp[i*3+2]&0xff;
          int32 g = temp[i*3+1]&0xff;
          int32 b = temp[i*3+0]&0xff;
          data[i] = (r<<16)|(g<<8)|b;
        }
        break;
      case 32:
        for( i=0; i < p_length; i++ ) {
          int32 r = temp[i*4+3]&0xff;
          int32 g = temp[i*4+2]&0xff;
          int32 b = temp[i*4+1]&0xff;
          data[i] = (r<<16)|(g<<8)|b;
        }
        break;
    }

    // load palette
    m_palette.load( data );

    // remove temp data
    delete [] temp;
    delete [] data;
  }

  // calculate size of pixels;
  int32 size = m_width * m_height * m_format.bytes();
  int pitch = m_width * m_format.bytes();

  // allocate image pixels
  m_pixels = new char8 [size];

  // read image pixels one line at a time (upside down)
  if( p_flag )
    is.seekg( 18+p_offset+p_size );
  else
    is.seekg( 18+header[0] );

  for( int y=m_height-1; y>=0; y-- )
    is.read( (char*)m_pixels+y*pitch, pitch );

  // free data
  delete [] header;
}

//
// usefull things
//

// random number
inline int random(int max)
{
  return rand() % max;
}

// random float
inline float nrandom()
{
  return rand() / (float)RAND_MAX;
}

void main()
{
  try {
    // create console
    Console console;
    Format format( 32, 0x00FF0000, 0x0000FF00, 0x000000FF );
    console.open( "Stretch example", 640, 480, format);

    // create surface matching console dimensions
    Surface surface( console.width(), console.height(), format );

    // load image to surface
    Image *loader1 = new Image( "image.tga" );
    Image *loader2 = new Image( "bghigh1a.tga" );
    Surface image1( loader1->width(), loader1->height(), format );
    Surface image2( loader2->width(), loader2->height(), format );
    loader1->copy( image1 );
    loader2->copy( image2 );
    delete loader1, loader2;

    // get image dimensions
    Area src_area1( 0, 0, image1.width(), image1.height() );
    Area src_area2( 0, 0, image2.width(), image2.height() );

    // get surface dimensions
    int width  = surface.width();
    int height = surface.height();

    // loop until a key is pressed
    while( !console.key() ) {

      // stretch image area to surface area
      Area dst_area1( random(width), random(height), random(width), random(height) );
      Area dst_area2( random(width), random(height), random(width), random(height) );
      image1.copy( surface, src_area1, dst_area1 );
      image2.copy( surface, src_area2, dst_area2 );
            
      // copy to and update console
      surface.copy( console );
      console.update();
    }

    while( console.key() )
      console.read();

  }

  // report error
  catch( Error &error ) {
    error.report();
  }
}


