#include "filter.h"

#ifdef __Ansi__
  #include "sys_ansi.h"
#elif defined (__Win__)
  #include "sys_win.h"
#endif

#include "metadata.h"
#include "file.h"

static int readBMPFileHeader(Image *im, file_spec input, int *reverse);


#pragma pack(push, 1) 
// Bitmap file header
typedef struct tagwin3xHead{
	short ImageFileType;						// Always 4D42h ("BM")
	long  FileSize;								// Physical size of file in bytes
	short Reserved1;							// Always 0
	short Reserved2;							// Always 0
	long  ImageDataOffset;						// Start of image data offset in bytes
} win3xHead;

typedef struct tagwin3xBitmapInfoHeader{
	long  HeaderSize;							// Size of this header
	long  ImageWidth;							// Image width in pixels
	long  ImageHeight;							// Image height in pixels
	short NumberOfImagePlanes;					// Number of planes (Always 1)
	short BitsPerPixel;							// Bits per pixel (1,4, 8 or 24)
	long  CompressionMethod;					// Compression method (0, 1 or 2)

	long  SizeOfBitmap;							// Size of bitmap in bytes
	long  HorzResolution;						// Horizontal resolution in pixels / meter
	long  VertResolution;						// Vertical resolution in pixels / meter
	long  NumColorsUsed;						// Number of colors in image
	long  NumSignificantColors;					// Number of important colors in palette
} win3xBitmapInfoHeader;
#pragma pack(pop)



int writeBMP( Image *im, fullPath *sfile )
{
	file_spec  output;
	unsigned char *buf, *data;
	long count;
	int scanLength;
	int y;
	win3xHead head;							// First part of bitmap header
	win3xBitmapInfoHeader ihead;			// Second part of bitmap header

	//In BMP files, each line MUST have n bytes, such that n is divisible by 4.
	//If the image doesn't have n divisible by 4, it will contain non-valid
	//information. For example if an image has 1 pixel per line in 24-bit color
	//depth, each line will have 1 additional byte that must be ignored when
	//reading the bitmap.

	//scanlength must be divisble by four (adding 3, dividing by 4 and then multiplying does this).
	scanLength = ((im->width * 3) + 3)/4;
	scanLength *= 4;

	// Build header structures

	head.ImageFileType=0x4D42;				// Always 4D42h ("BM")
	head.FileSize = scanLength * im->height+54;		// Physical size of file in bytes
	head.Reserved1=0;						// Always 0
	head.Reserved2=0;						// Always 0
	head.ImageDataOffset= 54;				// Start of image data offset in bytes

	ihead.HeaderSize=40;					// Size of this header
	ihead.ImageWidth=im->width;				// Image width in pixels
	ihead.ImageHeight=im->height;			// Image height in pixels
	ihead.NumberOfImagePlanes=1;			// Number of planes (Always 1)
	ihead.BitsPerPixel=24;					// Bits per pixel (24 bit color)
	ihead.CompressionMethod=0;				// Compression method (0=uncompressed)

	ihead.SizeOfBitmap = head.FileSize-54;	// Size of bitmap in bytes
	ihead.HorzResolution=7085;				// Horizontal resolution in pixels / meter
	ihead.VertResolution=7085;				// Vertical resolution in pixels / meter
	ihead.NumColorsUsed =0;					// Number of colors in image
	ihead.NumSignificantColors=0;			// Number of important colors in palette

	if( myopen( sfile, write_bin, output )	)
	{
		PrintError("writeBMP, could not open file"); 
		return -1;
	}
	
	count = sizeof( head );
	mywrite( output, count, &head );
	if( count != sizeof( head ) )
	{
		PrintError("writeBMP, could not write header"); 
		return -1;
	}
	

	count = sizeof( ihead );
	mywrite( output, count, &ihead );
	if( count != sizeof( ihead ) )
	{
		PrintError("writeBMP, could not write header"); 
		return -1;
	}

	buf = (unsigned char*)malloc( im->bytesPerLine + 1); // Worst case
	if( buf == NULL )
	{
		PrintError( "Not enough memory" );
		return -1;
	}

	// Write image in reverse order, also change rgb->bgr	
	
	data = *(im->data) + ((im->height-1) * im->bytesPerLine);
	for(y=0; y<im->height; y++)
	{
		if( im->bitsPerPixel == 32 )	// Convert 4->3 samples
		{
			int x;
			unsigned char *c1=buf, *c2=data;
			for(x=0; x < im->width; x++)
			{
				*c1++ = c2[3];
				*c1++ = c2[2];
				*c1++ = c2[1];
				c2 += 4;
			}
		}
		else
		{
			int x;
			unsigned char *c1=buf, *c2=data;
			for(x=0; x < im->width; x++)
			{
				*c1++ = c2[2];
				*c1++ = c2[1];
				*c1++ = c2[0];
				c2 += 3;
			}
		}
		


		count = scanLength;
		mywrite( output, count, buf );
		if( count != scanLength )
		{
			PrintError("writeBMP, could not write image data");
			free( buf );
			return -1;
		}
		data -= im->bytesPerLine;
	}
	myclose( output );
	free( buf );
	
	return 0;
}


// Read bitmap file
static int readBMP( Image *im, fullPath *sfile )
{
	file_spec input;
	unsigned char *data, *buf;
	int y;
	int scanLength;
	long count;
	int reverse = 0;
	
	// Bitmap file open
	if( myopen( sfile, read_bin, input )	)
	{
		PrintError("readBMP, could not open file"); 
		return -1;
	}


	// Read bitmap file header

	if( readBMPFileHeader(im, input, &reverse)  )
	{
		PrintError("readBMP, error reading bitmap file header");
		return -1;
	}

	scanLength = (im->width * 3 + 1)/2;
	scanLength *= 2;

	
	im->data = (unsigned char**) mymalloc( im->dataSize );
	if( im->data == NULL )
	{
		PrintError("Not enough memory");
		return -1;
	}

	buf = (unsigned char*)malloc( im->bytesPerLine + 1); // Worst case
	if( buf == NULL )
	{
		PrintError( "Not enough memory" );
		return -1;
	}
	

	if( 0 == reverse)
		data = *(im->data) + ((im->height-1) * im->bytesPerLine);
	else // 1 == reverse
		data = *(im->data);

	for(y=0; y<im->height; y++)
	{
		count = scanLength;
		myread(  input, count, buf );
		if( count != scanLength )
		{
			PrintError("Error reading image data");
			return -1;
		}
		// Convert 3->4 samples; bgr->rgb
		{
			int x;
			unsigned char *c1=buf, *c2=data;
			for(x=0; x < im->width; x++)
			{
				*c2++ = UCHAR_MAX;
				*c2++ = c1[2];
				*c2++ = c1[1];
				*c2++ = c1[0];
				c1+=3;
			}
		}
		if( 0 == reverse)
			data -= im->bytesPerLine;
		else //1 == reverse
			data += im->bytesPerLine;
	}
	
	myclose(input);
	free( buf );
	return 0;
}


// Read bitmap file header
static int readBMPFileHeader(Image *im, file_spec input, int *reverse)
{
	win3xHead header;						// First part of bitmap header
	win3xBitmapInfoHeader iheader;			// Second part of bitmap header
	long count;
	
	count = sizeof(header);
	myread(  input, count, &header );
	if( count != sizeof(header) )
	{
		PrintError("Error reading first BMP header segment");
		return -1;
	}

	if(header.ImageFileType != 0x4D42)
	{
		PrintError("readBMPFileHeader, BMP bad magic No");
		return -1;
	}


	count = sizeof(iheader);
	myread(  input, count, &iheader );
	if( count != sizeof(iheader) )
	{
		PrintError("Error reading second BMP header segment");
		return -1;
	}

	if(iheader.HeaderSize != 40)
	{			// Size of this header
		PrintError("readBMPFileHeader, secondary header length wrong -- Not MS version 3 compatible");
		return -1;
	}

	if(iheader.NumberOfImagePlanes != 1){	// Number of planes (Always 1)
		PrintError("readBMPFileHeader, should be 1 image plane");
		return -1;
	}

	if(iheader.BitsPerPixel != 24){			// Bits per pixel (1,4, 8 or 24)
		PrintError("readBMPFileHeader, only 24 bit color supported");
		return -1;
	}
	
	if(iheader.CompressionMethod != 0)
	{		// Compression method (0, 1 or 2)
		PrintError("readBMPFileHeader, only uncompressed BMP supported");
		return -1;
	}
	
	SetImageDefaults( im );
	
	// Setup height and width
	if(iheader.ImageHeight > 0) {
	    im->height = iheader.ImageHeight;
	    *reverse=0 ;
	} else {
	    im->height = -iheader.ImageHeight;
	    *reverse=1 ;
	}
	im->width  = iheader.ImageWidth;
	
	im->bitsPerPixel = 32;
	im->bytesPerLine = im->width * 4;
	// JMW Negative height BMP have rows bottom to top
	im->dataSize = im->bytesPerLine * im->height;
	
	// Advance file pointer to start of image data
	fseek( input, header.ImageDataOffset, SEEK_SET );
	
	return 0;							// Header ok -- no error
}


int panoBMPRead( Image *im, fullPath *sfile )
{
  if (readBMP(im, sfile) == 0) {
    return panoMetadataUpdateFromImage(im);
  } 
  else 
    return FALSE;
}


