#include "global.h"
#include "RageSurface_Load_BMP.h"
#include "RageFile.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "RageSurface.h"
using namespace FileReading;

/* Tested with http://entropymine.com/jason/bmpsuite/. */

enum
{
	COMP_BI_RGB = 0,
	COMP_BI_RLE4, /* unsupported */
	COMP_BI_RLE8, /* unsupported */
	COMP_BI_BITFIELDS
};


/* When returning error, the first error encountered takes priority. */
#define FATAL_ERROR(s) \
{ \
	if( sError.size() == 0 ) sError = (s); \
	return RageSurfaceUtils::OPEN_FATAL_ERROR; \
}

static RageSurfaceUtils::OpenResult LoadBMP( RageFile &f, RageSurface *&img, RString &sError )
{
	char magic[2];
	ReadBytes( f, magic, 2, sError );
	if( magic[0] != 'B' || magic[1] != 'M' )
	{
		sError = "not a BMP";
		return RageSurfaceUtils::OPEN_UNKNOWN_FILE_FORMAT;
	}

	img = NULL;

	read_u32_le( f, sError ); /* file size */
	read_u32_le( f, sError ); /* unused */
	uint32_t iDataOffset = read_u32_le( f, sError );
	uint32_t iHeaderSize = read_u32_le( f, sError );

	uint32_t iWidth, iHeight, iPlanes, iBPP, iCompression = COMP_BI_RGB, iColors = 0;
	if( iHeaderSize == 12 )
	{
		/* OS/2 format */
		iWidth = read_u16_le( f, sError );
		iHeight = read_u16_le( f, sError );
		iPlanes = read_u16_le( f, sError );
		iBPP = read_u16_le( f, sError );
	}
	else if( iHeaderSize == 40 )
	{
		iWidth = read_u32_le( f, sError );
		iHeight = read_u32_le( f, sError );
		iPlanes = read_u16_le( f, sError );
		iBPP = read_u16_le( f, sError );
		iCompression = read_u32_le( f, sError );
		read_u32_le( f, sError ); /* bitmap size */
		read_u32_le( f, sError ); /* horiz resolution */
		read_u32_le( f, sError ); /* vert resolution */
		iColors = read_u32_le( f, sError );
		read_u32_le( f, sError ); /* "important" colors */
	}
	else
		FATAL_ERROR( ssprintf( "expected header size of 40, got %u", iHeaderSize ) );

	if( iBPP <= 8 && iColors == 0 )
		iColors = 1 << iBPP;
	if( iPlanes != 1 )
		FATAL_ERROR( ssprintf( "expected one plane, got %u", iPlanes ) );
	if( iBPP != 1 && iBPP != 4 && iBPP != 8 && iBPP != 16 && iBPP != 24 && iBPP != 32 )
		FATAL_ERROR( ssprintf( "unsupported bpp %u", iBPP ) );
	if( iCompression != COMP_BI_RGB && iCompression != COMP_BI_BITFIELDS )
		FATAL_ERROR( ssprintf( "unsupported compression %u", iCompression ) );

	if( iCompression == COMP_BI_BITFIELDS && iBPP <= 8 )
		FATAL_ERROR( ssprintf( "BI_BITFIELDS unexpected with bpp %u", iBPP ) );

	int iFileBPP = iBPP;
	iBPP = max( iBPP, 8u );

	int Rmask = 0, Gmask = 0, Bmask = 0, Amask = 0;
	switch( iBPP )
	{
	case 16:
		Rmask = Swap16LE( 0x7C00 );
		Gmask = Swap16LE( 0x03E0 );
		Bmask = Swap16LE( 0x001F );
		break;
	case 24:
		Rmask = Swap24LE( 0xFF0000 );
		Gmask = Swap24LE( 0x00FF00 );
		Bmask = Swap24LE( 0x0000FF );
		break;
	case 32:
		Rmask = Swap32LE( 0x00FF0000 );
		Gmask = Swap32LE( 0x0000FF00 );
		Bmask = Swap32LE( 0x000000FF );
		break;
	}

	if( iCompression == COMP_BI_BITFIELDS )
	{
		Rmask = read_u32_le( f, sError );
		Gmask = read_u32_le( f, sError );
		Bmask = read_u32_le( f, sError );
	}

	/* Stop on error before we use any of the values we just read. */
	if( sError.size() != 0 )
		return RageSurfaceUtils::OPEN_FATAL_ERROR;

	img = CreateSurface( iWidth, iHeight, iBPP, Rmask, Gmask, Bmask, Amask );

	if( iBPP == 8 )
	{
		RageSurfaceColor Palette[256];
		ZERO( Palette );

		if( iColors > 256 )
			FATAL_ERROR( ssprintf( "unexpected colors %i", iColors ) );

		for( unsigned i = 0; i < iColors; ++i )
		{
			Palette[i].b = read_8( f, sError );
			Palette[i].g = read_8( f, sError );
			Palette[i].r = read_8( f, sError );
			Palette[i].a = 0xFF;
			/* Windows BMP palettes are padded to 32bpp.  */
			if( iHeaderSize == 40 )
				read_8( f, sError );
		}

		memcpy( img->fmt.palette->colors, Palette, sizeof(Palette) );
	}

	/* Stop on error before we seek, so we don't return the wrong error message. */
	if( sError.size() != 0 )
		return RageSurfaceUtils::OPEN_FATAL_ERROR;

	int iFilePitch = iFileBPP * iWidth; // in bits
	iFilePitch = (iFilePitch+7) / 8; // in bytes: round up
	iFilePitch = (iFilePitch+3) & ~3; // round up a multiple of 4

	{
		int ret = f.Seek( iDataOffset );
		if( ret == -1 )
			FATAL_ERROR( f.GetError() );
		if( ret != (int) iDataOffset )
			FATAL_ERROR( "Unexpected end of file" );
	}

	for( int y = (int) iHeight-1; y >= 0; --y )
	{
		uint8_t *pRow = img->pixels + img->pitch*y;
		RString buf;

		f.Read( buf, iFilePitch );

		/* Expand 1- and 4-bits to 8-bits. */
		if( iFileBPP == 1 )
		{
			for( unsigned x = 0; x < iWidth; ++x )
			{
				int iByteNo = x >> 3;
				int iBitNo = 7-(x&7);
				int iBit = 1 << iBitNo;
				pRow[x] = !!(buf[iByteNo] & iBit);
			}
		}
		else if( iFileBPP == 4 )
		{
			for( unsigned x = 0; x < iWidth; ++x )
			{
				if( (x & 1) == 0 )
					pRow[x] = buf[x/2] & 0x0F;
				else
					pRow[x] = (buf[x/2] >> 4) & 0x0F;
			}
		}
		else
			memcpy( pRow, buf.data(), img->pitch );
	}

	return sError.size() != 0? RageSurfaceUtils::OPEN_FATAL_ERROR: RageSurfaceUtils::OPEN_OK;
}

RageSurfaceUtils::OpenResult RageSurface_Load_BMP( const RString &sPath, RageSurface *&img, bool bHeaderOnly, RString &error )
{
	RageFile f;

	if( !f.Open( sPath ) )
	{
		error = f.GetError();
		return RageSurfaceUtils::OPEN_FATAL_ERROR;
	}

	RageSurfaceUtils::OpenResult ret;
	img = NULL;
	ret = LoadBMP( f, img, error );

	if( ret != RageSurfaceUtils::OPEN_OK && img != NULL )
	{
		delete img;
		img = NULL;
	}

	return ret;
}

/*
 * Copyright (c) 2004 Glenn Maynard
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
