#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef DOS_MODE
#include <fcntl.h>
#include <io.h>
#endif

void dwordSwap( unsigned long * dword );
void wordSwap( unsigned short * word );

int isBigEndian( void );

typedef unsigned long       DWORD;
typedef unsigned short      WORD;
typedef long LONG;

typedef struct tagBITMAPFILEHEADER {
        DWORD   bfSize;
        WORD    bfReserved1;
        WORD    bfReserved2;
        DWORD   bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER; 


void WriteBMP(FILE* infile, FILE *outfile, int sizeX, int sizeY );
int isRGBfile( FILE *infile, int sizeX, int sizeY );
char * CreateFilename( char * sourcefilename );
int isNumber( char * string );

#define MAX_IMAGE_X 2048

void main( int argc, char * argv[] )
{
	FILE *infile;
	FILE *outfile;
	char * outfilename;
	int i;
	int sfArg = 1; /* index to start of filename argument. */
	/* size of bitmap, default to 512 * 512 */
	int sizeX = 512;
	int sizeY = 512;
	
	if ( argc > 1 )
	{
		/* Figure out bitmap size from optional first two arguments. */
		
		if ( isNumber( argv[1] ) )
		{
			sizeX = atoi( argv[1] );
			sfArg = 2;
			if ( argc  > 2 )
			{
				if ( isNumber( argv[2] ) )
				{
					sizeY = atoi( argv[2] );
					sfArg = 3;
				}
				else
				{
					sizeY = sizeX;
				}
			}
		}
		else
		{
			sizeX = 512;
		}

		if ( sizeX > MAX_IMAGE_X || sizeY > MAX_IMAGE_X )
		{
			printf( "Maximum resolution is %d. Aborting.\n", MAX_IMAGE_X );
			return;
		}

		for ( i = sfArg; (i + 1) <= argc; i++ )
		{
			infile = fopen( argv[i], "rb" );
			if ( infile != NULL && isRGBfile( infile, sizeX, sizeY ))
			{
				/* Validated inputfile */
				/* Create output filename. */
				outfilename = CreateFilename( argv[i] );

				outfile = fopen( outfilename, "wb" );
				if ( outfile != NULL )
				{
					WriteBMP( infile, outfile, sizeX, sizeY );
				}
				else
				{
					fprintf( stderr, "Cannot open %s for output.\n",
						outfilename );
				}
			}
			else
			{
				fprintf( stderr, "%s is not a valid %d x %d, 24 bit rgb file.\n",
					 argv[i], sizeX, sizeY );

				if ( infile != NULL )
				{
					fclose( infile );
				}
			}
		}
	}
	else
	{

#ifdef DOS_MODE
		/* Set "stdin" and "stdout" to have binary mode: */
		if ( _setmode( _fileno( stdin ), _O_BINARY ) == -1 ||
			_setmode( _fileno( stdout ), _O_BINARY ) == -1 )
		{
		  perror( "Cannot set stdin and stdout to binary mode" );
		  abort();
		}
#endif

		WriteBMP( stdin, stdout, sizeX, sizeY );
	}
}

void WriteBMP(FILE* infile, FILE *outfile, int sizeX, int sizeY )
{
	int nSize;
	int red, green, blue;
	int row, column;
	static char PadBuf[10];

	BITMAPFILEHEADER bmfh;
	BITMAPINFOHEADER bmih;

	nSize =  sizeof(BITMAPINFOHEADER) + sizeX * sizeY * 3;
	bmfh.bfSize = nSize + sizeof(BITMAPFILEHEADER) + 2;
	/* meaning of bfSize open to interpretation (bytes, words, dwords?)
	 -- we won't use it */
	bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
	bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 2;
		
	bmih.biSize = sizeof(BITMAPINFOHEADER);
    bmih.biWidth = sizeX;
    bmih.biHeight = sizeY;
    bmih.biPlanes = 1;
    bmih.biBitCount = 24;
    bmih.biCompression = 0;
    bmih.biSizeImage = sizeX * sizeY * 3;
    bmih.biXPelsPerMeter = 0;
    bmih.biYPelsPerMeter = 0;
    bmih.biClrUsed = 0;
    bmih.biClrImportant = 0;

	if ( isBigEndian() )
	{
		dwordSwap( &bmfh.bfSize );
		dwordSwap( &bmfh.bfOffBits );
		dwordSwap( &bmih.biSize );
		dwordSwap( &bmih.biWidth );
		dwordSwap( &bmih.biHeight );
		wordSwap( &bmih.biPlanes );
		wordSwap( &bmih.biBitCount );
		dwordSwap( &bmih.biSizeImage );
	}	

	fputs ( "BM", outfile );
	fwrite( &bmfh, sizeof(BITMAPFILEHEADER), 1, outfile );
	fwrite( &bmih, sizeof(BITMAPINFOHEADER), 1, outfile );
	
	for ( row = 0; row < sizeY; row++  )
	{
		for ( column = 0; column < sizeX; column++ )
		{
			red = fgetc( infile ) ;
			green = fgetc( infile ) ;
			blue = fgetc( infile ) ;

			fputc( blue, outfile );	/* reverse order for BMP. */
			fputc( green, outfile );
			fputc( red, outfile );
		}
		/* pad rows to 4 byte boundary */
		fwrite( PadBuf, 1, (4 - ( (sizeX * 3) % 4 )) % 4, outfile );
	}
}

int isRGBfile( FILE *infile, int sizeX, int sizeY )
{
	long position;
	rewind( infile );
	fseek( infile, 0, SEEK_END );
	position = ftell( infile );
	rewind( infile );
	if ( position == sizeX * sizeY * 3 )
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

char * CreateFilename( char * sourcefilename )
{
	static char *string = NULL;
	char * dummystr;
	char * dotchar;
	
	if ( string != NULL )
	{
		free( string );
		string = NULL;
	}

	dummystr = strdup( sourcefilename );
	dotchar = strrchr( dummystr, '.' );
	if ( dotchar != NULL )
	{
		*dotchar = '\0';
	}
		
	string = malloc( strlen( dummystr ) + 5 );
	strcpy( string, dummystr );
	strcat( string, ".bmp" );
	free( dummystr );

	return string;
}

int isNumber( char * string )
{
	int i;
	int iFlag = 0;
	int iStop = 0;

	for ( i = 0; string[i] != '\0' && iStop == 0; i++ )
	{
		if( isdigit( string[i] ) )
		{
			iFlag = 1;
		}
		else
		{
			iFlag = 0;
			iStop = 1;
		}
	}

	return iFlag;
}

void dwordSwap( unsigned long * dword )
{
	unsigned char dummy;

	union words
	{
		unsigned long dword;
		unsigned char byte[4];
	} data;
	
	data.dword = *dword;
	dummy = data.byte[0];
	data.byte[0] = data.byte[3];
	data.byte[3] = dummy;
	dummy = data.byte[1];
	data.byte[1] = data.byte[2];
	data.byte[2] = dummy;
	*dword = data.dword;
}

void wordSwap( unsigned short * word )
{
	unsigned char dummy;

	union words
	{
		unsigned short word;
		unsigned char byte[2];
	} data;
	
	data.word = *word;
	dummy = data.byte[0];
	data.byte[0] = data.byte[1];
	data.byte[1] = dummy;
	*word = data.word;
}

int isBigEndian( void )
{
	union words
	{
		unsigned long dword;
		unsigned short word[2];
	} data;

	data.dword = 1;
	
	if ( data.word[0] == 0 )
	{
		/* fputs( "BigEndian!\n", stderr ); */
		return 1;
	}
	else
	{
		/* fputs( "LittleEndian!\n", stderr ); */
		return 0;
	}
}
