// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   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
// -------------------------- END HEADER -------------------------------------

#include "../hacks/winhacks.h"

#include <iostream>
#include <cassert>
using namespace std;

#include <GL/glew.h>
#include <GL/gl.h>

#include "../console/console.h"
using namespace Console;

#include "../filesystem/filesystem.h"
#include "../image/image.h"

#include "texture.h"

Texture::Texture(void) {
	timeSinceAccess = 0;
	loaded = false;
	texNum = 0;
	image = NULL;
	
	origWidth  = 0;
	origHeight = 0;
	pow2Width  = 0;
	pow2Height = 0;
	
	dontPrune = false;
	linearFilter = true;
	
	glTexDone = false;

    backgroundLoadFinished = false;
}

Texture::~Texture(void) {

    // We MUST wait for the loader to be done with this! But it's
    // okay, because if it's getting destroyed, then it's either
    // cleaning up everything or deleting something that's not used
    // anymore that's already loaded.
    waitForLoad();

	// Delete the Image.
	if(image) {
		delete image;
	}
	
	// Free the video memory.
	if(texNum != 0) {
		glDeleteTextures(1, &texNum);
	}

}

void Texture::startLoad(const std::string &fileName) {

	timeSinceAccess = 0;	
	assert(loaded == false);
	name = fileName;
}

void Texture::waitForLoad(void) {
	timeSinceAccess = 0;

    // This is really ugly and I hope it rarely hits this.
    if(!backgroundLoadFinished) {

        out("warning") << "Waiting for a texture to load: " << getName() << endl;

        while(!backgroundLoadFinished) {
            usleep(1000);
        }
    }
}

bool Texture::isLoaded(void) {
	return loaded;
}

unsigned int Texture::getTimeSinceAccess(void) {
	return timeSinceAccess;
}

void Texture::incrementAge(void) {
	if(!dontPrune) {
		timeSinceAccess++;
	}
}

void Texture::backgroundLoad(void) {

	timeSinceAccess = 0;

    // Use this if we ever need to test for worst-case load delays.
    //usleep(2000000);
	
	assert(!image);
	
	int length;
	
	// TODO: Use a loader appropriate for a given file type here
	//   instead of just TGAs for everything.
	char *tgaData = FileSystem::loadFile(name, &length);
	if(tgaData) {
		image = loadTGA(tgaData, length, true);
		delete[] tgaData;
	} else {
		out("error") << "Failed to load texture \"" << name << "\"" << endl;
	}
	
	if(image) {
		
		origWidth = image->getWidth();
		origHeight = image->getHeight();

		// Done. Everything is ready to be loaded into a texture.
		loaded = true;
		
	} else {
		
		// Something went horribly wrong.		
		loaded = false;
	}

    backgroundLoadFinished = true;
}

std::string Texture::getName(void) {
	return name;
}

void Texture::bindTexture(void) {
	
	// Reset timer because we know we're using this texture.
	timeSinceAccess = 0;

    if(backgroundLoadFinished && loaded) {
	
        // If the image is NULL, the file failed to load, so no texture.
        if(image == NULL && !hasGLTexture()) {
            glBindTexture(GL_TEXTURE_2D, 0);
            return;
        }
	
        // Make a GL texture out of our loaded stuff if it isn't yet.
        makeGLTexture();
	
        // Bind it.
        glBindTexture(GL_TEXTURE_2D, texNum);
	
        // No shader for a bare texture.
        if(glUseProgramObjectARB) glUseProgramObjectARB(0);

    } else {

        // Texture not loaded yet.
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}

void Texture::reloadFromImage(void) {
	
	// Clean up the existing texture.
	if(texNum != 0) {

		glDeleteTextures(1, &texNum);
		texNum = 0;

	}

	if(image) {

		ImageTexture imgTex;
		image->createGLTexture(&imgTex);
		texNum = imgTex.glTexNum;
		
		pow2Width  = imgTex.pow2Width;
		pow2Height = imgTex.pow2Height;
		origWidth  = imgTex.origWidth;
		origHeight = imgTex.origHeight;

	} else {

        out("error") << "Cannot reload texture from image. Image has been deleted." << endl;

    }

	setFilter(linearFilter);
}

Image *Texture::getOriginalImage(void) {
	timeSinceAccess = 0;
	
	return image;
}


void Texture::setDontPrune(bool dontPrune) {
	timeSinceAccess = 0;
	this->dontPrune = dontPrune;
}

bool Texture::getDontPrune(void) {
	return dontPrune;
}

void Texture::setFilter(bool filter) {
	linearFilter = filter;
	
	if(loaded && texNum) {
		
		glBindTexture(GL_TEXTURE_2D, texNum);
		
		GLuint filterType = filter ? GL_LINEAR : GL_NEAREST;
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterType);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterType);
	}
}

/// Get linear filtering.
bool Texture::getFilter(void) {
	return linearFilter;
}

int Texture::getGLTextureNumber(void) {
	
	timeSinceAccess = 0;
	
	makeGLTexture();
	
	return texNum;
}

void Texture::makeGLTexture(void) {
	
	if(texNum == 0) {
		
		// Texture hasn't been loaded into video memory yet...
		ImageTexture imgTex;
		
		if(image) {
			image->createGLTexture(&imgTex);
			texNum = imgTex.glTexNum;
			
			// Now that the GL texturizer's had its way with it, get the
			// adjusted power of two width and height. These might not be
			// actual powers of two if the GL system allowed us to use
			// non-power-of-two textures.
			pow2Width  = imgTex.pow2Width;
			pow2Height = imgTex.pow2Height;
            origWidth  = imgTex.origWidth;
            origHeight = imgTex.origHeight;
			
			setFilter(linearFilter);

            delete image;
            image = NULL;

		} else {
			pow2Width = 1;
			pow2Height = 1;
		}
		
        glTexDone = true;

	}
	
}

bool Texture::hasGLTexture(void) {
	return glTexDone;
}

