#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xutil.h>            /* shape.h の前に必要 */
#include <X11/extensions/shape.h> /* Shape Extension    */
#include <Imlib.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>


static Display* display;
static Window window;
static ImlibData*  imlib_data;
static ImlibImage* imlib_image;
static Pixmap pixmap;
static Pixmap mask;
static unsigned char* original;
static unsigned char* grayscale;
static int width;
static int height;


static int  save_original( void );
static void disable_resize( void );
static void render( void );
static void update_image( void );
static void show_original( void );
static void show_grayscale( void );
static void filter_negative( void );
static void filter_edge( void );

static void process_filter( int t_row, int t_col, int* mask, 
	    int* delta_r, int* delta_g, int* delta_b );


int
main( int argc, char** argv )
{
	int screen;

	if ( argc <= 1 ) {
		fprintf( stderr, "画像ファイルを指定してください。\n" );
		exit(1);
	}

	display = XOpenDisplay( NULL );
	screen  = DefaultScreen( display );

	if ( ( imlib_data = Imlib_init( display ) ) == NULL ) {
		fprintf( stderr, "Imlib の初期化に失敗しました。\n" );
		XCloseDisplay( display );
		exit(1);
	}

	if ( ( imlib_image = Imlib_load_image( imlib_data, argv[1] ) ) == NULL ) {
		fprintf( stderr, "画像の読み込みに失敗しました。\n" );
		XCloseDisplay( display );
		exit(1);
	}

	width  = imlib_image->rgb_width;
	height = imlib_image->rgb_height;

	if ( save_original() != 0 ) {
		fprintf( stderr, "元画像の保存に失敗しました。\n" );
		Imlib_kill_image( imlib_data, imlib_image );
		XCloseDisplay( display );
		exit(1);
	}

	window = XCreateSimpleWindow( 
		display, 
		DefaultRootWindow( display ),
		width,
		height,
		width,
		height,
		0,
		BlackPixel( display, screen ),
		BlackPixel( display, screen ) 
		);

	disable_resize();
	XSelectInput( display, window, StructureNotifyMask | KeyPressMask );
	XMapWindow( display, window );
	render();
	update_image();

	while ( 1 ) {
		int esc_pressed = 0;
		XEvent event;
		KeySym key_sym;

		XNextEvent( display, &event );

		switch ( event.type ) {
			case KeyPress:
				XLookupString( &( event.xkey ), 
					       NULL, 0, &key_sym, NULL );

				switch ( key_sym ) {
					case XK_Left:
						show_original();
						break;

					case XK_Right:
						show_grayscale();
						break;

					case XK_Up:
						filter_negative();
						break;

					case XK_Down:
						filter_edge();
						break;

					case XK_Escape:
						esc_pressed = 1;
						break;
				}

				break;
		}

		if ( esc_pressed ) {
			break;
		}
	}

	Imlib_free_pixmap( imlib_data, pixmap );
	Imlib_kill_image( imlib_data, imlib_image );
	free( original );
	free( grayscale );
	XDestroyWindow( display, window );
	XCloseDisplay( display );
	return 0;
}


static int
save_original( void )
{
	int bufsize = width * height * 3;

	if ( ( original = ( unsigned char* )malloc( bufsize ) ) == NULL ) {
		return -1;
	}

	if ( ( grayscale = ( unsigned char* )malloc( bufsize ) ) == NULL ) {
		free( original );
		return -1;
	}

	memcpy( original, imlib_image->rgb_data, bufsize );

	/*
	 * 元画像の保存と同時に、（主にエッジ抽出フィルタ用の）
	 * グレースケール画像を作成しておく。
	 */
	{
		int row;
		int col;
		int index;
		int mid;

		unsigned char r;
		unsigned char g;
		unsigned char b;

		printf( "Creating grayscale image..." );
		fflush( stdout );

		for ( row = 0; row < height; row ++ ) {
			for ( col = 0; col < width; col ++ ) {
				index = ( row * width + col ) * 3;

				r = imlib_image->rgb_data[ index + 0 ];
				g = imlib_image->rgb_data[ index + 1 ];
				b = imlib_image->rgb_data[ index + 2 ];

				mid = ( int )( ( r + g + b ) / 3 );

				grayscale[ index + 0 ] = ( unsigned char )mid;
				grayscale[ index + 1 ] = ( unsigned char )mid;
				grayscale[ index + 2 ] = ( unsigned char )mid;
			}
		}

		printf( "done.\n" );
		fflush( stdout );
	}

	return 0;
}


static void
disable_resize( void )
{
	XSizeHints hints;

	hints.min_width  = width;
	hints.min_height = height;
	hints.max_width  = width;
	hints.max_height = height;
	hints.flags = PMinSize | PMaxSize;

	XSetWMNormalHints( display, window, &hints );
}


static void
render( void )
{
	Imlib_render( imlib_data, imlib_image, width, height );
	pixmap = Imlib_move_image( imlib_data, imlib_image );
	mask = Imlib_move_mask( imlib_data, imlib_image );
}


static void
update_image( void )
{
	XSetWindowBackgroundPixmap( display, window, pixmap );
	
	if ( mask ) {
		XShapeCombineMask( display, window, 
			   ShapeBounding, 0, 0, mask, ShapeSet );
	}

	XClearWindow( display, window );
	XSync( display, False );
}


static void
show_original( void )
{
	memcpy( imlib_image->rgb_data, original, width * height * 3 );
	Imlib_free_pixmap( imlib_data, pixmap );
	Imlib_changed_image( imlib_data, imlib_image );
	render();
	update_image();
}


static void
show_grayscale( void )
{
	memcpy( imlib_image->rgb_data, grayscale, width * height * 3 );
	Imlib_free_pixmap( imlib_data, pixmap );
	Imlib_changed_image( imlib_data, imlib_image );
	render();
	update_image();
}


static void
filter_negative( void )
{
	int row;
	int col;
	int rgb;
	int index;

	printf( "Processing negative filter..." );
	fflush( stdout );

	for ( row = 0; row < height; row ++ ) {
		for ( col = 0; col < width; col ++ ) {
			for ( rgb = 0; rgb < 3; rgb ++ ) {
				index = ( row * width + col ) * 3 + rgb;
				imlib_image->rgb_data[ index ] = 
					UCHAR_MAX - original[ index ];
			}
		}
	}

	printf( "done.\n" );
	fflush( stdout );

	Imlib_free_pixmap( imlib_data, pixmap );
	Imlib_changed_image( imlib_data, imlib_image );
	render();
	update_image();
}


#define TIMER_DIFF( diff, end, start ) { \
	(diff)->tv_usec = (end)->tv_usec - (start)->tv_usec; \
	if ( (diff)->tv_usec < 0 ) {\
		(diff)->tv_usec += 1000000; \
		(diff)->tv_sec = (end)->tv_sec - (start)->tv_sec - 1;\
	}\
	else {\
		(diff)->tv_sec = (end)->tv_sec - (start)->tv_sec;\
	}\
	if ( (diff)->tv_sec < 0 ) {\
		(diff)->tv_sec = (diff)->tv_usec = 0;\
	}\
}

static void
filter_edge( void )
{
	/* Sobel Operator (X) */
	int mask_x[] = { 
		1,  0, -1,
		2,  0, -2,
		1,  0, -1
	};

	/* Sobel Operator (Y) */
	int mask_y[] = { 
		 1,  2,  1,
		 0,  0,  0,
		-1, -2, -1
	};

	int rx;
	int gx;
	int bx;
	int ry;
	int gy;
	int by;

	int r;
	int g;
	int b;

	int row;
	int col;
	int index;

	struct timeval start;
	struct timeval end;
	struct timeval diff;

	printf( "Processing edge filter..." );
	fflush( stdout );
	gettimeofday( &start, NULL );

	for ( row = 0; row < height; row ++ ) {
		for ( col = 0; col < width; col ++ ) {
			process_filter( row, col, mask_x, &rx, &gx, &bx );
			process_filter( row, col, mask_y, &ry, &gy, &by );

			r = ( int )sqrt( ( double )( rx * rx + ry * ry ) );
			g = ( int )sqrt( ( double )( gx * gx + gy * gy ) );
			b = ( int )sqrt( ( double )( bx * bx + by * by ) );

			index = ( row * width + col ) * 3;

			imlib_image->rgb_data[ index + 0 ] = 
				( r > UCHAR_MAX ) ? UCHAR_MAX : r;

			imlib_image->rgb_data[ index + 1 ] = 
				( g > UCHAR_MAX ) ? UCHAR_MAX : g;

			imlib_image->rgb_data[ index + 2 ] = 
				( b > UCHAR_MAX ) ? UCHAR_MAX : b;
		}
	}

	gettimeofday( &end, NULL );
	TIMER_DIFF( &diff, &end, &start );
	printf( "done ( %ld ).\n", diff.tv_sec * 1000000 + diff.tv_usec );
	fflush( stdout );

	Imlib_free_pixmap( imlib_data, pixmap );
	Imlib_changed_image( imlib_data, imlib_image );
	render();
	update_image();
}


#define INDEX( row, col ) ( ( (row) * width + (col) ) * 3 )

static void
process_filter( int t_row, int t_col, int* mask, 
	int* delta_r, int* delta_g, int* delta_b )
{
	int s_row;
	int s_col;
	int index;
	int m_index;
	int i;
	int j;

	*delta_r = 0;
	*delta_g = 0;
	*delta_b = 0;

	for ( i = -1; i <= 1; i ++ ) {
		s_row = t_row + i;
		s_row = ( s_row < 0 ) ? 0 : 
			        ( ( s_row >= height ) ? height - 1 : s_row );
		
		for ( j = -1; j <= 1; j ++ ) {
			s_col = t_col + j;
			s_col = ( s_col < 0 ) ? 0 : 
				( ( s_col >= width ) ? width - 1 : s_col );

			index = s_row * width + s_col;
			m_index = ( i + 1 ) * 3 + ( j + 1 );

			( *delta_r ) += ( int )( 
				grayscale[ index * 3 + 0 ] * mask[ m_index ] );

			( *delta_g ) += ( int )( 
				grayscale[ index * 3 + 1 ] * mask[ m_index ] );

			( *delta_b ) += ( int )( 
				grayscale[ index * 3 + 2 ] * mask[ m_index ] );
		}
	}
#if 0
	int r0 = ( t_row - 1 < 0 ) ? 0 : t_row - 1;
	int r1 = t_row;
	int r2 = ( t_row + 1 >= height ) ? height - 1 : t_row + 1;
	int c0 = ( t_col - 1 < 0 ) ? 0 : t_col - 1;
	int c1 = t_col;
	int c2 = ( t_col + 1 >= width ) ? width - 1 : t_col + 1;

	*delta_r =
		grayscale[ INDEX( r0, c0 ) + 0 ] * mask[0] +
		grayscale[ INDEX( r0, c1 ) + 0 ] * mask[1] +
		grayscale[ INDEX( r0, c2 ) + 0 ] * mask[2] +
		grayscale[ INDEX( r1, c0 ) + 0 ] * mask[3] +
		grayscale[ INDEX( r1, c1 ) + 0 ] * mask[4] +
		grayscale[ INDEX( r1, c2 ) + 0 ] * mask[5] +
		grayscale[ INDEX( r2, c0 ) + 0 ] * mask[6] +
		grayscale[ INDEX( r2, c1 ) + 0 ] * mask[7] +
		grayscale[ INDEX( r2, c2 ) + 0 ] * mask[8];

	*delta_g =
		grayscale[ INDEX( r0, c0 ) + 1 ] * mask[0] +
		grayscale[ INDEX( r0, c1 ) + 1 ] * mask[1] +
		grayscale[ INDEX( r0, c2 ) + 1 ] * mask[2] +
		grayscale[ INDEX( r1, c0 ) + 1 ] * mask[3] +
		grayscale[ INDEX( r1, c1 ) + 1 ] * mask[4] +
		grayscale[ INDEX( r1, c2 ) + 1 ] * mask[5] +
		grayscale[ INDEX( r2, c0 ) + 1 ] * mask[6] +
		grayscale[ INDEX( r2, c1 ) + 1 ] * mask[7] +
		grayscale[ INDEX( r2, c2 ) + 1 ] * mask[8];

	*delta_b =
		grayscale[ INDEX( r0, c0 ) + 2 ] * mask[0] +
		grayscale[ INDEX( r0, c1 ) + 2 ] * mask[1] +
		grayscale[ INDEX( r0, c2 ) + 2 ] * mask[2] +
		grayscale[ INDEX( r1, c0 ) + 2 ] * mask[3] +
		grayscale[ INDEX( r1, c1 ) + 2 ] * mask[4] +
		grayscale[ INDEX( r1, c2 ) + 2 ] * mask[5] +
		grayscale[ INDEX( r2, c0 ) + 2 ] * mask[6] +
		grayscale[ INDEX( r2, c1 ) + 2 ] * mask[7] +
		grayscale[ INDEX( r2, c2 ) + 2 ] * mask[8];
#endif
}


/* End of filter.c */
