#include "Textures.h"

#include "TextureData.h"
#include "ptexture/DynamicTexture.h"
#include "../Options.h"
#include "../../platform/time.h"
#include "../../AppPlatform.h"
#include "../../util/StringUtils.h"

/*static*/ int  Textures::textureChanges = 0;
/*static*/ bool Textures::MIPMAP = false;
/*static*/ const TextureId Textures::InvalidId = -1;

Textures::Textures( Options* options_, AppPlatform* platform_ )
:	clamp(false),
	blur(false),
	options(options_),
	platform(platform_),
	lastBoundTexture(Textures::InvalidId)
{
}

Textures::~Textures()
{
	clear();

	for (unsigned int i = 0; i < dynamicTextures.size(); ++i)
		delete dynamicTextures[i];
}

void Textures::clear()
{
	for (TextureMap::iterator it = idMap.begin(); it != idMap.end(); ++it) {
		if (it->second != Textures::InvalidId)
			glDeleteTextures(1, &it->second);
	}
	for (TextureImageMap::iterator it = loadedImages.begin(); it != loadedImages.end(); ++it) {
		if (!(it->second).memoryHandledExternally)
			delete[] (it->second).data;
	}
	idMap.clear();
	loadedImages.clear();

	lastBoundTexture = Textures::InvalidId;
}

TextureId Textures::loadAndBindTexture( const std::string& resourceName )
{
	//static Stopwatch t;

	//t.start();
	TextureId id = loadTexture(resourceName);
	//t.stop();
	if (id != Textures::InvalidId)
		bind(id);

	//t.printEvery(1000);

	return id;
}

TextureId Textures::loadTexture( const std::string& resourceName, bool inTextureFolder /* = true */ )
{
	TextureMap::iterator it = idMap.find(resourceName);
	if (it != idMap.end())
		return it->second;

	bool isUrl = Util::startsWith(resourceName, "http://") || Util::startsWith(resourceName, "https://");
	TextureData texdata = platform->loadTexture(resourceName, isUrl ? false : inTextureFolder);
	if (texdata.data)
		return assignTexture(resourceName, texdata);
    else if (texdata.identifier != InvalidId) {
        //LOGI("Adding id: %d for %s\n", texdata.identifier, resourceName.c_str());
		idMap.insert(std::make_pair(resourceName, texdata.identifier));
    }
	else {
		idMap.insert(std::make_pair(resourceName, Textures::InvalidId));
		//loadedImages.insert(std::make_pair(InvalidId, texdata));
	}
	return Textures::InvalidId;
}

TextureId Textures::assignTexture( const std::string& resourceName, const TextureData& img )
{
	TextureId id;
	glGenTextures(1, &id);

	bind(id);

	if (MIPMAP) {
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	} else {
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}
	if (blur) {
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}

	if (clamp) {
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	} else {
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}

    switch (img.format)
    {
        case TEXF_COMPRESSED_PVRTC_4444:
        case TEXF_COMPRESSED_PVRTC_565:
        case TEXF_COMPRESSED_PVRTC_5551:
        {
#if defined(__APPLE__)
            int fmt = img.transparent? GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG : GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
            glCompressedTexImage2D(GL_TEXTURE_2D, 0, fmt, img.w, img.h, 0, img.numBytes, img.data);
#endif
            break;
        }

        default:
            const GLint mode = img.transparent? GL_RGBA : GL_RGB;

            if (img.format == TEXF_UNCOMPRESSED_565) {
                glTexImage2D2(GL_TEXTURE_2D, 0, mode, img.w, img.h, 0, mode, GL_UNSIGNED_SHORT_5_6_5, img.data);
            }
            else if (img.format == TEXF_UNCOMPRESSED_4444) {
                glTexImage2D2(GL_TEXTURE_2D, 0, mode, img.w, img.h, 0, mode, GL_UNSIGNED_SHORT_4_4_4_4, img.data);
            }
            else if (img.format == TEXF_UNCOMPRESSED_5551) {
                glTexImage2D2(GL_TEXTURE_2D, 0, mode, img.w, img.h, 0, mode, GL_UNSIGNED_SHORT_5_5_5_1, img.data);
            }
            else {
                glTexImage2D2(GL_TEXTURE_2D, 0, mode, img.w, img.h, 0, mode, GL_UNSIGNED_BYTE, img.data);
            }
            break;
    }

    //LOGI("Adding id: %d to map\n", id);
	idMap.insert(std::make_pair(resourceName, id));
	loadedImages.insert(std::make_pair(id, img));

	return id;
}

const TextureData* Textures::getTemporaryTextureData( TextureId id )
{
	TextureImageMap::iterator it = loadedImages.find(id);
	if (it == loadedImages.end())
		return NULL;

	return &it->second;
}

void Textures::tick(bool uploadToGraphicsCard)
{
	for (unsigned int i = 0; i < dynamicTextures.size(); ++i ) {
		DynamicTexture* tex = dynamicTextures[i];
		tex->tick();

        if (uploadToGraphicsCard) {
            tex->bindTexture(this);
		    for (int xx = 0; xx < tex->replicate; xx++)
		    for (int yy = 0; yy < tex->replicate; yy++) {
			    glTexSubImage2D2(GL_TEXTURE_2D, 0, tex->tex % 16 * 16 + xx * 16,
				    tex->tex / 16 * 16 + yy * 16, 16, 16,
				    GL_RGBA, GL_UNSIGNED_BYTE, tex->pixels);
		    }
        }
	}
}

void Textures::addDynamicTexture( DynamicTexture* dynamicTexture )
{
	dynamicTextures.push_back(dynamicTexture);
	dynamicTexture->tick();
}

void Textures::reloadAll()
{
	//TexturePack skin = skins.selected;

	//for (int id : loadedImages.keySet()) {
	//    BufferedImage image = loadedImages.get(id);
	//    loadTexture(image, id);
	//}

	////for (HttpTexture httpTexture : httpTextures.values()) {
	////    httpTexture.isLoaded = false;
	////}

	//for (std::string name : idMap.keySet()) {
	//    try {
	//        BufferedImage image;
	//        if (name.startsWith("##")) {
	//            image = makeStrip(readImage(skin.getResource(name.substring(2))));
	//        } else if (name.startsWith("%clamp%")) {
	//            clamp = true;
	//            image = readImage(skin.getResource(name.substring(7)));
	//        } else if (name.startsWith("%blur%")) {
	//            blur = true;
	//            image = readImage(skin.getResource(name.substring(6)));
	//        } else {
	//            image = readImage(skin.getResource(name));
	//        }
	//        int id = idMap.get(name);
	//        loadTexture(image, id);
	//        blur = false;
	//        clamp = false;
	//    } catch (IOException e) {
	//        e.printStackTrace();
	//    }
	//}
}

int Textures::smoothBlend( int c0, int c1 )
{
	int a0 = (int) (((c0 & 0xff000000) >> 24)) & 0xff;
	int a1 = (int) (((c1 & 0xff000000) >> 24)) & 0xff;
	return ((a0 + a1) >> 1 << 24) + (((c0 & 0x00fefefe) + (c1 & 0x00fefefe)) >> 1);
}

int Textures::crispBlend( int c0, int c1 )
{
	int a0 = (int) (((c0 & 0xff000000) >> 24)) & 0xff;
	int a1 = (int) (((c1 & 0xff000000) >> 24)) & 0xff;

	int a = 255;
	if (a0 + a1 == 0) {
		a0 = 1;
		a1 = 1;
		a = 0;
	}

	int r0 = ((c0 >> 16) & 0xff) * a0;
	int g0 = ((c0 >> 8) & 0xff) * a0;
	int b0 = ((c0) & 0xff) * a0;

	int r1 = ((c1 >> 16) & 0xff) * a1;
	int g1 = ((c1 >> 8) & 0xff) * a1;
	int b1 = ((c1) & 0xff) * a1;

	int r = (r0 + r1) / (a0 + a1);
	int g = (g0 + g1) / (a0 + a1);
	int b = (b0 + b1) / (a0 + a1);

	return (a << 24) | (r << 16) | (g << 8) | b;
}

///*public*/ int loadHttpTexture(std::string url, std::string backup) {
//    HttpTexture texture = httpTextures.get(url);
//    if (texture != NULL) {
//        if (texture.loadedImage != NULL && !texture.isLoaded) {
//            if (texture.id < 0) {
//                texture.id = getTexture(texture.loadedImage);
//            } else {
//                loadTexture(texture.loadedImage, texture.id);
//            }
//            texture.isLoaded = true;
//        }
//    }
//    if (texture == NULL || texture.id < 0) {
//        if (backup == NULL) return -1;
//        return loadTexture(backup);
//    }
//    return texture.id;
//}

//HttpTexture addHttpTexture(std::string url, HttpTextureProcessor processor) {
//    HttpTexture texture = httpTextures.get(url);
//    if (texture == NULL) {
//        httpTextures.put(url, /*new*/ HttpTexture(url, processor));
//    } else {
//        texture.count++;
//    }
//    return texture;
//}

//void removeHttpTexture(std::string url) {
//    HttpTexture texture = httpTextures.get(url);
//    if (texture != NULL) {
//        texture.count--;
//        if (texture.count == 0) {
//            if (texture.id >= 0) releaseTexture(texture.id);
//            httpTextures.remove(url);
//        }
//    }
//}

//void tick() {
//	for (int i = 0; i < dynamicTextures.size(); i++) {
//		DynamicTexture dynamicTexture = dynamicTextures.get(i);
//		dynamicTexture.anaglyph3d = options.anaglyph3d;
//		dynamicTexture.tick();
//
//		pixels.clear();
//		pixels.put(dynamicTexture.pixels);
//		pixels.position(0).limit(dynamicTexture.pixels.length);
//
//		dynamicTexture.bindTexture(this);
//
//		for (int xx = 0; xx < dynamicTexture.replicate; xx++)
//			for (int yy = 0; yy < dynamicTexture.replicate; yy++) {
//
//				glTexSubImage2D2(GL_TEXTURE_2D, 0, dynamicTexture.tex % 16 * 16 + xx * 16, dynamicTexture.tex / 16 * 16 + yy * 16, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
//				if (MIPMAP) {
//					for (int level = 1; level <= 4; level++) {
//						int os = 16 >> (level - 1);
//						int s = 16 >> level;
//
//						for (int x = 0; x < s; x++)
//							for (int y = 0; y < s; y++) {
//								int c0 = pixels.getInt(((x * 2 + 0) + (y * 2 + 0) * os) * 4);
//								int c1 = pixels.getInt(((x * 2 + 1) + (y * 2 + 0) * os) * 4);
//								int c2 = pixels.getInt(((x * 2 + 1) + (y * 2 + 1) * os) * 4);
//								int c3 = pixels.getInt(((x * 2 + 0) + (y * 2 + 1) * os) * 4);
//								int col = smoothBlend(smoothBlend(c0, c1), smoothBlend(c2, c3));
//								pixels.putInt((x + y * s) * 4, col);
//							}
//							glTexSubImage2D2(GL_TEXTURE_2D, level, dynamicTexture.tex % 16 * s, dynamicTexture.tex / 16 * s, s, s, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
//					}
//				}
//			}
//	}
//
//	for (int i = 0; i < dynamicTextures.size(); i++) {
//		DynamicTexture dynamicTexture = dynamicTextures.get(i);
//
//		if (dynamicTexture.copyTo > 0) {
//			pixels.clear();
//			pixels.put(dynamicTexture.pixels);
//			pixels.position(0).limit(dynamicTexture.pixels.length);
//			glBindTexture2(GL_TEXTURE_2D, dynamicTexture.copyTo);
//			glTexSubImage2D2(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
//			if (MIPMAP) {
//				for (int level = 1; level <= 4; level++) {
//					int os = 16 >> (level - 1);
//					int s = 16 >> level;
//
//					for (int x = 0; x < s; x++)
//						for (int y = 0; y < s; y++) {
//							int c0 = pixels.getInt(((x * 2 + 0) + (y * 2 + 0) * os) * 4);
//							int c1 = pixels.getInt(((x * 2 + 1) + (y * 2 + 0) * os) * 4);
//							int c2 = pixels.getInt(((x * 2 + 1) + (y * 2 + 1) * os) * 4);
//							int c3 = pixels.getInt(((x * 2 + 0) + (y * 2 + 1) * os) * 4);
//							int col = smoothBlend(smoothBlend(c0, c1), smoothBlend(c2, c3));
//							pixels.putInt((x + y * s) * 4, col);
//						}
//						glTexSubImage2D2(GL_TEXTURE_2D, level, 0, 0, s, s, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
//				}
//			}
//		}
//	}
//}
//  void releaseTexture(int id) {
//      loadedImages.erase(id);
//      glDeleteTextures(1, (const GLuint*)&id);
//  }

