#include <stdio.h>
#include <string.h>

/* sna2tiff.c Version 0.5 */

char *remove_suffix(char *file);
void parseopt(int argc,int *arg,int *opt,char *argv[],char *args[],char *options[]);
void read_screen(unsigned char *c,FILE *fin);
void read_scr(unsigned char *c,FILE *fin);
void read_screenz80(unsigned char *c,FILE *fin);
void write_byte(unsigned char bits,unsigned char att,FILE *fout,int *outcnt);
char *find_field(int tag);
void set_field(int tag,unsigned long value);

int headernumber=190;
unsigned long width=256;
unsigned long height=192;

char header[]={77,77,0,42,0,0,0,8,/* IFD starts on byte 8 (after header) */
	0,13,			/* Fields in IFD*/
/*Imagewidth, 256*/
		1,0,0,3,0,0,0,1,1,0,0,0,
/*Imagelength, 192*/
		1,1,0,3,0,0,0,1,0,192,0,0,
/*Bits per sample, 4*/
		1,2,0,3,0,0,0,1,0,4,0,0,
/*Compression, none=1*/
		1,3,0,3,0,0,0,1,0,1,0,0,
/*Photometric Interpretation, Palette Colour=3*/
		1,6,0,3,0,0,0,1,0,3,0,0,
/*Strip offsets, image starts at byte 512*/
		1,17,0,4,0,0,0,1,0,0,2,0,
/*Orientation, first pixel top left*/
		1,18,0,3,0,0,0,1,0,1,0,0,
/*Rows per strip, same as Height*/
		1,22,0,4,0,0,0,1,0,0,0,192,
/*Strip byte counts, 24576 bytes of image data 256*192/2 */
		1,23,0,4,0,0,0,1,0,0,96,0,
/*X resolution, to conform with TIFF6.0 only*/
		1,26,0,5,0,0,0,1,0,0,0,172,
/*Y resolution, to conform with TIFF6.0 only*/
		1,27,0,5,0,0,0,1,0,0,0,180,
/*Resolution Unit, again for TIFF 6.0, might as well use inches*/
		1,40,0,3,0,0,0,1,0,2,0,0,
/*Plonk Colour Map at byte 256*/
		1,64,0,3,0,0,0,48,0,0,1,0,
/*No subsequent IFD*/
	0,0,0,0,0,0,
/*X & Y Resolution data, use 72dpi (why?!?  :-)*/
	0,0,0,72,0,0,0,1,
	0,0,0,72,0,0,0,1,
/* Bit of padding */
	0,0,0,0,0,0,0,0,0,0,0,0,0
};


void main(int argc,char*argv[])
{
	int loop,outcnt=0,a,b,e,f;
	static char filen[1024];
	static char basename[1024];
	static unsigned char c[6913],bcol;
	unsigned char outchr,scrnbyte;
	int z80=0,scrflg=0;
	int bwidth=0,bordloop;
	unsigned char attrib,att1,att2,brite;
	int arg=1,opt=3;
	char *args[15],*options[15];
	FILE *fin,*fout;
	if (argc<2)
	{
		printf ("Usage: %s input_file <output_file> <-z/-s> <-bn>\n",argv[0]);
		exit(0);
	}

	/*Separate options from filenames*/
	parseopt(argc,&arg,&opt,argv,args,options);

	
	/*Search through options and set flags accordingly*/
	for (loop=0;loop<opt;loop++)
	{
		switch (options[loop][1])
		{
			case 'b':if (options[loop][2]>='0' && options[loop][2]<='9') bwidth=options[loop][2]-'0';
				else bwidth=2;
				printf("Border width: %d pixels\n",bwidth*8);
				width+=bwidth*16;
				height+=bwidth*16;
				break;
			case 'z': z80=1;
				break;
			case 's': scrflg=1;
				break;
			default: printf("Warning: Option %s not recognised...ignored\n",options[loop]);
		}
	}

	if (arg<1)
	{
		printf ("Usage: %s input_file <output_file> <-z/-s>\n",argv[0]);
		exit(0);
	}
	if ((fin=fopen(args[0],"rb"))==NULL)
	{
		printf("Error opening snapshot %s\n",args[0]);
		exit(0);
	}

	/* Extract name */

	strcpy(filen,args[0]);
	strcpy(basename,remove_suffix(filen));
	if (arg==1)
	{
		/* No output file specified...make one up */
		strcpy(filen,basename);
		strcat(filen,".tif");
	}
	else
	{
		/* Output file specified on command line */
		strcpy(filen,args[1]);
	}
	if ((fout=fopen(filen,"wb"))==NULL)
	{
		printf("Error opening file %s for output\n",filen);
		exit(0);
	}

	/* Read in bytes of spectrum screen */
	printf("Conversion assuming: ");
	if (scrflg==1)
	{
		read_scr(c,fin);
		bcol=0;
	}
	else
	{
		if (z80==1) read_screenz80(c,fin);
		else read_screen(c,fin);
		bcol=c[6912];
	}

	/* Reset header for border */
	set_field(256,width);
	set_field(257,height);
	set_field(278,height);
	set_field(279,width*height/2);

	/* Write header data */
	for (a=0;a<=headernumber;a++)
	{
		putc(header[a],fout);
		outcnt++;
	}

	/* Write out colourmap (in right place)*/
	while (outcnt<256)
	{
		putc(0,fout);
		outcnt++;
	}
	for (attrib=4;attrib>=1;attrib/=2)
	{
		for (att1=0;att1<=15;att1++)
		{
			/*Use 255 for BRIGHT colours, else 216*/
			if (att1<=7) att2=216;
			else att2=255;
			outchr=att2*((att1 & attrib)/attrib);
			putc(outchr,fout);
			outcnt++;
			putc(outchr,fout);
			outcnt++;
		}
	}
	/*Cue up pixmap*/
	while (outcnt<512)
	{
		putc(0,fout);
		outcnt++;
	}
	/* Now write out RGB data */
	/* First the border-if bwidth>0 */
	for(loop=0;loop<bwidth*8;loop++)
	{
		for (a=0;a<width/8;a++)
		{
			write_byte(255,bcol,fout,&outcnt);
		}
	}
	/* Three blocks of screen bitmap, 2048 bytes per block */
	for (loop=0;loop<=6143;loop+=2048)
	{
		/* Eight lines per block, 32 characters per line */
		for(a=0;a<=255;a+=32)
		{
			/* Sort out lines into sensible order */
			for(b=0;b<=2045;b+=256)
			{
				for (bordloop=0;bordloop<bwidth;bordloop++) write_byte(255,bcol,fout,&outcnt);
				for(e=0;e<=31;e++)
				{
					scrnbyte=c[loop+a+b+e];
					attrib=c[6144+e+a+((loop/2048)*256)];
					write_byte(scrnbyte,attrib,fout,&outcnt);
				}
				for (bordloop=0;bordloop<bwidth;bordloop++) write_byte(255,bcol,fout,&outcnt);
			}
		}
	}
	for(loop=0;loop<bwidth*8;loop++)
	{
		for (a=0;a<width/8;a++)
		{
			write_byte(255,bcol,fout,&outcnt);
		}
	}
}

void set_field(int tag,unsigned long value)
{
	char *field;
	unsigned char test;
	unsigned long loop,ander,temp;
	field=find_field(tag);
	if (field==(NULL)) return;
	test=*(field+3);
	loop=0;
	if (test==3)
	{
		loop=256;
		ander=65280;
	}
	if (test==4)
	{
		loop=16777216;
		ander=16777216*255;
	}
	if (loop==0) return;
	field+=8;
	for(;loop>0;loop/=256)
	{
		temp=(value&ander)/loop;
		test=(char)temp;
		*field++=test;
		ander/=256;
	}
}
	
char *find_field(int tag)
{
	int test,loop;
	for(loop=10;loop<headernumber;loop+=12)
	{
		test=(int)header[loop+1]+256*(int)header[loop];
		if (test==tag) return(&header[loop]);
	}
	return (NULL);
}

void read_screen(unsigned char *c,FILE *fin)
{
	/*For snap (.sna) files, could be converted for z80's etc.*/
	printf("Snapshot (.sna) format\n");
	/* Clear out non-screen data */
	fread (c,1,27,fin);
	/* Border colour */
	c[6912]=c[26];
	/* Screen data here */
	fread (c,1,6912,fin);
}

void read_scr(unsigned char *c,FILE *fin)
{
	/*For screen (.scr) files, which I have never seen, but someone
	                  assures me that they exist*/
	printf("Screen memory dump\n");
	/* Screen data here */
	fread (c,1,6912,fin);
}

void read_screenz80(unsigned char *c,FILE *fin)
{
	/*For z80 files*/
	int loop,nextlength,loopt,justrun;
	unsigned char header[89],run,byte,runloop;
	printf("Z80 format\n");
	/* Clear out old style header */
	fread(header,1,30,fin);
	/* Border colour */
	c[6912]=(header[12]&14)/2;
	/*Determine if version<=1.45*/
	if (header[6]==0 && header[7]==0)
	{
		/*Newer version*/
		nextlength=(header[30]=fgetc(fin))+(header[31]=fgetc(fin))*256;
		printf("New format, extra length %d\n",nextlength+2);
		fread(&header[32],1,nextlength,fin);
		/*Use justrun as a flag for the correct block*/
		justrun=0;
		while (justrun==0)
		{
			fread(&header[86],1,3,fin);
			/* Correct block? */
			if (header[88]==8)
			{
				justrun=1;
			}
			else
			{
				nextlength=header[87];
				nextlength=nextlength*256+(int)header[86];
				for (loop=nextlength;loop>0;loop-=6912)
				{
					if (loop>6912) fread(c,1,6912,fin);
					else fread(c,1,loop,fin);
				}
			}
		}
		
	} else printf("Old Z80 format\n");
	loop=1;
	/*Now use justrun to indicate if a compressed sequence has just been
processed; otherwise could screw up after a compressed sequence of ED's*/
	justrun=0;
	c[0]=fgetc(fin);
	while (loop<6912)
	{
		c[loop]=fgetc(fin);
		loop++;
		if ((c[loop-1]==237) && (c[loop-2]==237) && (justrun==0))
		{
			loopt=loop-2;
			run=fgetc(fin);
			byte=fgetc(fin);
			justrun=1;
			for (runloop=0;runloop<run;runloop++)
			{
				if (loopt<6912) c[loopt]=byte;
				loopt++;
			}
			loop=loopt;
		}
		else 	justrun=0;
	}
}


/* Knock off .sna (or any other, eg .snapshot, .z80) suffix */

char *remove_suffix(char *file)
{
	static char ret[1024];
	int length;
	int loop;
	length=strlen(file);
	for (loop=length-1;loop>=1;loop--)
	{
		if (file[loop]!='.') continue;
		length=loop;
		break;
	}
	for (loop=0;loop<length;loop++)
	{
		ret[loop]=file[loop];
		ret[loop+1]=0;
	}
	return(ret);
}

void parseopt(int argc,int *arg,int *opt,char *argv[],char *args[],char *options[])
{
	/*Returns pointers to options and non-option arguments, along
	with counts of each. At the moment options must be grouped
	together either at the start or end of the command line argument list*/

	int loop,loop2,optfirst=0;
	if (argv[1][0]=='-')
	{
		for (loop=0;loop<argc-1;loop++) options[loop]=argv[loop+1];
		optfirst=1;
		*opt=argc-1;
		*arg=0;
	}
	else
	{
		for (loop=0;loop<argc-1;loop++) args[loop]=argv[loop+1];
		*arg=argc-1;
		*opt=0;
	}
	for (loop=2;loop<argc;loop++)
	{
		if ((argv[loop][0]=='-' && optfirst==0)||(argv[loop][0]!='-' && optfirst==1))
		{
			/*Crossover between types*/
			if (optfirst==1)
			{
				for (loop2=loop;loop2<argc;loop2++) args[loop2-loop]=argv[loop2];
				*opt=loop-1;
				*arg=argc-loop;
			}
			else
			{
				for (loop2=loop;loop2<argc;loop2++) options[loop2-loop]=argv[loop2];
				*arg=loop-1;
				*opt=argc-loop;
			}
			/*That's it!*/
			break;
		}
	}
}

void write_byte(unsigned char bits,unsigned char attrib,FILE *fout,int *outcnt)
{
	/*Take a byte from screen bitmap and the related attribute byte, and
		write relevant colours to file*/

	unsigned char and,brite,att1,att2,out,mult,a;

	/*Move up the colour table if BRIGHT is set*/
	brite=(attrib&64)/8;

	/*Extract Spectrum GRB attribute (f/g and b/g),
		convert to <BRIGHT>RGB format*/
	att1=((attrib&1)|((attrib&2)*2)|((attrib&4)/2)) | brite;
	att2=(((attrib&8)|((attrib&16)*2)|((attrib&32)/2))/8) | brite;
	a=1;
	out=0;
	for (and=128;and>=1;and/=2)
	{
		mult=a*15+1;
		if (bits&and) out = out|(att1*mult);
		else out = out|(att2*mult);
		a=1-a;
		if (a==1)
		{
			fputc(out,fout);
			(*outcnt)++;
			out=0;
		}
	}
}
