// ---------------------------------------------------------------------------
// 
//   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>
using namespace std;
#include "assert.h"

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

#include <boost/bind.hpp>

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

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

#include "texturemanager.h"
#include "texture.h"
#include "metatexture.h"
#include "atlastexture.h"

int textureLoadWorkerFunc(TextureManager *manager) {

    vector<Texture*> localQueue;

    while(!manager->loadWorkerQuit) {

        // Copy the whole main queue into something we can mess around
        // with in just this thread.

        manager->loadQueueMutex->lock();

        if(manager->loadQueue.size()) {
            localQueue = manager->loadQueue;
            manager->loadQueue.clear();
        }

        manager->loadQueueMutex->unlock();


        if(localQueue.size()) {

            // Load everything in the queue.

            for(unsigned int i = 0; i < localQueue.size(); i++) {

                Texture *tex = localQueue[i];

                out("system") << "Loading " << tex->getName() << endl;
                tex->backgroundLoad();
            }
        
            // Clear it.

            localQueue.clear();
        }

        // Sleep a little while.

        usleep(1000);
    }

    out("system") << "Texture load thread shutting down" << endl;

    return 0;
}

TextureManager::TextureManager(void) {
	
	shadersEnabled = true;

    loadWorkerQuit = false;
    loadQueueMutex = new Threads::Mutex();
    loadWorkerThread = new Threads::Thread(boost::bind(textureLoadWorkerFunc, this));
	
}

TextureManager::~TextureManager(void) {
	
	for(unsigned int i = 0; i < textures.size(); i++) {
		// Ignores pruning exclusion.
		delete textures[i];
	}
	
	// Clean up texture metadata.
	for(unsigned int i = 0; i < textureInfos.size(); i++) {
		delete textureInfos[i];
	}
    
    loadWorkerQuit = true;
    loadWorkerThread->join();
    delete loadWorkerThread;
    delete loadQueueMutex;
}

Texture *TextureManager::getTextureByName(const std::string &texName) {
	return getTextureByIndex(getTextureIndexByName(texName));
}

Texture *TextureManager::getTextureByIndex(int index) {
	// I suppose I could just return NULL for this, but it shouldn't
	// happen ever and there's a bug with the program if it does.
	assert(index < int(textures.size()));
	
	// Invalid texture.
	if(index == -1) return NULL;
	
	if(textures[index] == NULL) {
		
		// Looks like this texture was pruned at some point. Start
		// reloading it now that we need it again.
		Texture *newTex = createTexture(textureNames[index]);
		
		// Restore the filtering mode.
		if(newTex) {
			newTex->setFilter(getTextureFilter(index));
		}
		
		textures[index] = newTex;
	}
	
	return textures[index];
}

// Hack to deal with old PNG data.
static std::string convertToTGA(const std::string &fileName) {
	if(strEndsWith(".png", fileName)) {
		string converted = fileName.substr(0, fileName.size() - 4) + ".tga";
		
		out("warning") << "Converted filename " << fileName << " to " << converted << ". PNGs are currently unsupported." << endl;
		
		return converted;
	}
	
	return fileName;
}

int TextureManager::getTextureIndexByName(const std::string &texName) {
	
	// No name? We can default to nothing. That's okay.
	if(!texName.size()) return -1;
	
	// Fix the filename for consistency.
	string fixedName = FileSystem::fixFileName(texName);
	
	fixedName = convertToTGA(fixedName);
	
	if(!textureIndicesByName[fixedName]) {
		
		// Doesn't exist yet. Make it.
		Texture *newTex = createTexture(fixedName);
		textures.push_back(newTex);
		textureIndicesByName[fixedName] = textures.size();
		textureNames.push_back(fixedName);
		
		TextureInfo *texInfo = new TextureInfo();
		texInfo->filter = true;
		textureInfos.push_back(texInfo);
	}
	
	return textureIndicesByName[fixedName] - 1;
}

std::string TextureManager::getTextureNameByIndex(int index) {
	assert(index < int(textureNames.size()));

	// Invalid textures are valid input. They just get nothing back.
	if(index == -1) {
		return "";
	}
	
	return textureNames[index];
}

void TextureManager::backgroundLoad(const std::string &texName) {
	// TODO: Remove this?
	
	// This will start the background loading and assign an index.
	getTextureIndexByName(texName);
}

void TextureManager::cleanOldTextures(unsigned int maxAge) {
	// TODO: If textures.size() starts getting really huge, we might
	//   start to have a performance problem with this. Maybe we should
	//   just keep a list of textures that still aren't NULL in this
	//   list?
	for(unsigned int i = 0; i < textures.size(); i++) {
		if(textures[i]) {
			if(!textures[i]->getDontPrune()) {
				if(textures[i]->getTimeSinceAccess() > maxAge) {
					delete textures[i];
					textures[i] = NULL;
				} else {
					textures[i]->incrementAge();
				}
			}
		}
	}
}

// Just so it's on the record. I think singletons are a stupid, fancy
// name for globals.
static TextureManager mainTextureManager;

TextureManager *TextureManager::get(void) {
	return &mainTextureManager;
}

void TextureManager::setTextureFilter(int index, bool filter) {
	assert(index < (int)textureInfos.size() && index >= 0);
	
	textureInfos[index]->filter = filter;
	
	if(textures[index]) {
		textures[index]->setFilter(filter);
	}
}

bool TextureManager::getTextureFilter(int index) {
	assert(index < (int)textureInfos.size() && index >= 0);
	
	return textureInfos[index]->filter;
}

void TextureManager::addTextureToQueue(Texture *tex) {
    
    loadQueueMutex->lock();
    loadQueue.push_back(tex);
    loadQueueMutex->unlock();

}

Texture *TextureManager::createTexture(const std::string &fileName) {
	
	Texture *tex = NULL;

    vector<string> atlasTextures;
    stringTokenize(fileName, ":", atlasTextures, true);
	
    if(atlasTextures.size() > 1) {
        
        AtlasTexture *atlasTex = new AtlasTexture();

        tex = atlasTex;

        vector<string> fixedNames;
        
        fixedNames.push_back(atlasTextures[0]);
        
        //string dirName = FileSystem::getParentName(atlasTextures[0]) + "/";
        
        for(unsigned int i = 1; i < atlasTextures.size(); i++) {
            fixedNames.push_back(/*dirName + */ atlasTextures[i]);
        }

        atlasTex->startMultipleLoad(fixedNames);

    } else {

        if(strEndsWith(".tga", fileName)) {
	
            tex = new Texture();
		
        } else if(strEndsWith(".metatex", fileName)) {
		
            tex = new MetaTexture();
		
        } else {
	
            // I don't know what it wanted. vOv
            out("error") << "Unknown texture format on " << fileName << endl;
	
        }
	
        if(tex) {
            tex->startLoad(fileName);
        }
    }

    if(tex) {
        addTextureToQueue(tex);
    }

	// TODO: Return generic bad texture?
	return tex;
	
}

void TextureManager::clearAllTextures(void) {
	
	// TODO: Respect dontPrune thinger?
	for(unsigned int i = 0; i < textures.size(); i++) {
		delete textures[i];
		textures[i] = NULL;
	}
	
}

void TextureManager::setShadersEnabled(bool enable) {
	
	if(enable && !getShadersDetected()) {
		out("warning") << "Forcing shaders on, even though they may be unsupported." << endl;
	}
	
	if(!enable && getShadersDetected()) {
		out("warning") << "Disabling shaders, even though we support them." << endl;
	}

	shadersEnabled = enable;
}

bool TextureManager::getShadersEnabled(void) {
	
	return shadersEnabled;
}

bool TextureManager::getShadersDetected(void) {
	
	// I think this is a complete list of the stuff we need.
	return 
		GL_ARB_fragment_program &&
		GL_ARB_fragment_shader &&
		GL_ARB_multitexture &&
		GL_ARB_shader_objects &&
		GL_ARB_shading_language_100 &&
		GL_ARB_vertex_shader;
}

void TextureManager::scanTextureSizes(const std::string &path) {

    vector<string> nonDirList;
    vector<string> filteredList;
    vector<string> dirList;

    FileSystem::getNondirectories(path, nonDirList);
    FileSystem::getSubdirectories(path, dirList);

    FileSystem::filterFileListByType("tga", nonDirList, filteredList);
    for(unsigned int i = 0; i < filteredList.size(); i++) {
        getTextureSize(path + "/" + filteredList[i]);
    }

    FileSystem::filterFileListByType("metatex", nonDirList, filteredList);
    for(unsigned int i = 0; i < filteredList.size(); i++) {
        getTextureSize(path + "/" + filteredList[i]);
    }

    for(unsigned int i = 0; i < dirList.size(); i++) {
        scanTextureSizes(path + "/" + dirList[i]);
    }
}

Vector2D TextureManager::getTextureSize(const std::string &path) {

    // Check to see if it's already in the cache.
    if(cachedTextureSizes.count(path)) {
        return cachedTextureSizes[path];
    }

    // Nope. TGA? Load the header and have a look.
    if(strEndsWith(".tga", path)) {

        out("system") << "Scanning TGA: " << path << endl;

        char *tgaHeader = FileSystem::loadFileHead(path, 18);

        if(tgaHeader) {

            int width;
            int height;

            loadTGAHeaderGetSize(tgaHeader, &width, &height);
            cachedTextureSizes[path] = Vector2D(width, height);

            delete[] tgaHeader;

            return Vector2D(width, height);

        } else {

            cachedTextureSizes[path] = Vector2D(0, 0);
            return Vector2D(0, 0);

        }

    }

    // One of the weird textures. Looks like we just have to load
    // it. *sigh*

    out("system") << "Scanning unknown texture type: " << path << endl;

    Texture *tex = getTextureByName(path);
    if(tex) {

        tex->waitForLoad(); // We don't want to do this in the main
                            // thread. Hope it never comes to this.

        cachedTextureSizes[path] = Vector2D(tex->getOrigWidth(), tex->getOrigHeight());

        return Vector2D(tex->getOrigWidth(), tex->getOrigHeight());
    }

    out("error") << "Could not scan texture: " << path << endl;

    cachedTextureSizes[path] = Vector2D(0, 0);
    return Vector2D(0, 0);

}
