#include "img.inl"

void process_image( uint32_t* abgr, int w, int h )
	{
	using namespace img;
	image_t img = from_ARGB32( (unsigned) w, (unsigned) h, abgr );
	adjust_brightness( &img, 0.065f );
	sharpen( &img, 0.5f, 1.0f );
	adjust_contrast( &img, 1.15f );
	adjust_saturation( &img, 0.1f );

	to_ARGB32( &img, abgr );
	}


namespace pixie_build {

compilation_result compiler_jam_sprite_single( compile_context const* context )
	{		
	compilation_result result = { 0 };

	string format;
	string palette;
	string dither;
	for( int i = 0; i < context->parameters.count(); ++i )
		{
		string_id name( context->parameters[ i ].name );
		string value( context->parameters[ i ].value );
		str_switch( name )
			{
			str_case( "format" )
				{
				format = value;
				}
			str_case( "palette" )
				{
				palette = value;
				}
			str_case( "dither" )
				{
				dither = value;
				}
			}
		}
	
	string input_file = path_join( context->input_root, context->path, context->input );

	string output_file = path_join( context->output_root, context->path, basename( context->input ) ) + ".pix";
	if( !file_exists( output_file.c_str() ) || file_more_recent( input_file.c_str(), output_file.c_str() ) )
		{
		string palette_file = path_join( context->build_root, dirname( palette ), basename( palette ) ) + ".plut";
		string palette_input = path_join( context->input_root, palette );
		if( !file_exists( palette_file.c_str() ) || file_more_recent( palette_input.c_str(), palette_file.c_str() ) )
			{
			logf( palette + "\n" );
			build_palette_lookup( palette_input, palette_file );
			}

		logf( path_join( context->path, context->input ) + "\n" );
		
		file_t* file = file_load( palette_file.c_str(), FILE_MODE_BINARY, 0 );
		if( !file ) { return result; /* TODO: error handling */}
		paldither_palette_t* pal = paldither_palette_create_from_data( file->data, file->size, 0 );
		file_destroy( file );
		
		int w, h, c;
		stbi_uc* img = stbi_load( input_file.c_str(), &w, &h, &c, 4 );
		
		process_image( (uint32_t*) img, w, h );

		char const header[] = "PIXIE_PIX";
		int version = 1;
		size_t size = 5 * sizeof ( int ) + 2 * w * h + sizeof( header ) + sizeof( version ) + 4 * sizeof( int );
		u8* output = (u8*) malloc( size );
		memcpy( output, header, sizeof( header ) );
		memcpy( output + sizeof( header ), &version, sizeof( version ) );
		
		int* offset_and_pitch = (int*)( output + sizeof( header ) + sizeof( version ) + 5 * sizeof ( int ) );
		
        paldither_type_t dither_type = PALDITHER_TYPE_DEFAULT;
        if( dither == "none" )
            dither_type = PALDITHER_TYPE_NONE;
        else if( dither == "bayer" )
            dither_type = PALDITHER_TYPE_BAYER;

		u8* pixels = output + sizeof( header ) + sizeof( version ) + 5 * sizeof ( int ) + 4 * sizeof( int ) ;
        if( dither == "remap" )
            palettize_remap_xbgr32( (PALDITHER_U32*)img, w, h, pal->colortable, pal->color_count, pixels );	
        else
		    paldither_palettize( (PALDITHER_U32*)img, w, h, pal, dither_type, pixels );	

		u8* mask = pixels + w * h;
		bool is_masked = false; 
		int x_min = w;
		int x_max = -1;
		int y_min = h;
		int y_max = -1;
		for( int y = 0; y < h; ++y ) 
			{
			for( int x = 0; x < w; ++x ) 
				{
				u8 m = (u8)( ( ((u32*) img)[ x + w * y ] ) >> 24 );
				mask[ x + w * y ] = m;
				is_masked |= m < 0xff;			
				if( m )
					{
					x_min = x < x_min ? x : x_min;
					y_min = y < y_min ? y : y_min;
					x_max = x > x_max ? x : x_max;
					y_max = y > y_max ? y : y_max;
					}
				}
			}
		stbi_image_free( img );
		int offset_x = x_min;
		int offset_y = y_min;
		int pitch_x = x_max - x_min + 1;
		int pitch_y = y_max - y_min + 1;
		if( x_max < x_min || y_max < y_min )
			{
			offset_x = 0;
			offset_y = 0;
			pitch_x = 0;
			pitch_y = 0;
			}

		if( pitch_x < w || pitch_y < h )
			{
			size -= 2 * w * h;
			size += pitch_x * pitch_y;

			for( int y = 0; y < pitch_y; ++y ) 
				for( int x = 0; x < pitch_x; ++x ) 
					pixels[ x + pitch_x * y ] = pixels[ ( x + offset_x ) + ( y + offset_y ) * w ];

			if( is_masked ) 
				{
				size += pitch_x * pitch_y;
				u8* mask_target = pixels + pitch_x * pitch_y;
				for( int y = 0; y < pitch_y; ++y ) 
					for( int x = 0; x < pitch_x; ++x ) 
						mask_target[ x + pitch_x * y ] = mask[ ( x + offset_x ) + ( y + offset_y ) * w ];
				}
			}
		else if( !is_masked ) size -= w * h;

			
		offset_and_pitch[ 0 ] = offset_x;
		offset_and_pitch[ 1 ] = offset_y;
		offset_and_pitch[ 2 ] = pitch_x;
		offset_and_pitch[ 3 ] = pitch_y;

		int* info = (int*)( output + sizeof( header ) + sizeof( version ) );
		info[ 0 ] = 0; // raw
		info[ 1 ] = w;
		info[ 2 ] = h;
		info[ 3 ] = 1; // cel_count
		info[ 4 ] = is_masked ? 1 : 0;
		
		create_path( path_join( context->output_root, context->path ).c_str() );
		file_save_data( output, size, output_file.c_str(), FILE_MODE_BINARY );
		free( output );
		paldither_palette_destroy( pal );		
		}
	return result;
	}


compilation_result compiler_jam_sprite_strip( compile_context const* context )
	{		
	array<string> inputs;

	compilation_result result = { 0 };
	string ext = extname( context->input );
	char const* underscore = strrchr( basename( context->input ).c_str(), '_' );	
	string stripped_name( basename( context->input ).c_str(), underscore + 1 );
	
	dir_t* dir = dir_open( path_join( context->input_root, context->path ).c_str() );
	assert( dir );
	dir_entry_t* ent = dir_read( dir );

	while( ent )
		{
		if( dir_is_file( ent ) )
			{
			if( (int)strlen( dir_name( ent ) ) == len( context->input ) && left( string( dir_name( ent ) ), len( stripped_name ) ) == stripped_name )
				{
				inputs.add( dir_name( ent ) );
				}
			}
		ent = dir_read( dir );
		}
	dir_close( dir );

	pixie_build::sort( &inputs );

	for( int i = 0; i < inputs.count(); ++i )
		{
		compile_context single_context = *context;
		single_context.output_root = single_context.build_root;
		single_context.input = inputs[ i ];
		compiler_jam_sprite_single( &single_context );
		}	
		
	string output_file = path_join( context->output_root, context->path, left( stripped_name, len( stripped_name ) -1 ) ) + ".pix";	
	bool need_to_build = !file_exists( output_file.c_str() );
	if( !need_to_build )
		{
		for( int i = 0; i < inputs.count(); ++i )
			{
			string build_file = path_join( context->build_root, context->path, basename( context->input ) ) + ".pix";	
			if( file_more_recent( build_file.c_str(), output_file.c_str() ) )
				{
				need_to_build = true;
				break;
				}
			}
		}
		
	if( need_to_build )
		{
		array<pixie_build::ref<pixie_build::binary> > files;
		for( int i = 0; i < inputs.count(); ++i )
			{
			string build_file = path_join( context->build_root, context->path, basename( inputs[ i ] ) ) + ".pix";	
			pixie_build::ref<pixie_build::binary> file = pixie_build::bload( build_file );
			// TODO: error handling, file == 0, size == 0
			files.add( file );
			}
		
	

		char const header[] = "PIXIE_PIX";
		int version = 1;
		int w = -1;
		int h = -1;
		int is_masked = -1;
		for( int i = 0; i < files.count(); ++i )
			{
			u8* data = (u8*)files[ i ]->data;
			int* info = (int*)( data + sizeof( header ) + sizeof( version ) );
			if( w >= 0 && w != info[ 1 ] ) (void)w; // TODO: error handling
			if( h >= 0 && h != info[ 2 ] ) (void)h; // TODO: error handling
			if( is_masked >= 0 && is_masked != info[ 4 ] ) (void)is_masked; // TODO: error handling
			w = info[ 1 ];
			h = info[ 2 ];
			is_masked = info[ 4 ];
			}
		assert( w > 0 && h > 0 && is_masked >= 0 );

		size_t alloc_size = 5 * sizeof ( int ) + sizeof( header ) + sizeof( version ) + ( w * h * ( is_masked ? 2 : 1 ) + sizeof( int ) * 4 ) * files.count();
		u8* output = (u8*) malloc( alloc_size );
		memcpy( output, header, sizeof( header ) );
		memcpy( output + sizeof( header ), &version, sizeof( version ) );
		int* info = (int*)( output + sizeof( header ) + sizeof( version ) );
		info[ 0 ] = 0; // raw
		info[ 1 ] = w;
		info[ 2 ] = h;
		info[ 3 ] = files.count(); // cel_count
		info[ 4 ] = is_masked;
				
		size_t size = 5 * sizeof ( int ) + sizeof( header ) + sizeof( version ) + sizeof( int ) * 4 * files.count();
		u8* data = (u8*)( info + 5 );
		for( int i = 0; i < files.count(); ++i )
			{
			u8* src = (u8*)files[ i ]->data;
			src += 5 * sizeof ( int ) + sizeof( header ) + sizeof( version );
			memcpy( data, src, sizeof( int ) * 4 ); data += sizeof( int ) * 4; size += sizeof( int ) * 4;
			src += sizeof( int );
			src += sizeof( int );
			int pitch_x = *(int*) src; src += sizeof( int );
			int pitch_y = *(int*) src; src += sizeof( int );
			memcpy( data, src, (size_t) pitch_x * pitch_y * ( is_masked ? 2 : 1 ) );
			data += pitch_x * pitch_y * ( is_masked ? 2 : 1 );
			size += pitch_x * pitch_y * ( is_masked ? 2 : 1 );
			}
		create_path( path_join( context->output_root, context->path ).c_str() );
		file_save_data( output, size, output_file.c_str(), FILE_MODE_BINARY );
		free( output );
		}
	
	return result;
	}
	
	
compilation_result compiler_jam_sprite( compile_context const* context )
	{
	string name = basename( context->input );
	int digit_count = 0;
	char const* underscore = strrchr( name.c_str(), '_' );	
	string stripped_name( name.c_str(), underscore );
	if( underscore )
		{	
		++underscore;
		while( *underscore )
			{
			if( *underscore >= '0' && *underscore <= '9' )
				{
				++digit_count;
				}
			else
				{
				digit_count = -1;
				break;
				}
				
			++underscore;
			}
		}

	if( digit_count > 1 )
		{
		
		int frame_num = integer( mid( name, len( stripped_name ) + 1 ) );
		bool is_first = true;
		dir_t* dir = dir_open( path_join( context->input_root, context->path ).c_str() );
		assert( dir );
		dir_entry_t* ent = dir_read( dir );
		while( ent )
			{
			if( dir_is_file( ent ) )
				{
				if( left( string( dir_name( ent ) ), len( stripped_name ) + 1 ) == stripped_name + "_" )
					{
					int number = integer( mid( string( dir_name( ent ) ), len ( stripped_name ) + 1 ) );
					if( number < frame_num ) 
						{
						is_first = false;
						break;
						}
					}
				}
			ent = dir_read( dir );
			}
		dir_close( dir );

		// if not the first image of the strip, just silently skip it (as this file will be built when the first image of the strip is built)
		if( !is_first )
			{
			compilation_result result = { 0 };
			return result;
			}

		// there needs to be at least two images in sequence for it to count as a strip		
		string next_frame = str( frame_num + 1 );
		while( len( next_frame ) < digit_count ) next_frame = "0" + next_frame;
		next_frame = stripped_name + "_" + next_frame;
		next_frame = path_join( context->input_root, context->path, next_frame ) + extname( context->input );
		if( file_exists( next_frame.c_str() ) )
			return compiler_jam_sprite_strip( context );		
		}
	
	return compiler_jam_sprite_single( context );		
	}


} /* namespace pixie_build */