/*  $Id: sprite_packer.cpp,v 1.15 2004/02/28 14:24:38 sphair Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library 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
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

#include "Display/display_precomp.h"
#include "API/Display/sprite_description.h"
#include "API/Display/sprite_packer.h"
#include "API/Core/System/error.h"
#include "API/Core/System/log.h"
#include "sprite_packer_generic.h"

/////////////////////////////////////////////////////////////////////////////
// CL_SpritePacker construction:

CL_SpritePacker::CL_SpritePacker(const CL_SpriteDescription &description)
: impl(new CL_SpritePacker_Generic(description))
{
}

CL_SpritePacker::~CL_SpritePacker()
{
	delete impl;
}

/////////////////////////////////////////////////////////////////////////////
// CL_SpritePacker attributes:

const std::vector<CL_Size> &CL_SpritePacker::get_texture_sizes() const
{
	return impl->texture_sizes;
}

const std::list<CL_SpritePacker::TexturePair> &CL_SpritePacker::get_frames() const
{
	return impl->packed_frames;
}

/////////////////////////////////////////////////////////////////////////////
// CL_SpritePacker operations:

bool CL_SpritePacker::pack(int max_width, int max_height, int min_width, int min_height, int max_textures)
{
	// Fetch all frames from SpriteDescription.
	std::list<CL_SpriteDescription::FramePair>::const_iterator it_frames;
	const std::list<CL_SpriteDescription::FramePair> &frames = impl->description.get_frames();

	std::list<TexturePair>::iterator it_packed_frames;

	// Make a new list with just as many elements as spritedescription.
	// A texture index of -1 means the texture hasn't been packed yet.
	for(int i = frames.size(); i > 0; i--)
		impl->packed_frames.push_back(TexturePair(-1, CL_Rect()));

	// Initial values for texture-size
	int width = min_width;
	int height = min_height;
	impl->texture_sizes.push_back(CL_Size(width, height));

	CL_SpritePacker_Generic::Node root;
	int current_texture = 0;
	bool all_fit;
	do
	{
		root.rect = CL_Rect(0, 0, width, height);
		all_fit = true;
		
		// Pack all frames!
		for(it_frames = frames.begin(), it_packed_frames = impl->packed_frames.begin();
			it_frames != frames.end();
			++it_frames, ++it_packed_frames)
		{
			if((*it_frames).second.get_width() > max_width || (*it_frames).second.get_height() > max_height)
			{
			#ifdef _DEBUG
				CL_Log::log("debug", "Error: Trying to pack sprite larger than the maximum");
				CL_Log::log("debug", "Width: %1, Max Width %2", (*it_frames).second.get_width(), max_width);
				CL_Log::log("debug", "Height: %1, Max Height %2", (*it_frames).second.get_height(), max_height);
			#endif
				return false;
			}
			
			if((*it_packed_frames).first == -1)	// Frame not yet been packed.
			{
				CL_SpritePacker_Generic::Node *node = root.insert(*it_frames);

				if(node)	// Frame was packed ok.
				{
					(*it_packed_frames).first = current_texture;
					(*it_packed_frames).second = node->rect;
				}
				else	// Frame couldn't fit into current texture.
					all_fit = false;
			}
		}
		
		if(all_fit == false)	// All textures didnt fit.
		{
			// A maximum texture-size ?
			if((width == height && (width * 2) <= max_width) || (height * 2) <= max_height)
			{
				if(width == height && (width * 2) <= max_width)
					width *= 2;
				else
					height *= 2;

				// Clear current texture.
				for(it_packed_frames = impl->packed_frames.begin();
					it_packed_frames != impl->packed_frames.end();
					++it_packed_frames)
				{
					if((*it_packed_frames).first == current_texture)
						(*it_packed_frames).first = -1;
				}
				
				impl->texture_sizes[current_texture] = CL_Size(width, height);
			}
			else
			{
				current_texture++;

				// Check if we still have textures available.
				if(current_texture >= max_textures && max_textures != -1)
					return false;

				// Start a minimum size for next texture.
				width = min_width;
				height = min_height;

				impl->texture_sizes.push_back(CL_Size(width, height));
			}
							
			root.clear();
		}
	} while(all_fit == false);

	return true;
}
