/*
** CTD.CPP
*/
#include "texturestack.h"
#include "tags.h"
#include <assert.h>
#include "jpeg.h"
#include "revengeapp.h"

#define WORK_SIZE		128
static unsigned char c_work_texture[WORK_SIZE*WORK_SIZE*3];

#define BIG_SIZE		1024
#define SAMPLE_SIZE		(BIG_SIZE/WORK_SIZE)
#define SAMPLE_BLOCKS	(SAMPLE_SIZE*SAMPLE_SIZE)
static unsigned char c_big_texture[BIG_SIZE*BIG_SIZE*3];

#define MAX_DETAIL_LEVELS	16
static int c_num_details=0;
static unsigned char* c_details[MAX_DETAIL_LEVELS];

void AddDetailTexture( int detail_num, struct stacked_texture* mask );
void SampleDetails( struct stack_set* ss );
void SampleDetailTexture( struct stacked_texture* st );
void Cleanup( void );

void Cleanup( void )
{
	for( int i = 0; i < c_num_details; i++ )
		free( c_details[i] );
	memset( c_details, 0, sizeof(c_details) );
	c_num_details=0;
}

void AddDetailTexture( int detail_num, struct stacked_texture* mask )
{
	int x, y;
	float alpha;
	float s_r, s_g, s_b;
	float d_r, d_g, d_b;
	unsigned char* mask_data;
	unsigned char* detail_data;
	unsigned char* wrk_data;
	
	assert( mask->width == WORK_SIZE && mask->height == WORK_SIZE );
	assert( mask->channels == 1 );
	assert( detail_num < c_num_details );
	
	if( mask->format == 1 )
	{
		mask_data = (unsigned char*)malloc(WORK_SIZE*WORK_SIZE*1);
		assert( mask_data );
		decompress_jpeg( mask_data, mask->compressed_data, 
			mask->compressed_length, WORK_SIZE, WORK_SIZE, 1, TRUE );
	}
	else
		mask_data = (unsigned char*)mask->compressed_data;
		
	wrk_data = c_work_texture;
	detail_data = c_details[detail_num];
	
	for( y = 0; y < WORK_SIZE; y++ )
	{
		for( x = 0; x < WORK_SIZE; x++ )
		{
			alpha = (*mask_data)*(1.0f/255.0f);
			s_r = 1-(float)(*(detail_data))*(1.0f/255.0f);
			s_g = 1-(float)(*(detail_data+1))*(1.0f/255.0f);
			s_b = 1-(float)(*(detail_data+2))*(1.0f/255.0f);
			d_r = (float)(*(wrk_data))*(1.0f/255.0f);
			d_g = (float)(*(wrk_data+1))*(1.0f/255.0f);
			d_b = (float)(*(wrk_data+2))*(1.0f/255.0f);
			
			// combine.
			s_r *= alpha;
			s_g *= alpha;
			s_b *= alpha;
			
			d_r *= (1-s_r);
			d_g *= (1-s_g);
			d_b *= (1-s_b);
			
			/*d_r = 1-s_r;
			d_g = 1-s_g;
			d_b = 1-s_b;*/
			
			// write.
			*(wrk_data) = (unsigned char)(d_r*255.0f);
			*(wrk_data+1) = (unsigned char)(d_g*255.0f);
			*(wrk_data+2) = (unsigned char)(d_b*255.0f);
			
			wrk_data += 3;
			detail_data += 3;
			mask_data++;
		}
	}
}

void SampleDetails( struct stack_set* ss )
{
	assert( ss );
	if( ss->used_stacks < 1 )
		return;
	
	c_num_details = 0;
	memset( c_details, 0, sizeof(c_details) );
	
	for( int i = 0; i < ss->used_stacks; i++ )
	{
		texture_stack_head* sh = &ss->stacks[i];
		assert( sh->textures );
		if( !c_details[c_num_details] )
			c_details[c_num_details] = (unsigned char*)malloc(WORK_SIZE*WORK_SIZE*3);
		assert( c_details[c_num_details] );
			
		SampleDetailTexture( sh->textures );		
	}
}

void SampleDetailTexture( struct stacked_texture* st )
{
	assert( st );
	assert( st->channels == 3 || st->channels == 4 );
	
	float step_x = (float)st->width/(float)BIG_SIZE;
	float step_y = (float)st->height/(float)BIG_SIZE;
	
	float o_x = 0.0f;
	float o_y = 0.0f;
	
	float d_ux = -64.0f;
	float d_uy = 0;
	float d_vx = 0;
	float d_vy = 64.0f;
	
	if( st->decal_ux | st->decal_uy )
	{
		o_x = st->decal_corner_x;
		d_ux = st->decal_ux;
		d_uy = st->decal_uy;
	}
	
	if( st->decal_vx | st->decal_vy )
	{
		o_y = st->decal_corner_y;
		d_vx = st->decal_vx;
		d_vy = st->decal_vy;
	}
	
	float s = (float)st->width / (d_ux*d_ux+d_uy*d_uy);
	d_ux *= s;
	d_uy *= s;
	
	s = (float)st->height / (d_vx*d_vx+d_vy*d_vy);
	d_vx *= s;
	d_vy *= s;
	
	int u_mask = st->width-1;
	int v_mask = st->height-1;
	
	int u, v;
	int offset;
	int channels = st->channels;
	unsigned char* wrk=c_big_texture;
	unsigned char* src;
	
	if( st->format == 1 )
	{
		src = (unsigned char*)malloc(st->width*st->height*channels);
		assert( src );
		decompress_jpeg( src, st->compressed_data,
			st->compressed_length, st->width, st->height, st->channels, FALSE );
	}
	else
		src = (unsigned char*)st->compressed_data;
		
	for( float y = 0; y < st->height; y += step_y )
	{
		for( float x = 0; x < st->width; x += step_x )
		{
			u = (int)(((y - o_y) * d_uy) + ((x - o_x) * d_ux)) & u_mask;
			v = (int)(((y - o_y) * d_vy) + ((x - o_x) * d_vx)) & v_mask;
			offset = ((v*st->width*channels) + (u*channels));
			(*wrk) = src[offset];
			*(wrk+1) = src[offset+1];
			*(wrk+2) = src[offset+2];
			wrk+=3;
		}
	}
	
	if( src != st->compressed_data )
		free( src );
		
	// we have a big version of the image in c_big_texture.
	// sample it down to the work size by box filtering.
	int xu, yu;
	int xc, yc;
	float pix_r, pix_g, pix_b;
	
	wrk = c_details[c_num_details++];
	
	for( yu = 0; yu < BIG_SIZE; yu += SAMPLE_SIZE )
	{
		for( xu = 0; xu < BIG_SIZE; xu += SAMPLE_SIZE )
		{
			// grab the block of pixels to sample.
			pix_r = pix_g = pix_b = 0;
			for( yc = 0; yc < SAMPLE_SIZE; yc++ )
			{
				src = c_big_texture+((yu+yc)*BIG_SIZE*3+(xu*3));
				for( xc = 0; xc < SAMPLE_SIZE; xc++ )
				{
					pix_r += (float)*src;
					pix_g += (float)*(src+1);
					pix_b += (float)*(src+2);
					src+=3;
				}
			}
			
			// write out to destination.
			pix_r /= (float)SAMPLE_BLOCKS;
			pix_g /= (float)SAMPLE_BLOCKS;
			pix_b /= (float)SAMPLE_BLOCKS;
			
			if( pix_r > 255 )
				pix_r = 255;
			if( pix_r < 0 )
				pix_r = 0;
				
			if( pix_g > 255 )
				pix_g = 255;
			if( pix_g < 0 )
				pix_g = 0;
				
			if( pix_b > 255 )
				pix_b = 255;
			if( pix_b < 0 )
				pix_b = 0;
				
			*wrk = (unsigned char)pix_r;
			*(wrk+1) = (unsigned char)pix_g;
			*(wrk+2) = (unsigned char)pix_b;
			
			wrk+=3;
		}
	}
}

void CollapseTerrainDetails(tag_file* tf,  tag terrain_stack_tag, tag detail_stack_tag )
{
	stack_set* terrain_stack;
	stack_set* detail_stack;
	
	tag_entry* e = get_entry( 'txst', terrain_stack_tag );
	if(!e)
		return;
	terrain_stack = load_stackset( e );
	if(!terrain_stack)
		return;
		
	e = get_entry( 'txst', detail_stack_tag );
	if(!e)
		return;
	detail_stack = load_stackset( e );
	if( !detail_stack )
	{
		dispose_stackset( terrain_stack );
		return;
	}
	
	if( terrain_stack->used_stacks < 1 )
	{
		dispose_stackset( terrain_stack );
		dispose_stackset( detail_stack );
		return;
	}
		
	// down/up sample details so they can be combined w/ terrain.
	SampleDetails( detail_stack );	
	stack_set* new_set = new_empty_stackset( terrain_stack->used_stacks );	
	assert( new_set );

	int i;
	texture_stack_head* sh;
	stacked_texture* tex;
	unsigned char* data;
	
	for(i = 0; i < terrain_stack->used_stacks; i++)
	{
		// copy the terrain texture into the work area.
		sh = &terrain_stack->stacks[i];
		assert( sh->textures );
		tex = sh->textures;
		assert( tex->width == WORK_SIZE && tex->height == WORK_SIZE && tex->channels == 3 );
	
		if( tex->format == 1 )
		{
			data = (unsigned char*)malloc(WORK_SIZE*WORK_SIZE*3);
			assert( data );
			decompress_jpeg( data, tex->compressed_data, tex->compressed_length, 
				WORK_SIZE, WORK_SIZE, 3, TRUE );
		}				
		else
			data = (unsigned char*)tex->compressed_data;
		memcpy( c_work_texture, data, WORK_SIZE*WORK_SIZE*3 );
		if( data != tex->compressed_data )
			free( data );
			
		// combine all detail passes.
		for( ; tex; tex = tex->next )
		{
			if( tex->use != txst_use_detailmask )
				continue;
			assert( tex->layer < c_num_details );
			AddDetailTexture( tex->layer, tex );
			//break;
		}
		
		tex = (stacked_texture*)malloc(sizeof(stacked_texture));
		assert( tex );
		tex->width = WORK_SIZE;
		tex->height = WORK_SIZE;
		tex->use = txst_use_basecolor;
		tex->channels = 3;
		tex->format = 0;
		tex->layer = 0;
		tex->decal_corner_x = 0;
		tex->decal_corner_y = 0;
		tex->decal_ux = tex->decal_uy =
		tex->decal_vx = tex->decal_vy = 0;
		tex->compressed_length = WORK_SIZE*WORK_SIZE*3;
		tex->compressed_data = malloc( WORK_SIZE*WORK_SIZE*3 );
		tex->thumbnail = 0;
		assert( tex->compressed_data );
		memcpy( (void*)tex->compressed_data, c_work_texture, WORK_SIZE*WORK_SIZE*3 );
		insert_into_texturestack( new_set, i, 0, tex );
		recompress_texture( tex, 94 );
	}
	
	e = add_tag_entry( tf, 'txst', new_tag_id_from_string( "Collapsed Terrain Stack", TRUE, tf ), 
		get_type_version('txst'), "Collapsed Terrain Stack", 0, 0, 0, FALSE );
	
	save_stackset( e, new_set );
	dispose_stackset( new_set );
	Cleanup();
	TRevengeApp::OpenItem( e );
}