#include <xstep.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <jpeglib.h>

XSImage  *image,*oldimage;
XSPixmap *pixmap,*old;
XSWidget *d1,*w1,*w2,*w3,*w4;

double 	zoom=1.0,igamma=1.0;

int	last,next,count,max=1,inroot,antialiasing=1,sb=False;

int pad(int offset) {

	return offset&3?offset-(offset&3)+4:offset;
}

XSImage *jpgread(FILE *fp) {

	char 	format[80],*data=NULL,*target;
	int 	width=0,height=0,depth=0;

	struct jpeg_decompress_struct 	cinfo;
	struct jpeg_error_mgr 		jerr;

	fseek(fp,0,SEEK_SET);

	fgets(format,80,fp);
		
	if(!strncmp(format+6,"JFIF",4)) {

		fseek(fp,0,SEEK_SET);

		cinfo.err = jpeg_std_error(&jerr);
		jpeg_create_decompress(&cinfo);
		jpeg_stdio_src(&cinfo, fp);
		jpeg_read_header(&cinfo, TRUE);
		jpeg_start_decompress(&cinfo);
		
		width  = cinfo.output_width;
		height = cinfo.output_height;
		depth  = cinfo.output_components;

		fprintf(stderr,"*read: JFIF %dx%d, depth %d\n",width,height,depth);
		
		data=(char *)malloc(width*height*depth);
		
		for(target=data;cinfo.output_scanline<cinfo.output_height;target+=width*depth)
			jpeg_read_scanlines(&cinfo,(JSAMPARRAY)&target,1);
		
		jpeg_finish_decompress(&cinfo);
		jpeg_destroy_decompress(&cinfo);
		
	}

	return data?XSImageCreate(width,height,data,depth==3?XS_IMAGE_RGB24:XS_IMAGE_GRAY):NULL;
}


XSImage *pcxread(FILE *fp) {

	struct {

		unsigned char 	manufacturer 	__attribute__ ((packed));
		unsigned char 	version		__attribute__ ((packed));	
		unsigned char 	encoder		__attribute__ ((packed));
		unsigned char 	depth		__attribute__ ((packed));
		unsigned short 	x		__attribute__ ((packed));
		unsigned short 	y		__attribute__ ((packed));
		unsigned short 	width		__attribute__ ((packed));
		unsigned short 	height		__attribute__ ((packed));
		unsigned short 	hdpi		__attribute__ ((packed));
		unsigned short 	vdpi		__attribute__ ((packed));
		unsigned char 	palette[48]	__attribute__ ((packed));
		unsigned char 	reserved	__attribute__ ((packed));
		unsigned char 	planes		__attribute__ ((packed));
		unsigned short 	bpl		__attribute__ ((packed));
		unsigned short  type		__attribute__ ((packed));
		unsigned char  	pad[58]		__attribute__ ((packed));
	
	} pcxinfo;

	int x,y,z,repeat;
	unsigned char *q,*buffer,data,*raw=NULL;

	struct {
		
		unsigned char r,g,b;
	
	} palette[256];

	fseek(fp,0,SEEK_SET);

	fread(&pcxinfo,sizeof(pcxinfo),1,fp);
	
	fprintf(stderr,"check: PCX-v%d-%s-%d, %dx%d (%d bits, status=%s)\n",
		pcxinfo.version,
		pcxinfo.encoder?(pcxinfo.encoder==1?"RLE":"compressed"):"uncompressed",
		pcxinfo.encoder,
		pcxinfo.width,
		pcxinfo.height,
		pcxinfo.depth,
		pcxinfo.bpl == pad(pcxinfo.width*pcxinfo.depth/8)?"valid":"invalid");

	if(pcxinfo.bpl != pad(pcxinfo.width*pcxinfo.depth/8)) return NULL;
	if(pcxinfo.encoder>1) return NULL;
	if(pcxinfo.depth!=8)  return NULL;

	q=raw=(unsigned char *)malloc(pcxinfo.width*pcxinfo.height*3);

	fseek(fp,-768,SEEK_END);

	fread(&palette,sizeof(palette),1,fp);

	fseek(fp,128,SEEK_SET);

	buffer=(unsigned char *)malloc(pcxinfo.bpl*(pcxinfo.height+1));

	x=0;
	
	while(x<pcxinfo.bpl*pcxinfo.height) {

		data=fgetc(fp);
		repeat=1;

		if(data>192) {
			
			repeat=data-192;
			data=fgetc(fp);
		}

		while(repeat--) buffer[x++] = data;
	}

	for(z=y=0;y!=pcxinfo.height;y++) {
	
		for(x=0;x!=pcxinfo.width;x++,z++) {

			*q++ = palette[buffer[z]].b;
			*q++ = palette[buffer[z]].g;
			*q++ = palette[buffer[z]].r;
		}
		
		z+=(pcxinfo.bpl-pcxinfo.width);
	}	

	free(buffer);

	fprintf(stderr,"*read: PCX-v%d-RLE, %dx%d (8 bit/pixel, compressor=RLE, status=OK)\n",
		pcxinfo.version,
		pcxinfo.width,
		pcxinfo.height);

	return raw?XSImageCreate(pcxinfo.width,pcxinfo.height,raw,XS_IMAGE_BGR24):NULL;
}

XSImage *ppmread(FILE *fp) {

	unsigned char buffer[4096],*q,*raw=NULL;
	int type=0,x,y,z,max,r,g,b,width,height;
	
	fseek(fp,0,SEEK_SET);
	
	fgets(buffer,4096,fp);
	
	if(buffer[0]=='P') {

		type=buffer[1]-'0';

		while(fgets(buffer,4096,fp)) if(buffer[0]!='#') break;

		switch(type) {
	
			case 1:
			
				sscanf(buffer,"%d %d",&width,&height);
			
				q=raw=(unsigned char *)malloc(width*height*3);
			
				for(y=0;y!=height;y++) {
				
					for(x=0;x!=width;x++) {
				
						fscanf(fp,"%d",&z);
						z=z?0:255;
						
						*q++ = z;
						*q++ = z;
						*q++ = z;
					}
				}
				
				break;
			case 2:
			
				sscanf(buffer,"%d %d",&width,&height);
				
				fgets(buffer,4096,fp);
				sscanf(buffer,"%d",&max);
			
				q=raw=(unsigned char *)malloc(width*height*3);
			
				for(y=0;y!=height;y++) {
				
					for(x=0;x!=width;x++) {
				
						fscanf(fp,"%d",&z);
						z=z*255/max;
						
						*q++ = z;
						*q++ = z;
						*q++ = z;
					}
				}
				break;
			case 3:
				sscanf(buffer,"%d %d",&width,&height);
				
				fgets(buffer,4096,fp);
				sscanf(buffer,"%d",&max);

				q=raw=(unsigned char *)malloc(width*height*3);
			
				for(y=0;y!=height;y++) {
				
					for(x=0;x!=width;x++) {
				
						fscanf(fp,"%d %d %d",&r,&g,&b);

						*q++ = b*255/max;
						*q++ = g*255/max;
						*q++ = r*255/max;
					}
				}
				break;
			case 4:
			
				sscanf(buffer,"%d %d",&width,&height);
			
				q=raw=(unsigned char *)malloc(width*height*3);
			
				for(y=0;y!=height;y++) {
				
					fread(buffer,width/8,1,fp);

					for(x=0;x!=width/8;x++) {
					
						for(z=0;z!=8;z++) {
						
							if(buffer[x]&(0x80>>z))
							
								*q++ = 0,   
								*q++ = 0,   
								*q++ = 0;
							else
								*q++ = 255, 
								*q++ = 255, 
								*q++ = 255;
						}
					}
				}
				break;
			case 5:
			
				sscanf(buffer,"%d %d",&width,&height);
				
				fgets(buffer,4096,fp);
				sscanf(buffer,"%d",&max);			

				q=raw=(unsigned char *)malloc(width*height*3);
			
				for(y=0;y!=height;y++) {
				
					fread(buffer,width,1,fp);

					for(x=0;x!=width;x++) {
							
						z=buffer[x]*255/max;
						
						*q++ = z;
						*q++ = z;
						*q++ = z;
					}
				}
				break;
			case 6:
			
				sscanf(buffer,"%d %d",&width,&height);
				
				fgets(buffer,4096,fp);
				sscanf(buffer,"%d",&max);

				q=raw=(unsigned char *)malloc(width*height*3);
			
				for(y=0;y!=height;y++) {
				
					fread(buffer,width*3,1,fp);

					for(x=0;x!=width;x++) {
							
						r=buffer[x*3+0]*255/max;
						g=buffer[x*3+1]*255/max;
						b=buffer[x*3+2]*255/max;
						
						*q++ = b;
						*q++ = g;
						*q++ = r;
					}
				}
				break;
			
		}
	}

		
	if(raw)
		fprintf(stderr,"*read: PPM-P%d, %dx%d pixels\n",
			type,width,height);

	return raw?XSImageCreate(width,height,raw,XS_IMAGE_BGR24):NULL;
}

XSImage *bmpread(FILE *fp) {

	struct {
	
		unsigned short 	type		__attribute__ ((packed));
		unsigned int 	filesize	__attribute__ ((packed));
		unsigned short  reserved[2]	__attribute__ ((packed));
		unsigned int	offset		__attribute__ ((packed));
		unsigned int	header		__attribute__ ((packed));	
		unsigned int	width		__attribute__ ((packed));
		unsigned int	height		__attribute__ ((packed));	
		unsigned short	planes		__attribute__ ((packed));
		unsigned short	depth		__attribute__ ((packed));
		unsigned int 	format		__attribute__ ((packed));
		unsigned int	compsize	__attribute__ ((packed));
		unsigned int	hres		__attribute__ ((packed));
		unsigned int 	vres		__attribute__ ((packed));
		unsigned int	colors		__attribute__ ((packed));
		unsigned int	realcolors	__attribute__ ((packed));
		
	} bmpinfo;
	
	unsigned char 	*data,*p,*q,*raw=NULL;
	int  		size,x,y,z,width,height,depth;

	struct {
	
		unsigned char r,g,b,z;
	
	} palette[256];

	fseek(fp,0,SEEK_SET);
		
	fread(&bmpinfo,sizeof(bmpinfo),1,fp);

	fprintf(stderr,"check: BMP-%s, signature is '%u' (espected %u, status=%s)\n",
		bmpinfo.format?"compressed":"uncompressed",
		bmpinfo.type,
		((unsigned short)'B'<<8)+'M',
		(bmpinfo.type!=('B'<<8)+'M')?"failed":"sucess");
	
	if(bmpinfo.type==('B'<<8)+'M') 	return NULL;
	if(bmpinfo.width&(~0xfff))	return NULL;
	if(bmpinfo.height&(~0xfff)) 	return NULL;
	if(bmpinfo.format)		return NULL;
	if(bmpinfo.planes!=1)		return NULL;
	
	fprintf(stderr,"*read: BMP-%s, size=%dx%d, %d bits (offset = %d bytes)\n",
		bmpinfo.format?"compressed":"uncompressed",
		bmpinfo.width,
		bmpinfo.height,
		bmpinfo.depth,
		bmpinfo.offset);


	if(bmpinfo.depth!=24) fread(&palette,(1<<bmpinfo.depth)*4,1,fp);

	size=pad(bmpinfo.width*bmpinfo.depth/8)*bmpinfo.height;
	data=(char *)malloc(size);
	fread(data,size,1,fp);

	if(bmpinfo.filesize != ftell(fp))
		fprintf(stderr,"warning! BMP image truncated: %d bytes expected, %ld bytes read\n",
			bmpinfo.filesize,
			ftell(fp));

	width	=bmpinfo.width;
	height	=bmpinfo.height;
	depth	=bmpinfo.depth;

	p=data;

	raw=(unsigned char *)malloc(width*height*3);

	for(y=bmpinfo.height-1;y!=-1;y--) {

		q=raw+width*3*y;
		
		if(bmpinfo.depth==24) {

			for(x=0;x!=bmpinfo.width*3;x++) {
			
				q[x]=p[x];
			}
			
			p+=pad(bmpinfo.width*3);
		}
		
		if(bmpinfo.depth==8) {
		
			for(x=0;x!=bmpinfo.width;x++) {
			
				*q++=palette[p[x]].r;
				*q++=palette[p[x]].g;
				*q++=palette[p[x]].b;
			}
			
			p+=pad(bmpinfo.width);
		}
		
		if(bmpinfo.depth==4) {
		
			for(x=0;x!=bmpinfo.width;x++) {
			
				*q++=palette[x%2?p[x/2]&0xf:p[x/2]>>4].r;
				*q++=palette[x%2?p[x/2]&0xf:p[x/2]>>4].g;
				*q++=palette[x%2?p[x/2]&0xf:p[x/2]>>4].b;
			}
			
			p+=pad(bmpinfo.width/2);
		}

		if(bmpinfo.depth==1) {
		
			for(x=0;x!=bmpinfo.width/8;x++) {

				for(z=0;z!=8;z++) {

					*q++=(p[x]&(0x80>>z))?255:0;
					*q++=(p[x]&(0x80>>z))?255:0;
					*q++=(p[x]&(0x80>>z))?255:0;
				}
			}
			
			p+=pad(bmpinfo.width/8);
		}
	}
		
	free(data);

	return raw?XSImageCreate(width,height,raw,XS_IMAGE_BGR24):NULL;
}

XSImage *xpmread(FILE *fp) {

	char buffer[4096],*p,*q,index[16],color[16],*raw=NULL;
	unsigned short *s;

	int status=0,width,height,colors,type,palette[65536],x=0,y=0;

	fseek(fp,0,SEEK_SET);

	while(fgets(buffer,4096,fp)) {

		if(status&&status!=4) {
	
			p=strchr(buffer,'\"');
			
			if(!p) 	continue;
			else 	p++;
			
			q=strchr(p,'\"');
			*q=0;
							
			switch(status) {
			
				case 1:
					sscanf(p,"%d %d %d %d",
						&width,&height,&colors,&type);
					
					raw=(unsigned char *)malloc(width*height*3);
					
					status=2;
					break;
				case 2:
					sscanf(p,"%s c %s",index,color);
					
					if(type==2)
						palette[*((unsigned short *)index)]=strtol(color+1,NULL,16);
					if(type==1)
						palette[*((unsigned char *)index)]=strtol(color+1,NULL,16);
					x++;	
										
					if(x==colors) status=3;
					break;

				case 3:
				
					q=raw+width*y*3;
					s=(unsigned short *)p;
				
					for(x=0;x!=width;x++) {
					
						
					
						if(type==2) {
						
							*q++ = palette[*s]&0xff;
							*q++ = (palette[*s]>>8)&0xff;
							*q++ = palette[*s]>>16;
						
							s++;
						}
						
						if(type==1) {
						
							*q++ = palette[(unsigned)(*p)]&0xff;
							*q++ = (palette[(unsigned)(*p)]>>8)&0xff;
							*q++ = palette[(unsigned)(*p)]>>16;
							
							p++;
						}
					}
					
					y++;
					
					if(y==height) {
					
						status=4;

						fprintf(stderr,"*read: XPM-%d: %dx%d pixels, %d colors\n",
							type,width,height,colors);
					}

					break;
			}
		
		} else 
			if(strstr(buffer,"char")) 
				status=1;
	}

	if(status!=4) {

		fprintf(stderr,"check: XPM-%d: %dx%d pixels, %d colors (failed in stage %d)\n",
			type,width,height,colors,status);
		
		free(raw);
		
		return NULL;
	}

	return raw?XSImageCreate(width,height,raw,XS_IMAGE_BGR24):NULL;
}

XSImage *gifread(FILE *fp) {

	XSImage *aux=NULL;
	char     cmd[1024];
	FILE 	*fpx;
	
	sprintf(cmd,"giftopnm %s",d1->global->argv[count]);
	
	fpx=popen(cmd,"r");

	aux=ppmread(fpx);
	
	pclose(fpx);

	return aux;	
}

void XSImageGamma(XSImage *image,double gammavalue) {

        unsigned i,j;

        gammavalue=1.0/gammavalue;

        for(i=0;i!=256;i++) 
                for(j=0;j!=image->channels;j++) {

                        image->filter[j][i]=(unsigned char)(pow((double)image->filter[j][i]/255.0,gammavalue)*255.0);
                }
}

XSImage *readimage(char *name) {

	XSImage *aux=NULL;
	FILE 	*fp;
	
	fp=fopen(name,"r");
	
	if(fp) {

		if(!aux) aux=jpgread(fp);
		if(!aux) aux=gifread(fp);
		if(!aux) aux=bmpread(fp);
		if(!aux) aux=ppmread(fp);
		if(!aux) aux=xpmread(fp);
		if(!aux) aux=pcxread(fp);

		if(aux) aux->name = name;
		else	fprintf(stderr,"FAILED TO LOAD IMAGE %s\n",name);

		fclose(fp);
	}

	return aux;
}


void showimage(void) {

	int w,h;

	static int oldcount;
	
        if(count<1) count=1;
        if(count>=d1->global->argc) count=d1->global->argc-1;

	if(oldcount!=count) {

		oldimage=image;

		if((image=readimage(d1->global->argv[count]))) {

			XStoreName(d1->display,w2->window,image->name);

			fprintf(stderr,
				"sucess: %dx%d pixels read from %s (%s)\n",
				image->width,
				image->height,
				image->name,
				image->format==XS_IMAGE_RGB24?"RGB24":(image->format==XS_IMAGE_BGR24?"BGR24":"GRAY"));

			if(oldimage) {
			
				XSImageDestroy(oldimage);
				oldimage=NULL;
			}
	
		} else {
		
			XStoreName(d1->display,w2->window,"*** failed to open ***");
		
			fprintf(stderr,"%s: failed to open\n",d1->global->argv[count]);
			image=oldimage;
		}
	}

	if(max==1) {
		
		if(!inroot) {
		
			if(w1->parent->width*1.0/image->width < w1->parent->height*1.0/image->height)
			
				zoom=w1->parent->width*1.0/image->width;
			else
				zoom=w1->parent->height*1.0/image->height;
		}
	}

	if(max==2) {
		
		if(!inroot) {
		
				zoom=w1->parent->width*1.0/image->width;
		}
	}

	if(image) {
		
		if(inroot&&max) {
		
			w=d1->width;
			h=d1->height;
	
		} else {
		
			w=image->width*zoom;
			h=image->height*zoom;
		}

		old=pixmap; 
		pixmap=XSPixmapCreate(inroot?d1:w1,w,h,NULL,XS_PIXMAP_XSHM);

		if(igamma!=1.0) XSImageGamma(image,igamma);

		if(antialiasing) 
			XSImage2PixmapAA(w1,image,pixmap,True);
		else 
			XSImage2Pixmap(w1,image,pixmap,True);

		if(inroot) {
		
			XSetWindowBackgroundPixmap(w1->display,w1->desktop->window,pixmap->pixmap);
			XClearWindow(w1->display,w1->desktop->window);
		}

		XSetWindowBackgroundPixmap(w1->display,w1->window,pixmap->pixmap);

		w1->geometry.w=w1->width =w;
		w1->geometry.h=w1->height=h;
		
		if(sb) XSScrollCheck(w3);
		else {
		
			w1->event->xconfigure.x=(w1->parent->width -w)/2;
			w1->event->xconfigure.y=(w1->parent->height-h)/2;
			w1->event->xconfigure.width =w;
			w1->event->xconfigure.height=h; 		

			XMoveResizeWindow(w1->display,w1->window,
				(w1->parent->width -w)/2,(w1->parent->height-h)/2,
				w,h);
		}

		XClearWindow(w1->display,w1->window);
	}

	if(old) {
		
		XSPixmapDestroy(w1,old);
		old=NULL;
	}

	oldcount=count;
}

void foo(XSWidget *wid) {

        int key=XLookupKeysym(&wid->event->xkey,0);

//	printf("debug: keypressed=%d\n",key);

        switch(key) {

		case XK_Home:
			count=1;
			break;
			
		case XK_End:
			count=d1->global->argc-1;
			break;

		case XK_Page_Up:
			count-=10;
			break;
			
		case XK_Page_Down:
			count+=10;
			break;

		case XK_Down:
			count++;
			break;
		
		case XK_Up:
			count--;
			break;
			
                case XK_Escape:
                	XSWindowClose(w1);
                	return;
                	
                case '=':
                	zoom+=0.1;
                	break;
                case '-':
                	zoom-=0.1;
                	break;
                case 'm':
                	max=1;
                	break;
                case 'a':
                	antialiasing=!antialiasing;
                	break;
                case 'l':
                	max=2;
                	break;
		case 'n':
		case '1':
			max=0;
			zoom=1.0;
			break;
		case '2':
			max=0;
			zoom=2.0;
			break;
		case '3':
			max=0;
			zoom=3.0;
			break;
		case '4':
			max=0;
			zoom=4.0;
			break;
		case '5':
			max=0;
			zoom=0.5;
			break;
		case '6':
			max=0;
			zoom=1.5;
			break;
		case '7':
			max=0;
			zoom=0.75;
			break;
		default: 
			return;
        }

	if(zoom<0.1)  zoom=0.1;
	if(zoom>10.0) zoom=10.0;

	showimage();
}

void bar(XSWidget *wid) {

	XSWidgetCreate(wid);
	wid->on.create=NULL;

	showimage();
}

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

	int i,width=800,height=800;
	
	char *displayname;

	for(i=1;argv[i];i++) {
	
		if(!strcmp(argv[i],"-display"))  	displayname=argv[i+1];
		if(!strcmp(argv[i],"-root"))   		inroot=True;
		if(!strcmp(argv[i],"-antialiasing"))    antialiasing=True;
		if(!strcmp(argv[i],"-sb"))   		sb=True;
		if(!strcmp(argv[i],"-max"))   		max=True;
		if(!strcmp(argv[i],"-gamma"))    {

			if(argv[i+1]) igamma=atol(argv[i+1]);
		}
		if(!strcmp(argv[i],"-geometry")) {

			if(argv[i+1]) sscanf(argv[i+1],"%dx%d",&width,&height);
		}
	}
	
	d1=XSDesktop(getenv("DISPLAY"),argv,argc);

	w2=XSWindow(d1,0,0,width,height,argv[0]);
	w2->on.event[KeyPress]=foo;

	if(sb) {
	
		w3=XSScroll(w2,0,0,0,0,600,600);
		w1=w3->viewport;

	} else {
	
		w1=XSWindow(w2,0,0,0,0,"");
	}

	w1->on.create=bar;	
		w2->bgcolor=d1->black;

	while(XSCheckEvent(d1,XS_BLOCK));
	
	return 0;
}