/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / 3D rendering plugin
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *   
 *  GPAC 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */


#include "render3d_nodes.h"
#include "gl_inc.h"


/*tx flags*/
enum
{
	/*these 4 are exclusives*/
	TX_MUST_SCALE = (1<<1),
	TX_IS_POW2 = (1<<2),
	TX_IS_RECT = (1<<3),
	TX_EMULE_POW2 = (1<<4),
	/*signal video data must be sent to hw*/
	TX_NEEDS_HW_LOAD = (1<<5),
};

typedef struct
{
	/*opengl texture id*/
	u32 id;
	/*tx type*/
	u32 tx_flags;
	u32 blend_mode;
	Bool first_load;
	u32 rescale_width, rescale_height;
	char *scale_data;
	char *conv_data;
	Fixed conv_wscale, conv_hscale;
	u32 conv_format, conv_w, conv_h;

	/*gl textures vars (gl_type: 2D texture or rectangle (NV ext) )*/
	u32 nb_comp, gl_format, gl_type;
} GLTexture;

M4Err tx_allocate(TextureHandler *txh)
{
	GLTexture *gltx;
	if (txh->hwtx) return M4OK;
	gltx = malloc(sizeof(GLTexture));
	if (!gltx) return M4OutOfMem;
	txh->hwtx = gltx;
	memset(gltx, 0, sizeof(GLTexture));
	glGenTextures(1, &gltx->id);
	if (!gltx->id) return M4IOErr;
	gltx->first_load = 1;
	return M4OK;
}

void tx_delete(TextureHandler *txh)
{
	if (txh->hwtx) {
		GLTexture *gltx = (GLTexture *) txh->hwtx;
		if (gltx->id) glDeleteTextures(1, &gltx->id);
		if (gltx->scale_data) free(gltx->scale_data);
		if (gltx->conv_data) free(gltx->conv_data);
		free(gltx);
		txh->hwtx = NULL;
	}
}

void tx_set_blend_mode(TextureHandler *txh, u32 mode)
{
	if (txh->hwtx) {
		GLTexture *gltx = (GLTexture *) txh->hwtx;
		if (gltx) gltx->blend_mode = mode;
	}
}

void tx_bind(TextureHandler *txh)
{
	GLTexture *gltx = (GLTexture *) txh->hwtx;
	if (!gltx->id || !gltx->gl_type) return;
	glEnable(gltx->gl_type);

	switch (gltx->blend_mode) {
	case TX_BLEND:
		if (txh->transparent) glEnable(GL_BLEND);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
		break;
	case TX_REPLACE:
		if (txh->transparent) glEnable(GL_BLEND);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
		break;
	case TX_MODULATE:
		if (txh->transparent) glEnable(GL_BLEND);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		break;
	case TX_DECAL:
	default:
		if ((gltx->gl_format==GL_LUMINANCE) || (gltx->gl_format==GL_LUMINANCE_ALPHA)) {
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		} else {
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
		}
		break;
	}
	glBindTexture(gltx->gl_type, gltx->id);
}

void tx_disable(TextureHandler *txh)
{
	GLTexture *gltx;
	if (txh && txh->hwtx) {
		gltx = (GLTexture *) txh->hwtx;
		glDisable(gltx->gl_type);
		if (txh->transparent) glDisable(GL_BLEND);
	}
}


u32 get_pow2(u32 s)
{
	u32 i;
	u32 res = s;
    u32 sizes[] = { 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048 };
    u32 nSizes = sizeof(sizes) / sizeof(int);
    for (i = 0; i < nSizes; ++i) { if (res <= sizes[i]) { res = sizes[i]; break; } }
	return res;
}

Bool tx_can_use_rect_ext(Render3D *sr, TextureHandler *txh)
{
	u32 i, count;
	if (!txh->compositor->hw_caps.rect_texture) return 0;
	if (!sr->disable_rect_ext) return 1;
	/*this happens ONLY with text texturing*/
	if (!txh->owner) return 0;

	count = Node_GetParentCount(txh->owner);

	/*background 2D can use RECT ext without pb*/
	if (Node_GetTag(txh->owner)==TAG_MPEG4_Background2D) return 1;
	/*if a bitmap is using the texture force using RECT*/
	for (i=0; i<count; i++) {
		SFNode *n = Node_GetParent(txh->owner, 0);
		if (Node_GetTag(n)==TAG_MPEG4_Appearance) {
			count = Node_GetParentCount(n);
			for (i=0; i<count; i++) {
				M_Shape *s = (M_Shape *) Node_GetParent(n, 0);
				if (s->geometry && (Node_GetTag((SFNode *)s)==TAG_MPEG4_Shape) && (Node_GetTag(s->geometry)==TAG_MPEG4_Bitmap)) return 1;
			}
		}
	}
	return 0;
}

Bool tx_setup_format(TextureHandler *txh)
{
	Bool is_pow2, use_rect;
	Render3D *sr = (Render3D *)txh->compositor->visual_renderer->user_priv;
	GLTexture *gltx = (GLTexture *) txh->hwtx;

	/*first setup, this will force recompute bounds in case used with bitmap - we could refine and only 
	invalidate for bitmaps only*/
	if (txh->owner && (!gltx->rescale_width || !gltx->rescale_height)) 
		Node_SetDirty(txh->owner, 1);

	gltx->rescale_width = get_pow2(txh->width);
	gltx->rescale_height = get_pow2(txh->height);

	is_pow2 = ((gltx->rescale_width==txh->width) && (gltx->rescale_height==txh->height)) ? 1 : 0;
	gltx->tx_flags = TX_IS_POW2;
	gltx->gl_type = GL_TEXTURE_2D;
	use_rect = tx_can_use_rect_ext(sr, txh);
	if (!is_pow2 && use_rect) {
		gltx->gl_type = GL_TEXTURE_RECTANGLE_EXT;
		gltx->tx_flags = TX_IS_RECT;
	}
	if (!use_rect && !txh->compositor->hw_caps.npot_texture && !is_pow2) gltx->tx_flags = TX_MUST_SCALE;

	gltx->nb_comp = gltx->gl_format = 0;
	switch (txh->pixelformat) {
	case M4PF_GREYSCALE:
		gltx->gl_format = GL_LUMINANCE;
		gltx->nb_comp = 1;
		gltx->gl_type = GL_TEXTURE_2D;
		if (!is_pow2) gltx->tx_flags = TX_MUST_SCALE;
		break;
	case M4PF_ALPHAGREY:
		gltx->gl_format = GL_LUMINANCE_ALPHA;
		gltx->nb_comp = 2;
		gltx->gl_type = GL_TEXTURE_2D;
		if (!is_pow2) gltx->tx_flags = TX_MUST_SCALE;
		break;
	case M4PF_RGB_24:
		gltx->gl_format = GL_RGB;
		gltx->nb_comp = 3;
		break;
	case M4PF_RGB_32:
	case M4PF_RGBA:
		gltx->gl_format = GL_RGBA;
		gltx->nb_comp = 4;
		break;
	case M4PF_ARGB:
		if (!txh->compositor->hw_caps.bgra_texture) return 0;
		gltx->gl_format = GL_BGRA_EXT;
		gltx->nb_comp = 4;
		break;
	case M4PF_YV12:
		if (!use_rect && sr->emul_pow2) gltx->tx_flags = TX_EMULE_POW2;
		gltx->gl_format = GL_RGB;
		gltx->nb_comp = 3;
		break;
	default:
		return 0;
	}
	/*note we don't free the data if existing, since this only happen when re-setting up after context loss (same size)*/
	if ((gltx->tx_flags == TX_MUST_SCALE) & !gltx->scale_data) gltx->scale_data = malloc(sizeof(char) * gltx->nb_comp*gltx->rescale_width*gltx->rescale_height);

	glEnable(gltx->gl_type);
	glBindTexture(gltx->gl_type, gltx->id);

	glTexParameteri(gltx->gl_type, GL_TEXTURE_WRAP_S, (txh->flags & TX_REPEAT_S) ? GL_REPEAT : GL_CLAMP);
	glTexParameteri(gltx->gl_type, GL_TEXTURE_WRAP_T, (txh->flags & TX_REPEAT_T) ? GL_REPEAT : GL_CLAMP);
	if (gltx->gl_type == GL_TEXTURE_2D) {
		glTexParameteri(gltx->gl_type, GL_TEXTURE_MAG_FILTER, txh->compositor->high_speed ? GL_NEAREST : GL_LINEAR);
		glTexParameteri(gltx->gl_type, GL_TEXTURE_MIN_FILTER, txh->compositor->high_speed ? GL_NEAREST : GL_LINEAR);
	} else {
		glTexParameteri(gltx->gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(gltx->gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glDisable(gltx->gl_type);
	gltx->first_load = 1;
	return 1;
}

char *tx_get_data(TextureHandler *txh, u32 *pix_format)
{
	GLTexture *gltx = (GLTexture *) txh->hwtx;
	char *data = gltx->conv_data;
	*pix_format = gltx->conv_format;
	if (*pix_format == txh->pixelformat) data = txh->data;
	return data;
}

u32 get_next_pow2(u32 s)
{
	u32 i;
	u32 res = s;
    u32 sizes[] = { 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 };
    u32 nSizes = sizeof(sizes) / sizeof(int);
    for (i = 0; i < nSizes; ++i) { if (res <= sizes[i]) { res = sizes[i]; break; } }
	return res;
}

/*note about conversion: we consider that a texture without a stream attached is generated by the renderer
hence is never flipped. Otherwise all textures attached to stream are flipped in order to match uv coords*/
Bool tx_convert(TextureHandler *txh)
{
	GLTexture *gltx = (GLTexture *) txh->hwtx;

	u32 out_stride;
	unsigned char *pY, *pU, *pV;
	switch (txh->pixelformat) {
	case M4PF_ARGB:
		if (!txh->compositor->hw_caps.bgra_texture) return 0;
	case M4PF_GREYSCALE:
	case M4PF_ALPHAGREY:
	case M4PF_RGB_24:
	case M4PF_RGB_32:
	case M4PF_RGBA:
		if (txh->stream && !(txh->stream->mo_flags & MO_IS_FLIP) ) {
			u32 i, hy;
			char *tmp;
			/*flip image*/
			tmp = malloc(sizeof(char)*txh->stride);
			hy = txh->height/2;
			for (i=0; i<hy; i++) {
				memcpy(tmp, txh->data + i*txh->stride, txh->stride);
				memcpy(txh->data + i*txh->stride, txh->data + (txh->height - 1 - i) * txh->stride, txh->stride);
				memcpy(txh->data + (txh->height - 1 - i) * txh->stride, tmp, txh->stride);
			}
			free(tmp);
			txh->stream->mo_flags |= MO_IS_FLIP;
		}
		gltx->conv_format = txh->pixelformat;
		gltx->tx_flags |= TX_NEEDS_HW_LOAD;
		return 1;
	case M4PF_YV12:
		break;
	default:
		gltx->conv_format = 0;
		return 0;
	}
	if (!gltx->conv_data) {
		if (gltx->tx_flags == TX_EMULE_POW2) {
			/*convert video to a po of 2 WITHOUT SCALING VIDEO*/
			gltx->conv_w = get_next_pow2(txh->width);
			gltx->conv_h = get_next_pow2(txh->height);
			gltx->conv_data = malloc(sizeof(char) * 3 * gltx->conv_w * gltx->conv_h);
			memset(gltx->conv_data , 0, sizeof(char) * 3 * gltx->conv_w * gltx->conv_h);
			gltx->conv_wscale = INT2FIX(txh->width) / gltx->conv_w;
			gltx->conv_hscale = INT2FIX(txh->height) / gltx->conv_h;
		} else {
			gltx->conv_data = malloc(sizeof(char) * 3 * txh->width * txh->height);
		}
	}
	out_stride = 3 * ((gltx->tx_flags & TX_EMULE_POW2) ? gltx->conv_w : txh->width);

	pY = txh->data;
	pU = pY + txh->width*txh->height;
	pV = pY + 5*txh->width*txh->height/4;
	m4_yuv_to_rgb_24_flip(gltx->conv_data, out_stride, pY, pU, pV, txh->stride, txh->stride/2, txh->width, txh->height);
	gltx->conv_format = M4PF_RGB_24;
	gltx->tx_flags |= TX_NEEDS_HW_LOAD;
	return 1;
}

Bool tx_set_image(TextureHandler *txh, Bool generate_mipmaps)
{
	char *data;
	u32 pixel_format, w, h;
	GLTexture *gltx = (GLTexture *) txh->hwtx;
	if (! (gltx->tx_flags & TX_NEEDS_HW_LOAD) ) return 1;
	if (!gltx->gl_type) return 0;

	/*in case the ID has been lost, resetup*/
	if (!gltx->id) {
		glGenTextures(1, &gltx->id);
		tx_setup_format(txh);
	}
	tx_bind(txh);

	gltx->tx_flags &= ~TX_NEEDS_HW_LOAD;
	data = tx_get_data(txh, &pixel_format);
	if (!data) return 0;
	if (gltx->tx_flags & TX_EMULE_POW2) {
		w = gltx->conv_w;
		h = gltx->conv_h;
	} else {
		w = txh->width;
		h = txh->height;
	}

	/*pow2 texture or hardware support*/
	if (! (gltx->tx_flags & TX_MUST_SCALE) ) {
		if (gltx->first_load) {
			gltx->first_load = 0;
			glTexImage2D(gltx->gl_type, 0, gltx->nb_comp, w, h, 0, gltx->gl_format, GL_UNSIGNED_BYTE, (unsigned char *) data);
		} else {
			glTexSubImage2D(gltx->gl_type, 0, 0, 0, w, h, gltx->gl_format, GL_UNSIGNED_BYTE, (unsigned char *) data);
		}
	} else {
		gluScaleImage(gltx->gl_format, txh->width, txh->height, GL_UNSIGNED_BYTE, data, gltx->rescale_width, gltx->rescale_height, GL_UNSIGNED_BYTE, gltx->scale_data);
		if (gltx->first_load) {
			gltx->first_load = 0;
			glTexImage2D(gltx->gl_type, 0, gltx->nb_comp, gltx->rescale_width, gltx->rescale_height, 0, gltx->gl_format, GL_UNSIGNED_BYTE, gltx->scale_data);
		} else {
			glTexSubImage2D(gltx->gl_type, 0, 0, 0, gltx->rescale_width, gltx->rescale_height, gltx->gl_format, GL_UNSIGNED_BYTE, gltx->scale_data);
		}
	}
	return 1;
}

void tx_copy_to_texture(TextureHandler *txh)
{
	GLTexture *gltx = (GLTexture *) txh->hwtx;
	tx_bind(txh);
	glCopyTexImage2D(gltx->gl_type, 0, gltx->gl_format, 0, 0, txh->width, txh->height, 0);
	glDisable(gltx->gl_type);
}


Bool tx_get_transform(TextureHandler *txh, SFNode *tx_transform, M4Matrix *mx)
{
	M4Matrix tmp;
	Bool ret = 0;
	GLTexture *gltx = (GLTexture *)txh->hwtx;
	m4_mx_init(*mx);

	/*WATCHOUT: this may be GL specific (GL_TEXTURE-RECTANGLE coords are w, h not 1.0, 1.0)*/
	if (gltx->tx_flags & TX_IS_RECT) {
		m4_mx_add_scale(mx, INT2FIX(txh->width), INT2FIX(txh->height), FIX_ONE);
		/*disable any texture transforms when using RECT textures (no repeat) ??*/
		/*tx_transform = NULL;*/
		ret = 1;
	} 
	else if (gltx->tx_flags & TX_EMULE_POW2) {
		m4_mx_add_scale(mx, gltx->conv_wscale, gltx->conv_hscale, FIX_ONE);
		/*disable any texture transforms when emulating pow2 textures*/
		tx_transform = NULL;
		ret = 1;
	}

	if (tx_transform) {
		switch (Node_GetTag(tx_transform)) {
		case TAG_MPEG4_TextureTransform:
		case TAG_X3D_TextureTransform:
		{
			M4Matrix2D mat;
			M_TextureTransform *tt = (M_TextureTransform *)tx_transform;
			m4_mx2d_init(mat);
			m4_mx2d_add_translation(&mat, -tt->center.x, -tt->center.y);
			m4_mx2d_add_scale(&mat, tt->scale.x, tt->scale.y);
			if (fabs(tt->rotation) > M4_EPSILON_FLOAT) m4_mx2d_add_rotation(&mat, 0, 0, tt->rotation);
			m4_mx2d_add_translation(&mat, tt->translation.x + tt->center.x, tt->translation.y + tt->center.y);
			if (ret) {
				m4_mx_from_mx2d(&tmp, &mat);
				m4_mx_add_matrix(mx, &tmp);
			} else {
				m4_mx_from_mx2d(mx, &mat);
			}
			ret = 1;
		}
			break;
		case TAG_MPEG4_TransformMatrix2D:
		{
			M_TransformMatrix2D *tm = (M_TransformMatrix2D *)tx_transform;
			memset(tmp.m, 0, sizeof(Fixed)*16);
			tmp.m[0] = tm->mxx; tmp.m[4] = tm->mxy; /*0*/ tmp.m[12] = tm->tx;
			tmp.m[1] = tm->myx; tmp.m[5] = tm->myy; /*0*/ tmp.m[13] = tm->ty;
			/*rest is all 0 excep both diag*/
			tmp.m[10] = tmp.m[15] = FIX_ONE;

			if (ret) {
				m4_mx_add_matrix(mx, &tmp);
			} else {
				m4_mx_copy(*mx, tmp);
			}
			ret = 1;
		}
			break;
		default:
			break;
		}
	}
	return ret;
}

Bool tx_enable(TextureHandler *txh, SFNode *tx_transform)
{
	M4Matrix mx;
	Render3D *sr;
	GLTexture *gltx;
	if (!txh || !txh->hwtx) return 0;
	tx_set_image(txh, 0);
	sr = (Render3D *)txh->compositor->visual_renderer->user_priv;
	gltx = (GLTexture *)txh->hwtx;

	VS3D_SetMatrixMode(sr->surface, MAT_TEXTURE);
	if (tx_get_transform(txh, tx_transform, &mx)) 
		VS3D_LoadMatrix(sr->surface, mx.m);
	else
		VS3D_ResetMatrix(sr->surface);
		
	VS3D_SetMatrixMode(sr->surface, MAT_MODELVIEW);

	tx_bind(txh);
	return 1;
}

M4Err R3D_SetTextureData(TextureHandler *hdl)
{
	GLTexture *gltx = (GLTexture *)hdl->hwtx;

	/*init if needed*/
	if (!gltx->gl_type) {
		if (!tx_setup_format(hdl)) return M4NotSupported;
	}
	/*convert image - don't push it to HW until used*/
	tx_convert(hdl);
	return M4OK;
}

void R3D_TextureHWReset(TextureHandler *hdl)
{
	GLTexture *gltx = (GLTexture *)hdl->hwtx;

	if (gltx->id) {
		glDeleteTextures(1, &gltx->id);
		gltx->id = 0;
	}
	gltx->tx_flags |= TX_NEEDS_HW_LOAD;
}

Bool tx_needs_reload(TextureHandler *hdl)
{
	return ( ((GLTexture *)hdl->hwtx)->tx_flags & TX_NEEDS_HW_LOAD ) ? 1 : 0;
}

