////////////////////////////////////////////////////////////////////////////////
// Implementation of TextureCompiler
// Copyright Bjoern Ganster 
// Developed since 2005
////////////////////////////////////////////////////////////////////////////////

#include "TextureCompiler.h"
#include "FileCache.h"

//#include <assert.h>
#include <iostream>

#define FILE_NUMBER FILE_NO_TEXTURE_COMPILER_CPP

////////////////////////////////////////////////////////////////////////////////
// Constructor

ManagedTexture::ManagedTexture()
: VirtualClass ("ManagedTexture", ManagedTextureID),
  xreps (1),
  yreps (1),
  texture(NULL),
  u1(0.0),
  v1(0.0),
  u2(1.0),
  v2(1.0)
{
}

// Destructor
ManagedTexture::~ManagedTexture()
{
}

////////////////////////////////////////////////////////////////////////////////
// Constructor

TextureCompiler::TextureCompiler()
: VirtualClass ("TextureCompiler", TextureCompilerID),
  atlas(NULL),
  usedWidth(0),
  usedHeight(0)
{
}

////////////////////////////////////////////////////////////////////////////////
// Destructor

TextureCompiler::~TextureCompiler()
{
	SET_SCOPE;
	clearAtlas();
	// mantext is destroyed by objSaver::execute
	RETURN;
}

void TextureCompiler::clearAtlas()
{
	safeDelete(atlas);
}

////////////////////////////////////////////////////////////////////////////////
// Add texture to list of managed textures, returns NULL if unsucessful
// The texture will be deleted when TextureCompiler is deleted

ManagedTexture* TextureCompiler::add(Texture* texture, int ID)
{
	if (texture == NULL) {
		return nullptr;
	}

	// Find smallest unused space that texture fits into
	SET_SCOPE;
	ManagedTexture* mantext = new ManagedTexture();
	mantext->texture = texture;
	mantext->setID (ID);
	mantext->xreps = 0;
	mantext->yreps = 0;
	textures.push_back(mantext);
	clearAtlas();
	RETURN mantext;
}

////////////////////////////////////////////////////////////////////////////////

void TextureCompiler::flipCopy(const Texture* source, TextureDim su1, TextureDim sv1, 
	TextureDim su2, TextureDim sv2, Texture* target, TextureDim tu, TextureDim tv, bool flipX, bool flipY)
{
	if (source == nullptr || target == nullptr) {
		return;
	}

	SET_SCOPE;
	for (size_t y = 0; y < sv2-sv1; ++y) {
		for (size_t x = 0; x < su2-su1; ++x) {
			size_t sx = su1 + x;
			size_t sy = sv1 + y;

			if (flipX)
				sx = su2 - x -1;
			if (flipY)
				sy = sv2 - y -1;

			unsigned char r, g, b, a;
			source->getRGBA(sx, sy, r, g, b, a);
			target->setRGBA(tu+x, tv+y, r, g, b, a);
		}
	}
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Build OpenGL texture from list of textures, user should free its memory

Texture* TextureCompiler::buildAtlas()
{
	SET_SCOPE;
	if (!reassignPatches()) {
		RETURN nullptr;
	}

	atlas = new Texture (usedWidth, usedHeight, GL_RGBA);
	for (vector<ManagedTexture*>::iterator i = textures.begin(); 
		i != textures.end(); 
		i++) 
	{
		ManagedTexture* mantext = (*i);
		Texture* texture = mantext->texture;
		for (size_t x = 0; (int) x < mantext->getXreps(); ++x) {
			for (size_t y = 0; (int) y < mantext->getYreps(); ++y) {
				bool flipX = ((x % 2) > 0);
				bool flipY = ((y % 2) > 0);
				/*atlas->copy (texture, 0, 0, texture->getWidth(),
					texture->getHeight (), 
					(size_t) mantext->u1+x*texture->getWidth(), 
					(size_t) mantext->v1+y*texture->getHeight());*/
				flipCopy (texture, 0, 0, texture->getWidth(), texture->getHeight (),
					atlas, 
					(size_t) mantext->u1+x*texture->getWidth(), 
					(size_t) mantext->v1+y*texture->getHeight(),
					flipX, flipY);
			}
		}

		// Add or distract 0.5 from the coordinates to ensure that we are in the
		// texel centers?
		mantext->u1 = mantext->u1 / usedWidth;
		mantext->v1 = mantext->v1 / usedHeight;
		mantext->u2 = mantext->u2 / usedWidth;
		mantext->v2 = mantext->v2 / usedHeight;
	}

	RETURN atlas;
}

////////////////////////////////////////////////////////////////////////////////
// Manage textures by ID

bool TextureCompiler::removeAndDeleteTextureByID(int ID)
{
   bool found = false;
   vector<ManagedTexture*>::iterator i = textures.begin();
   while (i != textures.end())
   {
      ManagedTexture* candidate = (*i);
      if (candidate->getID() == ID) {
         delete candidate->texture;
         delete candidate;
         i = textures.erase(i);
         found = true;
         clearAtlas();
      } else
         i++;
   }

   return found;
}

ManagedTexture* TextureCompiler::getTextureByID(int ID)
{
   ManagedTexture* result = NULL;;
   vector<ManagedTexture*>::const_iterator i = textures.begin();
   while (i != textures.end())
   {
      ManagedTexture* candidate = (*i);
      if (candidate->getID() == ID) {
         return (*i);
      } else
         i++;
   }

   return NULL;
}

////////////////////////////////////////////////////////////////////////////////
// Reassign UV mappings in all managed textures

bool TextureCompiler::reassignPatches()
{
	SET_SCOPE;
	if (textures.size() > 0) {
		// Determine biggest height parameter and sum of widths
		usedHeight = 0;
		usedWidth = 0;

		SET_OP;
		for (size_t i = 0; i < textures.size(); i++) {
			ManagedTexture* mt = textures[i];
			Texture* text = mt->texture;
			if (text->getHeight()*mt->getYreps() > usedHeight)
				usedHeight = text->getHeight()*mt->getYreps();
			usedWidth += text->getWidth()*mt->getXreps();
		}

		if (usedWidth > 8192) {
			SET_OP;
			COUT << "Atlas size reaches absurd limits, aborting" << std::endl;
			RETURN false;
		}

		// Create power-of-two texture
		SET_OP;
		size_t w = 1, h = 1;
		while (w < usedWidth)
			w += w;
		while (h < usedHeight)
			h += h;
		usedWidth = w;
		usedHeight = h;

		// Assign coordinates
		SET_OP;
		double u = 0;
		for (size_t i = 0; i < textures.size(); i++) {
			ManagedTexture* mt = textures[i];
			Texture* text = mt->texture;
			mt->u1 = u;
			mt->u2 = u + text->getWidth();
			mt->v1 = 0.0;
			mt->v2 = text->getHeight();
			u += text->getWidth() * mt->getXreps();
		}

		SET_OP;
		clearAtlas();
	}
	RETURN true;
}

////////////////////////////////////////////////////////////////////////////////

ManagedTexture* TextureCompiler::findTextureByID(int ID) const
{
   // Test all textures
   for (size_t i = 0; i < textures.size(); i++) {
      ManagedTexture* mantext = textures[i];
      if (mantext->getID() == ID) {
         return mantext;
      }
   }

   // If nothing was found, return NULL
   return NULL;
}

// Transform coordinates local to texture into global coordinates
double ManagedTexture::transformU (double u) const
{
	return u1 + (u2-u1) * u;
}
double ManagedTexture::transformV (double v) const
{
	return v1 + (v2-v1) * v;
}

// Get atlas
Texture* TextureCompiler::getAtlas()
{
	if (atlas == NULL) {
		atlas = buildAtlas();
	}
	return atlas; 
}

// Get texture
ManagedTexture* TextureCompiler::getTexture(size_t i)
{
	COUT << "Getting texture " << i << "/" << textures.size() << std::endl;
	if (textures.size() > 0)
		return textures[i%textures.size()];
	else
		return NULL;
}

// Get number of included textures
size_t TextureCompiler::getTextureCount() const
{
	return textures.size();
}

// Get width/height
size_t TextureCompiler::GetWidth ()
{
	return usedWidth;
}
size_t TextureCompiler::GetHeight ()
{
	return usedHeight;
}
