#include	<aesbind.h>
#include	<gemfast.h>
#include	<obdefs.h>
#include	<macros.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<vdibind.h>

/* this should really go into a header file for debugging stuff		*/
#ifndef		NDEBUG
#ifdef		__TURBOC__
static void _DEBUG(const char *s,...) \
   {vfprintf(stderr,s,...);fprintf(stderr,"\n");}
/*static void _DEBUGL(const char *s,...) \
   {vfprintf(stderr,s,...);}*/
#else
static void _DEBUG(const char *s,...) \
   {vfprintf(stderr,s,(__VA_LIST__)__builtin_next_arg()); \
   fprintf(stderr,"\n");}
/*static void _DEBUGL(const char *s,...) \
   {vfprintf(stderr,s,(__VA_LIST__)__builtin_next_arg());}*/
#endif
#define 	DEBUG(x)	_DEBUG x
#define		DEBUGL(x)	_DEBUGL x
#else
#define		DEBUG(x)
#define		DEBUGL(x)
#endif

#define		BUFSIZE		512

typedef struct {
	unsigned int	compression : 1;
	unsigned int	reserved0	: 13;
	unsigned int	resolution	: 2;
	struct {
		unsigned int reserved1	: 5;
		unsigned int red		: 3;
		unsigned int reserved2	: 1;
		unsigned int green		: 3;
		unsigned int reserved3	: 1;
		unsigned int blue		: 3; } palette[16];
} DegasPic;

static int vr_pal_trnfm(int vdihandle,unsigned short *colcnv,
                        MFDB *source,MFDB *dest)
/* convert standard to non-standard format, even if the amount of	*/
/* planes does not match; handle palette conversion for vdi colors;	*/
/* currently no more than sixteen vdicolors are supported (MAXCOL);	*/
/* you can provide an array with sixteen entries for converting		*/
/* your input colors to vdi colors.									*/
/* Warning! if transformation between different colors/planes has	*/
/* to be performed, this algorithm is very slow and (temporarly)	*/
/* memory expensive!												*/
{
	#define		MAXPLANES		24
	#define		MAXCOL			16

	static unsigned long	vditab[MAXCOL];	/* cache for speed-up	*/
	static int				dest_planes = -1;
	static int				maxcol,trivial;
	unsigned short			bios2vdi[MAXCOL];
	unsigned long			col,lookup[MAXCOL];
	MFDB					tmp_mfdb = *source;

	if (source->fd_stand == dest->fd_stand ||	/*       ??? 		*/
	    !source->fd_stand ||					/* implementation	*/
	    (1UL << source->fd_nplanes) > MAXCOL ||	/* restriction!		*/
	    source->fd_nplanes > 8*sizeof(unsigned short) ||
	    dest->fd_nplanes > MAXPLANES) {			/*        "			*/
	    /* Call standard vr_trnfm() function if extended handling is*/
	    /* not needed or not possible...							*/
		vr_trnfm(vdihandle,source,dest);
		return(1); }
	if (dest_planes != dest->fd_nplanes) {	/* resolution change or	*/
		int					plane;			/* uninitialized vdi	*/
		MFDB				m_std,m_nstd;	/* lookup table...		*/
		unsigned long		d_std[MAXPLANES],d_nstd[MAXCOL];
		int					pxy[8],trans_col[2];
		dest_planes       = dest->fd_nplanes;
		maxcol = (unsigned short)(min(MAXCOL,(1UL << dest_planes)));
		m_nstd.fd_addr    = (long)d_nstd;
		m_nstd.fd_w       = sizeof(unsigned long)*8;
		m_nstd.fd_h       = 1;
		m_nstd.fd_wdwidth = sizeof(unsigned long)/2;
		m_nstd.fd_stand   = 0;
		m_nstd.fd_nplanes = dest_planes;
		m_nstd.fd_r1      =
		m_nstd.fd_r2      =
		m_nstd.fd_r3      = 0;
		m_std             = m_nstd;
		m_std.fd_addr     = (long)d_std;
		m_std.fd_stand    = 1;
		m_std.fd_nplanes  = 1; 				/* for vrt_cpyfm		*/
		d_std[0]          = ~0;
		memset(pxy,0,sizeof(pxy));
		trans_col[1]      = 0;
		memset(d_nstd,0,sizeof(d_nstd));
		for (trans_col[0] = 0; trans_col[0] < maxcol; trans_col[0]++) {
			pxy[4] = pxy[6] = (8*(int)sizeof(unsigned long))-trans_col[0]-1;
			vrt_cpyfm(vdihandle,MD_TRANS,pxy,&m_std,&m_nstd,trans_col); }
		m_std.fd_nplanes  = dest_planes;
		vr_trnfm(vdihandle,&m_nstd,&m_std);
		for (col = 0; col < maxcol; col++) {
			vditab[col] = 0;
			for (plane = 0; plane < dest_planes; plane++)
				if (d_std[plane] & (1UL << col))
					vditab[col] |= 1UL << plane; }
		for (col = maxcol; col < MAXCOL; col++)
			vditab[col] = vditab[maxcol-1]; }
	trivial = 1;
	if (colcnv) {
		int		black = min(15,(int)((1UL << source->fd_nplanes)-1));
		for (col = MAXCOL; col--;)
			bios2vdi[col] = col < black ? colcnv[col] :
			                col == black ? 1 : colcnv[col]; }
	for (col = MAXCOL; col--;)
		if ((lookup[col] = colcnv ?
		    vditab[bios2vdi[col]] : vditab[col]) != col)
			trivial = 0;
	if ((!trivial || source->fd_nplanes != dest_planes)) {
		if ((tmp_mfdb.fd_addr = (long)malloc(2L*
		    (long)source->fd_wdwidth*(long)source->fd_h*
	        (long)dest_planes)) == 0L) {
	        /* Sorry! Algorithm for splitting bit blocks and con-	*/
	        /* serving some memory not yet implemented!				*/
	        return(0); /* fail */ }
		else {
			unsigned long  		offset,index;
			unsigned short 		plane,bit;
			unsigned short		*s_word,*ptr,*d_word;
			s_word = (unsigned short *)source->fd_addr;
			d_word = (unsigned short *)tmp_mfdb.fd_addr;
			offset = (unsigned long)source->fd_h*
			         (unsigned long)source->fd_wdwidth;
			/* This loop *needs* optimization! Assembly language	*/
			/* might be a really good idea :-)						*/
			for (index = offset; index--; s_word++,d_word++) {
				for (plane = dest_planes, ptr = d_word;
				     plane--;
				     *ptr = 0, ptr += offset);
				for (bit = 8*sizeof(unsigned short); bit--;) {
					for (ptr = s_word, col = 0, plane = 0;
					     plane < source->fd_nplanes;
					     plane++, ptr += offset) {
						if (*ptr & (1U << bit))
							col |= 1UL << plane; }
					col = lookup[col%MAXCOL];
					for (ptr = d_word, plane = 0;
					     plane < dest_planes;
					     plane++, ptr += offset)
						if (col & (1U << plane))
							*ptr |= 1U << bit; } }
			tmp_mfdb.fd_nplanes = dest_planes;
			vr_trnfm(vdihandle,&tmp_mfdb,dest);
			free((void *)tmp_mfdb.fd_addr); } }
	else {
		vr_trnfm(vdihandle,source,dest); }
	return(1);
	
	#undef	MAXPLANES
	#undef	MAXCOL
}

static void separatebitplanes(MFDB *source,MFDB *dest)
/* basically the same function as vr_trnfm(nstd,std) for standard	*/
/* ST video modes; but you cannot call vr_trnfm() for converting	*/
/* Degas Elite images, if you do not want to make assumption on the	*/
/* video system...													*/
{
	unsigned short	*s,*d,*ptr;
	unsigned long	offset,index;
	int				plane;
	
	offset = (long)source->fd_wdwidth*(long)source->fd_h;
	s = (unsigned short *)source->fd_addr;
	d = (unsigned short *)dest->fd_addr;
	for (index = offset; index--; d++) {
		ptr = d;
		for (plane = source->fd_nplanes; plane--; ptr += offset)
			*ptr = *s++; }
	return;
}

static MFDB *loaddegas(int vdihandle,char *fname)
{
	#define DATA(x)		((unsigned char *)((DegasPic *)(x)+1))
	#define getbyte()	(pos < len ? buffer[pos++] : \
	       	         	 len <= 0 ? -1 : \
	       	         	 (pos = 0,len = (int)fread(buffer,1L,BUFSIZE,file)) \
	       	         	 <= 0 ? -1 : buffer[pos++])
	static unsigned short	colcnv[4][16] = {
				/* adapt this table as needed (your mileage may vary!)*/
				{ 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1},
				{ 0, 2, 2, 2, 2, 2, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1},
				{ 0, 2, 3, 6, 4, 7, 5, 8, 1, 2, 3, 6, 4, 7, 5, 1},
				{ 0, 2, 3, 6, 4, 7, 5, 8, 9,10,11,14,12,15,13, 1}};
	static unsigned char buffer[BUFSIZE];
	FILE		*file;
	int			planes,workout[57];
	int			pos,len;
	DegasPic	*source;
	MFDB		*m_source,m_dest;
	int			splanes,x,y,p,b,w,h,c,inc,data = 0;
	unsigned char *d;

	vq_extnd(vdihandle,1,workout);
	planes = workout[4];
	if ((file = fopen(fname,"rb")) == NULL) {
		DEBUG(("file not found"));
		return(0); }
	if ((source = (void *)calloc(1,sizeof(DegasPic)+32000)) == NULL) {
		DEBUG(("out of memory"));
		fclose(file);
		return(0); }
	if ((len = (int)fread(buffer,1L,BUFSIZE,file)) < sizeof(DegasPic) ||
	    source->resolution == 3) {
	    DEBUG(("illegal file format"));
		free(source);
		fclose(file);
		return(0); }
	*source = *(DegasPic *)&buffer;
	pos = (int)sizeof(DegasPic);
	splanes = 1 << (2 - source->resolution);
	if (!source->compression) {
		memcpy(DATA(source),buffer+pos,BUFSIZE-sizeof(DegasPic));
		if (fread(DATA(source)+BUFSIZE-sizeof(DegasPic),1L,
		    32000-BUFSIZE+sizeof(DegasPic),file) !=
		    32000-BUFSIZE+sizeof(DegasPic)) {
			DEBUG(("file too short"));
			free(source);
			fclose(file);
			return(0); }
		switch (source->resolution) {
			case 0:
				w = 320; h = 200; break;
			case 1:
				w = 640; h = 200; break;
			case 2:
				w = 640; h = 400; break;
			default:
				DEBUG(("this cannot happen!"));
				free(source);
				fclose(file);
				return(0); }
		fclose(file); }
	else {
		source->compression = 0;
		switch (source->resolution) {
			case 0:
				w = 320; h = 200; break;
			case 1:
				w = 640; h = 200; break;
			case 2:
				w = 640; h = 400; break;
			default:
				DEBUG(("this cannot happen!"));
				free(source);
				fclose(file);
				return(0); }
		d = DATA(source);
		c = 0;
		inc = 1;
		for (y = 0; y < h; y++, d += (w/8-2)*splanes)
			for (p = 0; p < splanes; p++,d -= (w/8)*splanes-2)
				for (x = 0; x < w/16; x++, d += 2*(splanes-1))
					for (b = 0; b < 2; b++,d++) {
						if (!c) {
							do {
								c = getbyte();
								if (c == 128)
									DEBUG(("NOP found in compressed data"));
								else if (c == -1) {
						eof:		DEBUG(("insufficient data"));
									free(source);
									fclose(file);
									return(0); }
							} while (c == 128);
							if (c > 128) {
								c = 257 - c; inc = 0;
								if ((data = getbyte()) == -1)
									goto eof; }
							else {
								c += 1; inc = 1; } }
						if (inc && ((data = getbyte()) == -1))
							goto eof;
						*d = data; c--; }
		fclose(file); }
	m_source			= (MFDB *)malloc(sizeof(MFDB)+
	                      (long)w/8L*(long)h*
                          (long)max(planes,splanes));
    if (m_source == NULL) {
    	free(DATA(source));
    	return(0); }
	m_source->fd_addr   = (long)DATA(source);
	m_source->fd_w      = w;
	m_source->fd_h      = h;
	m_source->fd_wdwidth= m_source->fd_w / 16;
	m_source->fd_stand  = 0;
	m_source->fd_nplanes= splanes;
	m_source->fd_r1     =
	m_source->fd_r2     =
	m_source->fd_r3     = 0;
	m_dest              = *m_source;
	m_dest.fd_addr      = (long)(m_source+1);
	m_dest.fd_stand     = 1;
	separatebitplanes(m_source,&m_dest);
	memcpy(DATA(source),m_source+1,
	       (long)w/8L*(long)h*(long)splanes);
	m_dest.fd_addr      = (long)DATA(source);
	m_dest.fd_stand     = 1;
	m_source->fd_addr   = (long)(m_source+1);
	m_source->fd_nplanes= planes;
	m_source->fd_stand  = 0;
 	if (!vr_pal_trnfm(vdihandle,colcnv[splanes > 4 ? 3 : splanes-1],
 	    &m_dest,m_source)) {
		free(source);
 		free((void *)m_source);
 		return(0); }
	free(source);
	return(m_source);
	
	#undef	DATA
	#undef	getbyte
}

int		main(int argc,char *argv[])
{
	int			vdihandle,workin[11],workout[57];
	int			x,y,w,h,pxy[8];
	MFDB		scr,*img;
	
	appl_init();
	memset(workin,0,sizeof(workin));
	workin[0] = vdihandle = graf_handle(&x,&y,&w,&h);
	workin[10] = 2;
	v_opnvwk(workin,&vdihandle,workout);
	x = y = 0;
	w = workout[0] + 1;
	h = workout[1] + 1;
	img = loaddegas(vdihandle,argc == 2 ? argv[1] : "DEMO.PC1");
	if (img != NULL) {
		wind_update(BEG_UPDATE);
		wind_update(BEG_MCTRL);
		form_dial(FMD_START,0,0,0,0,x,y,w,h);
		scr.fd_addr = 0;
		pxy[6] = pxy[2] = (pxy[4] = pxy[0] = x) + w - 1;
		pxy[7] = pxy[3] = (pxy[5] = pxy[1] = y) + h - 1;
		graf_mouse(M_OFF,0);
		vro_cpyfm(vdihandle,ALL_WHITE,pxy,&scr,&scr);
		pxy[2] = (pxy[0] = 0) + img->fd_w - 1;
		pxy[3] = (pxy[1] = 0) + img->fd_h - 1;
		pxy[6] = (pxy[4] = (w - img->fd_w) / 2) + img->fd_w - 1;
		pxy[7] = (pxy[5] = (h - img->fd_h) / 2) + img->fd_h - 1;
		vro_cpyfm(vdihandle,S_ONLY,pxy,img,&scr);
		graf_mouse(M_ON,0);
		evnt_keybd();
		form_dial(FMD_FINISH,0,0,0,0,x,y,w,h);
		wind_update(END_MCTRL);
		wind_update(END_UPDATE);
		free(img); }
	else
		DEBUG(("picture could not be loaded"));
	v_clsvwk(vdihandle);
	appl_exit();
	return(0);
}
