#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define WIN_X   100  /* ウィンドウ表示位置(X) */
#define WIN_Y   100  /* ウィンドウ表示位置(Y) */


/* DIB ファイルの画像情報を格納する構造体 */
typedef struct _DIBImage_t
{
    int            width;           /* 画像の幅 */
    int            height;          /* 画像の高さ */
    int            n_color;         /* 画像の色数 */
    unsigned char  palette[256][3]; /* パレット情報 */
    unsigned char* data;            /* ピクセルデータ */
}
DIBImage;


static Display* dpy;        /* ディスプレイ */
static Window   root, win;  /* ルートウィンドウおよび表示ウィンドウ */
static int      screen;     /* スクリーン */
static int      depth;      /* スクリーンの深さ */
static Pixmap   pixmap;     /* ピクスマップ */
static GC       gc;         /* グラフィックスコンテキスト */
static XColor*  colors;     /* DIB ファイルのパレット情報を格納する領域 */


static int  xdib_readFile( char* fname, DIBImage* dib_image );
static void xdib_setup( DIBImage* dib_image );


int 
main( int argc, char** argv )
{
	XEvent   evt;
	DIBImage dib_image;


	/* 指定された DIB ファイルを読み込んで画像情報を保存する */
	if ( xdib_readFile( argv[1], &dib_image ) == 0 ) {
		printf( "Failed to read specified DIB file : %s\n", argv[1] );
		return 1;
	}

	dpy = XOpenDisplay( "" );

	root   = DefaultRootWindow( dpy );
	screen = DefaultScreen( dpy );
	depth  = DefaultDepth( dpy, screen );

	/* depth が 16、32 以外の場合はここで終了する */
	if ( depth != 16 && depth != 32 ) {
		XCloseDisplay( dpy );
		printf( "Screen depth is not 16 nor 32 bpp.\n" );
		return 1;
	}


	win = XCreateSimpleWindow( dpy, root, WIN_X, WIN_Y, 
				   dib_image.width,
				   dib_image.height,
				   0,
				   BlackPixel( dpy, screen ), 
				   BlackPixel( dpy, screen ) );

	gc = XCreateGC( dpy, win, 0, NULL );

	/* 読み込んだ DIB ファイル情報を表示できるように設定する */
	xdib_setup( &dib_image );

	XSelectInput( dpy, win, ExposureMask | KeyPressMask );
	XMapWindow( dpy, win );

	while( 1 ) {
		XNextEvent( dpy, &evt );

		switch( evt.type ) { 
			case Expose:
				if ( evt.xexpose.count == 0 ) {
					/* ピクスマップをウィンドウにコピーする */
					XCopyArea( dpy, pixmap, win, gc, 
						   0, 0,
						   dib_image.width,
						   dib_image.height,
						   0, 0 );
					XFlush( dpy );
				}
				break;

			case KeyPress:
				free( colors );
				XFreePixmap( dpy, pixmap );
				XDestroyWindow( dpy, win );
				XCloseDisplay( dpy );
				return 0;
		}
	}
}


/* 
 * 指定した DIB ファイルを読み込み、画像情報を dib_image 変数
 * に格納する。ピクセルデータを保存するための dib_image.data 
 * 領域は関数内で確保する。そのため本関数を利用する際には、
 * dib_image.data が未確保である dib_image 変数へのポインタを
 * 引数に指定すること。
 * 読み込み処理に失敗した場合は 0 を、成功した場合はそれ以外の値を返す。
 */
static int 
xdib_readFile( char* fname, DIBImage* dib_image )
{
	unsigned char buf[4];    
	FILE* fp;
	int   i;
	int   dib_depth;
	int   offset;
	int   bw;
	int   index;


	if ( ( fp = fopen( fname, "r" ) ) == NULL ) {
		printf( "Failed to open %s.\n", fname );
		return 0;
	}


	/* 先頭 2 バイトが "BM" であれば、BMP ファイル */
	fread( buf, 2, 1, fp );

	if ( strncmp( ( char* )buf, "BM", 2 ) != 0 ) {
		printf( "Specified file is not DIB file.\n" );
		return 0;
	}


	/* ファイルの先頭から 28 バイトの位置に移動 */
	fseek( fp, 28, SEEK_SET );

	/* 1 ピクセルあたりのビット数を取得 */
	fread( buf, 2, 1, fp );
	dib_depth = buf[0] + ( buf[1] << 8 );

	/* 256 色以下のファイルが対象 */
	if ( dib_depth > 8 ) {
		printf( "Specified file is more than 8 bpp ( %d bpp ).\n", dib_depth );
		return 0;
	}


	/* ファイルの先頭から 30 バイトの位置に移動 */
	fseek( fp, 30, SEEK_SET );
	fread( buf, 4, 1, fp );

	/* 読み込んだ値が 0 でなければ、biCompression が BI_RGB でない
	   (ピクセルデータが圧縮されている)ので、ここで終了する。*/

	if ( buf[0] != 0 ) {
		printf( "Specified DIB file is compressed\n" );
      		return 0;
	}


	/* ファイルの先頭から 46 バイトの位置に移動 */
	fseek( fp, 46, SEEK_SET );
	fread( buf, 4, 1, fp );

	/* 読み込んだ値が 0 でなければ、depth によって色数が決まる。
	   256 色以下のファイルであるため、最初の 1 バイトのみを考えれば良い */
	if ( buf[0] == 0 ) {
		dib_image->n_color = 1 << dib_depth;
	}
	else {
		dib_image->n_color = buf[0];
	}


	/* ファイルの先頭から 10 バイトの位置に移動 */
	fseek( fp, 10, SEEK_SET );

	/* ファイルの先頭からピクセルデータ領域の先頭までのオフセットを取得 */
	fread( buf, 2, 1, fp );
	offset = buf[0] + ( buf[1] << 8 );


       	/* ファイルの先頭から 18 バイトの位置に移動 */
	fseek( fp, 18, SEEK_SET );

	/* 画像の幅を取得 */
	fread( buf, 4, 1, fp );

	dib_image->width  = buf[0] + ( buf[1] << 8 ) + 
		( buf[2] << 16 ) + ( buf[3] << 24 );

	/* 画像の高さを取得 */
	fread( buf, 4, 1, fp );

	dib_image->height = buf[0] + ( buf[1] << 8 ) + 
		( buf[2] << 16 ) + ( buf[3] << 24 );

    
	/* ファイルの先頭から 54 バイトの位置に移動 */
	fseek( fp, 54, SEEK_SET );

	/* パレット情報を取得 */
	for ( i = 0; i < dib_image->n_color; i ++ ) {
		fread( buf, 4, 1, fp );
		dib_image->palette[i][0] = buf[2];
		dib_image->palette[i][1] = buf[1];
		dib_image->palette[i][2] = buf[0];
	}


	/* 画像の 1 行分のバイト数は、画像の幅に 1 番近い 4 の倍数に繰り上げられる */
	bw = ( dib_image->width + 3 ) & ~3;


	/* ピクセルデータ領域の確保 */
	dib_image->data = 
		( unsigned char* )malloc( dib_image->width * dib_image->height );

	for ( i = 0; i < dib_image->height; i ++ ) {
		/* ピクセルデータ領域の先頭から 1 行ずつ読み込む */
		fseek( fp, offset + bw * i, SEEK_SET );

		/* 
		 * DIB 内のピクセルデータは下行から順に詰められている。
		 * dib_image->data には上行から順に詰めるため、対応する
		 * インデックスを計算している。
		 */
		index = dib_image->width * ( dib_image->height - i - 1 );

		/* 1 行読み込み */
		fread( &( dib_image->data[ index ] ), dib_image->width, 1, fp );
	}

	fclose( fp );
	return 1;
}


/*
 * 読み込んだ DIB ファイル情報を使って、実際にウィンドウ
 * に表示できるように設定を行う
 */
static void 
xdib_setup( DIBImage* dib_image )
{
	/* 
	 * 読み込んだ DIB ファイルの画像情報をクライアント側で
	 * 保持するための XImage 構造体
	 */
	XImage*  ximage;

	/* クライアント側から X サーバに転送するピクセルデータを格納する領域 */
	unsigned char* img_data;  

	int size;
	int i;


	/* DIBImage 型変数に格納されたカラーパレット情報を、XColor 構造体の
	   配列形式に変換する */
	colors = ( XColor* )malloc( sizeof( XColor ) * dib_image->n_color );

	for ( i = 0; i < dib_image->n_color; i ++ ) {
		colors[i].red   = dib_image->palette[i][0] * 257;
		colors[i].green = dib_image->palette[i][1] * 257;
		colors[i].blue  = dib_image->palette[i][2] * 257;
		colors[i].flags = DoRed | DoGreen | DoBlue;

		XAllocColor( dpy, DefaultColormap( dpy, screen ), &colors[i] );
	}


	/* X サーバ側で画像を保持するためのピクスマップを作成する */
	pixmap = XCreatePixmap( dpy, win, 
				dib_image->width, 
				dib_image->height, 
				depth );


	/* スクリーンの深さに合わせた、転送用ピクセルデータを作成する */
	size     = dib_image->width * dib_image->height;
	img_data = ( unsigned char* )malloc( size * depth / 8 );

	memset( img_data, 0, size * depth / 8 );


	/* 
	 * DIBImage 内のピクセルデータは、パレット情報配列のどの要素が
	 * ピクセルに対応する色であるかを表している。 転送用のピクセル
	 * データは、 スクリーンの深さと同じビット数で表された各色の
	 * ピクセル値でなければならないので、ここで変換を行う。
	 */
	for ( i = 0; i < size; i ++ ) {
		memcpy( img_data + i * ( depth / 8 ) , 
			&( colors[dib_image->data[i]].pixel ), depth / 8 );
	}


	/* クライアント側の画像情報保持領域である XImage 構造体変数を作成する */
	ximage = XCreateImage( dpy, DefaultVisual( dpy, screen ),
			       depth, ZPixmap, 0, 
			       ( char* )img_data,
			       dib_image->width, 
			       dib_image->height,
			       /* 16 bpp の場合は 16、24 / 32 bpp の場合は 32 にする */
			       ( depth == 16 ) ? 16 : 32, 
			       0 );

	/* ピクセルデータをピクスマップに転送する */
	XPutImage( dpy, pixmap, gc, ximage, 
		   0, 0, 
		   0, 0, 
		   dib_image->width, 
		   dib_image->height );

	XFlush( dpy );
	XDestroyImage( ximage );
	free( dib_image->data );
}


/* End of xdib.c */
