#ifndef _XYZ_BMP_INCLUDED
#define _XYZ_BMP_INCLUDED

struct _BMPHeader {
  int       fileSize;
  int       reserved;
  int       imageOffset;

  int       dibHeaderSize;
  int       width;
  int       height;
  short     numColorPlanes;
  short     bitsPerPixel;
  int       compression;
  int       imageSize;
  int       ppm_x; // x pixels per meter
  int       ppm_y; // x pixels per meter
  int       numColors;
  int       numImportantColors;
}; // 13*4 = 52 bytes

// returns 0 on success
static int
_BMPRead( const char *filename, int *ret_width, int *ret_height, 
              int *ret_depth, unsigned char **ret_image, 
              unsigned char **ret_palette )
{
  FILE *file;
  struct _BMPHeader header;
  unsigned char *p, *src, temp;
  int bytesPerLine, bytesPerLine_4, x, y;
  
  #ifdef _MSC_VER
  if( fopen_s( &file, filename, "rb" ) != 0 ) return( 1 );
  #else
  file = fopen( filename, "rb" );
  if( file == NULL ) return( 1 );
  #endif

  if( fgetc( file ) != (int)'B' ) return( 2 ); 
  if( fgetc( file ) != (int)'M' ) return( 2 ); 
  if( fread( &header, 1, 52, file ) != 52 ) return( 3 );
  
  if( header.dibHeaderSize != 40 ) return( 4 ); 
  *ret_width = header.width;
  *ret_height = header.height;
  // support only positive heights
  if( header.height < 0 ) return( 5 );

  // bits per pixel; only support 8 or 24
  *ret_depth = header.bitsPerPixel;
  if( header.bitsPerPixel != 8 && header.bitsPerPixel != 24 ) return( 6 );
  bytesPerLine_4 = bytesPerLine = (header.bitsPerPixel >> 3) * header.width;
  temp = bytesPerLine_4 & 0x3;
  if( temp ) bytesPerLine_4 += (4 - temp);

  if( header.imageSize != bytesPerLine_4 * header.height ) return( 8 );
  // number of colors in palette; only support default
  if( header.numColors != 0 ) return( 9 );
  // number of important colors in palette; only support default
  if( header.numImportantColors != 0 ) return( 10 );

  if( header.bitsPerPixel == 8 ) {
    // allocate and read in palette (256*rgbx = 256*4 = 1024 bytes)
    *ret_palette = p = (unsigned char *)malloc( 1024 );
    if( fread( p, 1, 1024, file ) != 1024 ) return( 11 );
    // swap the blue and red entries to make it rgb
    src = p;
    for( x = 0; x < 256; x++, p += 3, src += 4 ) {
      temp = src[ 0 ];
      p[ 0 ] = src[ 2 ];
      p[ 1 ] = src[ 1 ];
      p[ 2 ] = temp;
    }
  } else {
    *ret_palette = NULL;
  }

  // read in the image
  *ret_image = p = (unsigned char *)malloc( bytesPerLine * header.height );

  // read in lines (reversing their order)
  p += (header.height - 1)*bytesPerLine;
  for( y = 0; y < header.height; y++, p -= bytesPerLine ) {

    // read line + padding from the file
    if( fread( p, 1, bytesPerLine, file ) != (unsigned)bytesPerLine ) 
      return( 100 + y );
    // skip over row padding
    for( x = bytesPerLine; x < bytesPerLine_4; x++ ) fgetc( file ); 

    if( header.bitsPerPixel == 24 ) {
      // swap the blue and red entries to make it rgb
      for( x = 0; x < bytesPerLine; x += 3 ) {
        temp = p[ x + 0 ];
        p[ x + 0 ] = p[ x + 2 ];
        p[ x + 2 ] = temp;
      }
    }
  }

  fclose( file );

  return( 0 );
}

// bytesPerRGBRow refers to the scan width (in bytes) of 'rgb'
//   (keep in mind: 3 bytes per rgb pixel)
// x,y,width,height specify the clipped rectangle (in pixels) within 'rgb'
//   that will be written out to the BMP.
// returns 0 on success
static int
_BMPWriteRGB( const char *filename, unsigned char *rgb, int bytesPerRGBRow, 
  int x, int y, int width, int height ) 
{
  int i, j;
  int bytesPerLine, bytesPerLine_4;
  unsigned char *line, *dest, *src;
  FILE *file;
  struct _BMPHeader header;

  #ifdef _MSC_VER
  if( fopen_s( &file, filename, "wb" ) != 0 ) return( 1 );
  #else
  file = fopen( filename, "wb" );
  if( file == NULL ) return( 1 );
  #endif

  bytesPerLine = 3 * width;
  bytesPerLine_4 = bytesPerLine;
  // pad with extra bytes to make the bytesPerLine divisible by 4
  i = bytesPerLine & 0x3;
  if( i ) bytesPerLine_4 += (4-i);

  // BMP header (14 bytes)
  fputc( 'B', file ); // two magic bytes
  fputc( 'M', file );
  header.fileSize = 54 + bytesPerLine_4 * height;
  header.reserved = 0;
  header.imageOffset = 54; // offset where the pixel data starts

  // DIB header (40 bytes)
  header.dibHeaderSize = 40; // DIB header size: 40 => "Windows V3"
  header.width = width;
  header.height = height;
  header.numColorPlanes = 1; // always 1
  header.bitsPerPixel = 24; // (8 b, 8 g, 8 r)
  header.compression = 0; // none
  header.imageSize = bytesPerLine_4 * height;
  header.ppm_x = 0; // x pixels per m
  header.ppm_y = 0; // y pixels per m
  header.numColors = 0; // no palette when bitsPerPixel >= 16
  header.numImportantColors = 0;

  if( fwrite( &header, 1, 52, file ) != 52 ) return( 2 );

  line = (unsigned char *)calloc( bytesPerLine_4, 1 );

  // offset into rgb to get lower left corner of clipped rectangle
  src = rgb + (y + height - 1) * bytesPerRGBRow + x * 3;

  for( i = 0; i < height; i++ ) {
    dest = line;
    for( j = 0; j < width; j++ ) {
      dest[ 0 ] = src[ 2 ]; // blue
      dest[ 1 ] = src[ 1 ]; // green
      dest[ 2 ] = src[ 0 ]; // red
      dest += 3;
      src += 3;
    }
    // go back to beginning of row
    src -= 3 * width;
    // go up one line
    src -= bytesPerRGBRow;

    fwrite( line, bytesPerLine_4, 1, file );
  }

  free( line );
  fclose( file );

  return( 0 );  
}

// bytesPerSrcRow refers to the scan width (in bytes) of 'pixels' 
//   (keep in mind: 1 byte per pixel)
// x,y,width,height specify the clipped rectangle (in pixels), within 
//   'pixels', that will be written out to the BMP.
// returns 0 on success
static int
_BMPWriteGray( const char *filename, unsigned char *pixels, int bytesPerSrcRow, 
  int x, int y, int width, int height ) 
{
  int i, j;
  int bytesPerLine, bytesPerLine_4;
  unsigned char *src;
  FILE *file;
  struct _BMPHeader header;

  #ifdef _MSC_VER
  if( fopen_s( &file, filename, "wb" ) != 0 ) return( 1 );
  #else
  file = fopen( filename, "wb" );
  if( file == NULL ) return( 1 );
  #endif

  bytesPerLine = width;
  bytesPerLine_4 = bytesPerLine;
  // pad with extra bytes to make the bytesPerLine divisible by 4
  i = bytesPerLine & 0x3;
  if( i ) bytesPerLine_4 += (4-i);

  // BMP header (14 bytes)
  fputc( 'B', file ); // two magic bytes
  fputc( 'M', file );
  header.fileSize = 54 + 256*4 + bytesPerLine_4 * height;
  header.reserved = 0;
  header.imageOffset = 54 + 256*4; // where the pixel data starts

  // DIB header (40 bytes)
  header.dibHeaderSize = 40; // DIB header size: 40 => "Windows V3"
  header.width = width;
  header.height = height;
  header.numColorPlanes = 1; // always 1
  header.bitsPerPixel = 8; // (8 b, 8 g, 8 r)
  header.compression = 0; // none
  header.imageSize = bytesPerLine_4 * height;
  header.ppm_x = 0; // x pixels per m
  header.ppm_y = 0; // y pixels per m
  header.numColors = 0; // 0; default => size of palette = 2^n = 2^8 = 256 
  header.numImportantColors = 0; // 0; default => all palette entries are "important"

  if( fwrite( &header, 1, 52, file ) != 52 ) return( 2 );

  // grayscale palette
  for( i = 0; i < 256; i++ ) {
    fputc( i, file ); // blue
    fputc( i, file ); // green
    fputc( i, file ); // red
    fputc( 0, file ); // unused and useless
  }

  // offset into pixels to get lower left corner of clipped rectangle
  src = pixels + (y + height - 1)*bytesPerSrcRow + x;

  for( i = 0; i < height; i++ ) {
    fwrite( src, 1, bytesPerLine, file );
    for( j = bytesPerLine; j < bytesPerLine_4; j++ ) fputc( 0, file );
    // move up one row
    src -= bytesPerSrcRow;
  }

  fclose( file );

  return( 0 );  
}

// A text dump... nothing at all interesting or special, but damn if it isn't
// useful for debugging.
//
// returns 0 on success
static int
_ASCIIWriteGray( const char *filename, unsigned char *pixels, int bytesPerSrcRow, 
  int x, int y, int width, int height ) 
{
  int row, col;
  unsigned char *src;
  FILE *file;

  #ifdef _MSC_VER
  if( fopen_s( &file, filename, "wb" ) != 0 ) return( 1 );
  #else
  file = fopen( filename, "wb" );
  if( file == NULL ) return( 1 );
  #endif

  src = pixels + y * bytesPerSrcRow + x;
  fprintf( file, "x1     = %d\n", x );
  fprintf( file, "y1     = %d\n", y );
  fprintf( file, "x2     = %d\n", x + width - 1 );
  fprintf( file, "y2     = %d\n", y + height - 1 );
  fprintf( file, "width  = %d\n", width );
  fprintf( file, "height = %d\n", height );

  int xBannerFreq = 4;  // print x scale axis after this many row groups
  int yBannerFreq = 4; // print y scale axis after this many column groups
  const char *xSpacerShort = " "; // this is what's put between column groups
  const char *xSpacerLong  = "        ";
  int rowSpacerFreq = 4;
  int rowSpacerCount = 0;
  int colSpacerFreq = 4;
  int colSpacerCount = 0;

  for( row = 0; row < height; row++ ) {
    if( ( row % rowSpacerFreq ) == 0 ) {
      if( ( rowSpacerCount % xBannerFreq ) == 0 ) {
        // print x axis scale
        fprintf( file, "\n      " );
        colSpacerCount = 0;
        for( col = 0; col < width; col++ ) {
          if( ( col % colSpacerFreq ) == 0 ) {
            fprintf( file, "%-16d", col+x );
            colSpacerCount++;
            if( ( colSpacerCount % yBannerFreq ) == 0 )
              fprintf( file, "%s", xSpacerLong );
            else
              fprintf( file, "%s", xSpacerShort );
          }
        }
        fprintf( file, "\n      " );
        colSpacerCount = 0;
        for( col = 0; col < width; col++ ) {
          if( col && ( col % colSpacerFreq ) == 0 ) {
            colSpacerCount++;
            if( ( colSpacerCount % yBannerFreq ) == 0 )
              fprintf( file, "%s", xSpacerLong );
            else
              fprintf( file, "%s", xSpacerShort );
          }
          fprintf( file, "____" );
        }
      }
      fprintf( file, "\n" );
      rowSpacerCount++;
    }

    // print the pixel values
    fprintf( file, "%5d| ", row+y );
    colSpacerCount = 0;
    for( col = 0; col < width; col++, src++ ) {
      if( col && ( col % colSpacerFreq ) == 0 ) {
        colSpacerCount++;
        if( ( colSpacerCount % yBannerFreq ) == 0 )
          fprintf( file, " %5d| ", row+y );
        else
          fprintf( file, "%s", xSpacerShort );
      }
      fprintf( file, "%3d ", *src );
    }
    fprintf( file, "\n" );

    src += bytesPerSrcRow - width;
  }

  fclose( file );

  return( 0 );
}


#endif

