/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Please read the file LICENSE.TXT for additional details.


	source: 
		mipmap generation

	revision history:
		Sep/15/1999 - Jukka Liimatta - initial revision
		Oct/26/2000 - Jukka Liimatta - HighQuality 8bit indexed mipping
*/
#include <prcore/prcore.hpp>
using namespace prcore;



//////////////////////////////////////////////////////
// mipinfo                                         //
////////////////////////////////////////////////////

	struct MipInfo
	{
		uint8*		pdest;
		uint8*		psrc;
		int			width, height, pitch;
		uint32		rmask, gmask, bmask, amask, imask;
		float		rcp;
	};

	typedef void (*MipFunc)(const MipInfo&);


//////////////////////////////////////////////////////
// innerloops                                      //
////////////////////////////////////////////////////

static Color32* palette = NULL;


static int FindPaletteIndex(int red, int green, int blue)
{
	int best = 0xfffffff;
	int index = 0;
	for ( int i=0; i<256; i++ )
	{
		int b = palette[i].b - blue;
		int g = palette[i].g - green;
		int r = palette[i].r - red;
		int dist = b*b + g*g + r*r;

		if ( dist < best )
		{
			best = dist;
			index = i;
		}
	}
	return index;
}


static void MipIndexed8(const MipInfo& info)
{
	float r = 0.0f;
	float g = 0.0f;
	float b = 0.0f;

	uint8* ptr = info.psrc;
	int adder = info.pitch - info.width;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width;
		do
		{
			Color32& color = palette[ *ptr++ ];
			b += color.b;
			g += color.g;
			r += color.r;
		} while (--count);
		ptr += adder;
	}

	int red   = int(r * info.rcp);
	int green = int(g * info.rcp);
	int blue  = int(b * info.rcp);
	*info.pdest = FindPaletteIndex(red,green,blue);
}


static void MipIndexed16(const MipInfo& info)
{
	*(int16*)info.pdest = *(int16*)info.psrc;
}


static void MipIndexed24(const MipInfo& info)
{
	info.pdest[0] = info.psrc[0];
	info.pdest[1] = info.psrc[1];
	info.pdest[2] = info.psrc[2];
}


static void MipIndexed32(const MipInfo& info)
{
	*(uint32*)info.pdest = *(uint32*)info.psrc;
}


static void MipIntensity8(const MipInfo& info)
{
	float i = 0.0f;
	float a = 0.0f;

	uint8* ptr = info.psrc;
	int adder = info.pitch - info.width;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width;
		do
		{
			uint32 color = *ptr++;
			i += float(color & info.imask);
			a += float(color & info.amask);
		} while (--count);
		ptr += adder;
	}

	uint32 luma  = uint32( i*info.rcp ) & info.imask;
	uint32 alpha = uint32( a*info.rcp ) & info.amask;
	*info.pdest = uint8(alpha | luma);
}


static void MipIntensity16(const MipInfo& info)
{
	float i = 0.0f;
	float a = 0.0f;

	uint32* ptr = (uint32*)info.psrc;
	int adder = info.pitch/4 - info.width/2;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width / 2;
		do
		{
			uint32 low = *ptr++;
			uint32 high = low >> 16;
			i += float((low & info.imask) + (high & info.imask));
			a += float((low & info.amask) + (high & info.amask));
		} while (--count);
		ptr += adder;
	}

	uint32 luma  = uint32( i*info.rcp ) & info.imask;
	uint32 alpha = uint32( a*info.rcp ) & info.amask;
	*(int16*)info.pdest = alpha | luma;
}


static void MipIntensity24(const MipInfo& info)
{
	float i = 0.0f;
	float a = 0.0f;

	uint8* ptr = info.psrc;
	int adder = info.pitch - info.width*3;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width;
		do
		{
			uint32 color;
			uint8* pc = (uint8*)&color;
			pc[0] = *ptr++;
			pc[1] = *ptr++;
			pc[2] = *ptr++;

			i += float(color & info.imask);
			a += float(color & info.amask);
		} while (--count);
		ptr += adder;
	}

	uint32 luma  = uint32( i*info.rcp ) & info.imask;
	uint32 alpha = uint32( a*info.rcp ) & info.amask;
	uint32 color = alpha | luma;

	uint8* pc = (uint8*)&color;
	info.pdest[0] = pc[0];
	info.pdest[1] = pc[1];
	info.pdest[2] = pc[2];
}


static void MipIntensity32(const MipInfo& info)
{
	float i = 0.0f;
	float a = 0.0f;

	uint32* ptr = (uint32*)info.psrc;
	int adder = info.pitch/4 - info.width;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width;
		do
		{
			uint32 color = *ptr++;
			i += float(color & info.imask);
			a += float(color & info.amask);
		} while (--count);
		ptr += adder;
	}

	uint32 luma  = uint32( i*info.rcp ) & info.imask;
	uint32 alpha = uint32( a*info.rcp ) & info.amask;
	*(uint32*)info.pdest = alpha | luma;
}


static void MipDirect8(const MipInfo& info)
{
	float r = 0.0f;
	float g = 0.0f;
	float b = 0.0f;
	float a = 0.0f;

	int16* ptr = (int16*)info.psrc;
	int adder = (info.pitch - info.width)/2;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width/2;
		do
		{
			uint32 low = *ptr++;
			uint32 high = low >> 8;
			r += float((low & info.rmask) + (high & info.rmask));
			g += float((low & info.gmask) + (high & info.gmask));
			b += float((low & info.bmask) + (high & info.bmask));
			a += float((low & info.amask) + (high & info.amask));
		} while (--count);
		ptr += adder;
	}

	uint32 red   = uint32( r*info.rcp ) & info.rmask;
	uint32 green = uint32( g*info.rcp ) & info.gmask;
	uint32 blue  = uint32( b*info.rcp ) & info.bmask;
	uint32 alpha = uint32( a*info.rcp ) & info.amask;
	*info.pdest = uint8(alpha | red | green | blue);
}


static void MipDirect16(const MipInfo& info)
{
	float r = 0.0f;
	float g = 0.0f;
	float b = 0.0f;
	float a = 0.0f;

	uint32* ptr = (uint32*)info.psrc;
	int adder = info.pitch/4 - info.width/2;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width / 2;
		do
		{
			uint32 low = *ptr++;
			uint32 high = low >> 16;
			r += float((low & info.rmask) + (high & info.rmask));
			g += float((low & info.gmask) + (high & info.gmask));
			b += float((low & info.bmask) + (high & info.bmask));
			a += float((low & info.amask) + (high & info.amask));
		} while (--count);
		ptr += adder;
	}

	uint32 red   = uint32( r*info.rcp ) & info.rmask;
	uint32 green = uint32( g*info.rcp ) & info.gmask;
	uint32 blue  = uint32( b*info.rcp ) & info.bmask;
	uint32 alpha = uint32( a*info.rcp ) & info.amask;
	*(int16*)info.pdest = alpha | red | green | blue;
}


static void MipDirect24(const MipInfo& info)
{
	float r = 0.0f;
	float g = 0.0f;
	float b = 0.0f;
	float a = 0.0f;

	uint8* ptr = info.psrc;
	int adder = info.pitch - info.width*3;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width;
		do
		{
			uint32 color;
			uint8* pc = (uint8*)&color;
			pc[0] = *ptr++;
			pc[1] = *ptr++;
			pc[2] = *ptr++;

			r += float(color & info.rmask);
			g += float(color & info.gmask);
			b += float(color & info.bmask);
			a += float(color & info.amask);
		} while (--count);
		ptr += adder;
	}

	uint32 red   = uint32( r*info.rcp ) & info.rmask;
	uint32 green = uint32( g*info.rcp ) & info.gmask;
	uint32 blue  = uint32( b*info.rcp ) & info.bmask;
	uint32 alpha = uint32( a*info.rcp ) & info.amask;
	uint32 color = alpha | red | green | blue;

	uint8* pc = (uint8*)&color;
	info.pdest[0] = pc[0];
	info.pdest[1] = pc[1];
	info.pdest[2] = pc[2];
}


static void MipDirect32(const MipInfo& info)
{
	float r = 0.0f;
	float g = 0.0f;
	float b = 0.0f;
	float a = 0.0f;

	uint32* ptr = (uint32*)info.psrc;
	int adder = info.pitch/4 - info.width;
	for ( int y=0; y<info.height; y++ )
	{
		int count = info.width;
		do
		{
			uint32 color = *ptr++;
			r += float(color & info.rmask);
			g += float(color & info.gmask);
			b += float(color & info.bmask);
			a += float(color & info.amask);
		} while (--count);
		ptr += adder;
	}

	uint32 red   = uint32( r*info.rcp ) & info.rmask;
	uint32 green = uint32( g*info.rcp ) & info.gmask;
	uint32 blue  = uint32( b*info.rcp ) & info.bmask;
	uint32 alpha = uint32( a*info.rcp ) & info.amask;
	*(uint32*)info.pdest = alpha | red | green | blue;
}


//////////////////////////////////////////////////////
// helpers                                         //
////////////////////////////////////////////////////

static inline int SubLevel(int lod, int sublevel)
{
	int level = lod - sublevel;
	return (level < 0) ? 0 : level;
}


static MipFunc SelectInnerloop(const PixelFormat& format)
{
	if ( format.IsDirect() )
	{
		switch ( format.GetBits() )
		{
			case 8: return MipDirect8;
			case 16: return MipDirect16;
			case 24: return MipDirect24;
			case 32: return MipDirect32;
			default: return NULL;
		}
	}
	else if ( format.IsIndexed() )
	{
		switch ( format.GetBits() )
		{
			case 8: return MipIndexed8;
			case 16: return MipIndexed16;
			case 24: return MipIndexed24;
			case 32: return MipIndexed32;
			default: return NULL;
		}
	}
	else
	{
		switch ( format.GetBits() )
		{
			case 8: return MipIntensity8;
			case 16: return MipIntensity16;
			case 24: return MipIntensity24;
			case 32: return MipIntensity32;
			default: return NULL;
		}
	}
	return NULL;
}


static Bitmap DrawMipLevel(const Surface& surface, int lodx, int lody, int sublevel)
{
	// just copy, as the sizes match
	if ( !sublevel )
		return Bitmap(surface);

	// precalculate stuff.
	int slodx = SubLevel(lodx,sublevel);
	int slody = SubLevel(lody,sublevel);
	int tilex = (1 << slodx);
	int tiley = (1 << slody);

	// create mipmap
	const PixelFormat& format = surface.GetFormat();
	Bitmap mipmap(tilex,tiley,format);

	// parms
	MipInfo info;
	info.pdest = mipmap.GetImage();
	info.psrc = surface.GetImage();
	info.width = surface.GetWidth() / tilex;
	info.height = surface.GetHeight() / tiley;
	info.pitch = surface.GetPitch();
	info.rmask = format.GetRedMask();
	info.gmask = format.GetGreenMask();
	info.bmask = format.GetBlueMask();
	info.amask = format.GetAlphaMask();
	info.imask = format.GetIntensityMask();
	info.rcp = 1.f / float( info.width*info.height );

	// palette
	if ( format.IsIndexed() )
		palette = format.GetPalette();

	// innerloop pointer
	MipFunc Func = SelectInnerloop( format );
	if ( !Func )
		return mipmap;


	// filtering
	for ( int y=0; y<tiley; y++ )
	{
		info.pdest = mipmap.GetImage() + y*mipmap.GetPitch();
		info.psrc = surface.GetImage() + y*info.height*surface.GetPitch();
		for ( int x=0; x<tilex; x++ )
		{
			Func( info );

			info.pdest += mipmap.GetFormat().GetBytes();
			info.psrc += format.GetBytes() * info.width;
		}
	}

	return mipmap;
}


//////////////////////////////////////////////////////
// CreateMipLevel                                  //
////////////////////////////////////////////////////

Bitmap prcore::CreateMipLevel(const Surface& surface, int sublevel)
{
	// assert
	assert( sublevel >= 0 );
	assert( surface.GetImage() );

	uint16 width = surface.GetWidth();
	uint16 height = surface.GetHeight();

	// log2 based size ( pseudo nearest )
	int lodx = ilog2(width);
	int lody = ilog2(height);
	
	// resize if not log2
	uint16 w = 1 << lodx;
	uint16 h = 1 << lody;
	if ( (width != w) || (height != h) )
	{
		Bitmap temp(w,h,surface.GetFormat());
		temp.Blit(surface,Surface::BLIT_BILINEAR_SCALE);
		return DrawMipLevel(temp,lodx,lody,sublevel);
	}

	return DrawMipLevel(surface,lodx,lody,sublevel);
}
