/*********************************************************\
		 __      __           __  __
		/ /     / / _____    / / / / ______   __     __
	   / /     / / /___  \  / / / / / ___  | / /    / /
	  / /_____/ / _____| / / / / / / /__/ / / /    / /
	 / ______  / / ___  / / / / / / _____/ / /    / /
	/ /     / / / /__/ / / / / / / |____  / |____/ /
   /_/     /_/ |______/ /_/ /_/  \_____/  \_____  /
										 ______/ /
			High Level Game Framework   /_______/

  ------------------------------------------------------

  Copyright (c) 2007 - Rodrigo Braz Monteiro.
  This file is subject to the terms of license.txt.

\*********************************************************/

#pragma once

#include "texture.h"
#include "draw.h"
#include "exception.h"
#include "resource_locator.h"
#include <SDL.h>
#include <SDL_opengl.h>
#include <SDL_image.h>

using namespace Halley;

ConcreteTexture::ConcreteTexture(String filename)
: Texture(filename), id(0)
{
	LoadTexture(filename);
}

ConcreteTexture::~ConcreteTexture()
{
	if (id != 0) {
		DestroyTexture();
	}
}

template<typename T>
static T nextPowerOf2(T val)
{
	val--;
	val = (val >> 1) | val;
	val = (val >> 2) | val;
	val = (val >> 4) | val;
	val = (val >> 8) | val;
	val = (val >> 16) | val;
	return val+1;
}

void ConcreteTexture::LoadTexture(String filename)
{
	// Load texture
	auto texData = ResourceLocator::get(filename);
	SDL_RWops* ops = texData->getRWOps();
	shared_ptr<SDL_Surface> surface (IMG_Load_RW(ops, 1), SDL_FreeSurface);
	//SDL_Surface* surface = IMG_Load_RW(ops, 1);
	if (!surface) {
		throw Exception("Unable to load texture image: "+filename);
	}

	// Gather texture data
	GLenum texture_format = GL_RGBA;
	int nOfColors = surface->format->BytesPerPixel;
	bool rgb = surface->format->Rmask == 0x000000ff;
	if (nOfColors == 4) {
		texture_format = rgb ? GL_RGBA : GL_BGRA;
	} else if (nOfColors == 3) {
		texture_format = rgb ? GL_RGB : GL_BGR;
	} else {
		throw Exception("Failure to load texture.");
	}

	// Set texture size
	size_t w = surface->w;
	size_t h = surface->h;
	size_t aw = nextPowerOf2(w);
	size_t ah = nextPowerOf2(h);
	tw = float(w)/float(aw);
	th = float(h)/float(ah);
	SetW((Uint16)w);
	SetH((Uint16)h);
	
	// Analyze alpha channel
	alphaType = GetSurfaceAlphaType(surface.get());

	// Upload to OpenGL
	glGenTextures(1, &id);
	BindTexture(id);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	 
	//glTexImage2D(GL_TEXTURE_2D, 0, nOfColors, w, h, 0, texture_format, GL_UNSIGNED_BYTE, surface->pixels);
	glTexImage2D(GL_TEXTURE_2D, 0, nOfColors, aw, ah, 0, texture_format, GL_UNSIGNED_BYTE, nullptr);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, texture_format, GL_UNSIGNED_BYTE, surface->pixels);
}

Texture::AlphaType ConcreteTexture::GetSurfaceAlphaType(const SDL_Surface *surface)
{
	AlphaType type = ALPHA_OPAQUE;

	size_t h = surface->h;
	size_t w = surface->w;
	size_t pitch = surface->pitch;
	int nOfColors = surface->format->BytesPerPixel;
	if (nOfColors == 4) {
		Uint32 aMask = surface->format->Amask;
		Uint32 aShift = surface->format->Ashift;
		for (size_t y=0; y<h; y++) {
			unsigned int* pixels = (unsigned int*)((char*)surface->pixels + y*pitch);
			for (size_t x=0; x<w; x++) {
				unsigned int pixel = pixels[x];
				unsigned int alpha = (pixel & aMask) >> aShift;
				if (alpha < 255) {
					if (alpha > 0) {
						return ALPHA_BLEND;
					} else {
						type = ALPHA_TEST;
					}
				}
			}
		}
	}
	return type;
}

void ConcreteTexture::DestroyTexture()
{
	glDeleteTextures(1, &id);
	id = 0;
}

void ConcreteTexture::Bind()
{
	BindTexture(id);
}

void ConcreteTexture::GetTexCoords(float* buffer) const
{
	float vals[] = { 0, th, tw, th, tw, 0, 0, 0 };
	//static float vals[] = { 0, 1, 1, 1, 1, 0, 0, 0 };
	memcpy(buffer, vals, sizeof(float)*8);
}

void SubTexture::Bind()
{
	parent->Bind();
}

SubTexture::SubTexture(spTexture _parent, Rect4i _area)
: Texture(_parent->GetName()+"_sub"), parent(_parent), area(_area)
{
	float w = _parent->GetW();
	float h = _parent->GetH();
	SetW((Uint16)w);
	SetH((Uint16)h);

	float tx1 = area.GetP1().x / w;
	float ty1 = area.GetP1().y / h;
	float tx2 = area.GetP2().x / w;
	float ty2 = area.GetP2().y / h;

	float vals[] = { tx1, ty2, tx2, ty2, tx2, ty1, tx1, ty1 };
	memcpy(texCoords.c_array(), vals, sizeof(float)*8);
}

// TODO: optimize this
void Texture::GetTexCoords(float* buffer) const
{
	static float vals[] = { 0, 1, 1, 1, 1, 0, 0, 0 };
	memcpy(buffer, vals, sizeof(float)*8);
}

// TODO: optimize this
void SubTexture::GetTexCoords(float* buffer) const
{
	memcpy(buffer, texCoords.data(), sizeof(float)*8);
}

Texture::AlphaType SubTexture::GetAlphaType()
{
	return parent->GetAlphaType();
}
