/*

 KFax -- A G3/G4 Fax Viewer

 Copyrigh (C) 1997 Bernd Johannes Wuebben
 wuebben@math.cornell.edu
 wuebben@kde.org

 Based on:
 
 viewfax - g3/g4 fax processing software.
 Copyright (C) 1990, 1995  Frank D. Cringle.

 This file is part of viewfax - g3/g4 fax processing software.
     
 viewfax is free software; you can redistribute it and/or modify it
 under the terms of the GNU General Public License as published by the
 Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.
     
 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.
     
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 

*/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>

#include <tqglobal.h>
#include <tqdir.h>
#include <tqfile.h>
#include <tqevent.h>
#include <tqprinter.h>
#include <tqstring.h>

#include <tdecmdlineargs.h>
#include <tdelocale.h>

#include "kfax.h"
#include "faxexpand.h"
#include "version.h"
#include "viewfax.h"

/* NewImage() needs to fiddle with the Display structure */
#define XLIB_ILLEGAL_ACCESS
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
#include <X11/cursorfont.h>


#define VIEWFAXVERSION "2.3"


/* If moving the image around with the middle mouse button is jerky or
   slow, try defining USE_MOTIONHINT.  It may help (it may also make
   things worse - it depends on the server implementation) */
#undef USE_MOTIONHINT



/* access the 'extra' field in a pagenode */

#define Pimage(p)	((XImage *)(p)->extra)


/* forward declarations */

XImage *FlipImage(XImage *xi);
XImage *MirrorImage(XImage *xi);
XImage *NewImage(int w, int h, char *data, int bit_order);
XImage *RotImage(XImage *Image);
XImage *ZoomImage(XImage *Big);

void FreeImage(XImage *Image);

static int release(int quit);


/* X variables */

extern  Cursor WorkCursor;
extern  Cursor ReadyCursor;
extern  Cursor MoveCursor;
extern  Cursor LRCursor;
extern  Cursor UDCursor;

extern bool have_no_fax;
extern Display* qtdisplay ;
extern Window  qtwin;
extern Window Win;
extern int qwindow_width;
extern int qwindow_height;

struct pagenode *firstpage, *lastpage, *thispage, *helppage, *auxpage;
struct pagenode defaultpage;

Display *Disp;

int Default_Screen;
int verbose = 0;

int abell = 1;			/* audio bell */
int vbell = 0;			/* visual bell */
bool have_cmd_opt = FALSE;

size_t Memused = 0;		/* image memory usage */
static size_t Memlimit = 8*1024*1024;	/* try not to exceed */

#undef min
#undef max
#define min(a,b)	((a)<(b)?(a):(b))
#define max(a,b)	((a)>(b)?(a):(b))

#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif

/* OK, OK - this is a dreadful hack.  But it adequately distinguishes
   modern big- and little- endian hosts.  We only need this to set the
   byte order in XImage structures */

static union { t32bits i; unsigned char b[4]; } bo;
#define ByteOrder	bo.b[3]

static char Banner[] =
"KFax version " KFAXVERSION "\tCopyright (C) 1997, Bernd Johannes Wuebben\n";

/*"KFax is based on:\n"
"viewfax " VERSION ":\t\tCopyright (c) 1990,  1995 Frank D. Cringle.\n"
"libtiff v 3.4beta:\tCopyright (c) 1988 - 1955 Sam Leffler\n"
"                  \tCopyright (c) 1991 - 1995 Silicon Graphics, Inc.\n\n" 
"KFax comes with ABSOLUTELY NO WARRANTY; for details see the\n"
"file \"COPYING\" in the distribution directory.\n";*/

XEvent Event;
XImage *Image, *Images[MAXZOOM];
XSizeHints size_hints;

Time Lasttime = 0; 

struct pagenode *viewpage = 0;  

int viewfaxmain()
{
    int banner = 0;
    int have_height = 0;

    bo.i = 1;
    defaultpage.vres = -1;
    have_no_fax = TRUE;

    /* TODO Do I need to know this: */
    defaultpage.expander = g31expand;

    TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs();

    if (args->isSet("height"))
    {
       have_height = 1;
       defaultpage.height = args->getOption("height").toInt();
    }

    if (args->isSet("2"))
    {
       defaultpage.expander = g32expand;
       if(!have_height)
          defaultpage.height = 2339;
    }

    if (args->isSet("4"))
    {
       defaultpage.expander = g4expand;
       if(!have_height)
          defaultpage.height = 2155;
    }

    if (args->isSet("invert"))
    {
       defaultpage.inverse = 1;
    }

    if (args->isSet("landscape"))
    {
       defaultpage.orient |= TURN_L;
    }

    if (args->isSet("fine"))
    {
       defaultpage.vres = 1;
    }

    if (!args->isSet("rmal")) // "normal" is interpreted as "no"+"rmal" :-)
    {
       defaultpage.vres = 0;
    }
 
    if (args->isSet("reverse"))
    {
       defaultpage.lsbfirst = 1;
    }

    if (args->isSet("upsidedown"))
    {
       defaultpage.orient |= TURN_U;
    }

    if (args->isSet("width"))
    {
       defaultpage.width = args->getOption("width").toInt();
    }

    TQCString mem = args->getOption("mem");
    Memlimit = atoi(mem.data());
    switch(mem[mem.length()-1]) {
	    case 'M':
	    case 'm':
		Memlimit *= 1024;
	    case 'K':
	    case 'k':
		Memlimit *= 1024;
    }

    if (defaultpage.expander == g4expand && defaultpage.height == 0) {
	TDECmdLineArgs::usage("--height value is required to interpret raw g4 faxes\n");
    }

    firstpage = lastpage = thispage = helppage = auxpage =  0;

    for (int i = 0; i < args->count(); i++){
	loadfile(TQFile::decodeName(args->arg(i)));
    }
    args->clear();

    if (banner ) {
      fputs(Banner, stderr);
      exit(1);
    }

    have_no_fax = (firstpage == 0);

    Disp = qtdisplay;
    Default_Screen = XDefaultScreen(qtdisplay);

    return 1;
}


/* Change orientation of all following pages */
void TurnFollowing(int How, struct pagenode *pn)
{
    while (pn) {
	if (Pimage(pn)) {
	    FreeImage(Pimage(pn));
	    pn->extra = 0;
	}
	pn->orient ^= How;
	pn = pn->next;
    }
}

static void
drawline(pixnum *run, int LineNum, struct pagenode *pn)
{
    t32bits *p, *p1;		/* p - current line, p1 - low-res duplicate */
    pixnum *r;			/* pointer to run-lengths */
    t32bits pix;		/* current pixel value */
    t32bits acc;		/* pixel accumulator */
    int nacc;			/* number of valid bits in acc */
    int tot;			/* total pixels in line */
    int n;

    LineNum += pn->stripnum * pn->rowsperstrip;
    if (LineNum >= pn->height) {
       if (verbose && LineNum == pn->height)
           fputs("Height exceeded\n", stderr);
       return;
    }
    
    p = (t32bits *) (Pimage(pn)->data + LineNum*(2-pn->vres)*Pimage(pn)->bytes_per_line);
    p1 =(t32bits *)( pn->vres ? 0 : p + Pimage(pn)->bytes_per_line/sizeof(*p));
    r = run;
    acc = 0;
    nacc = 0;
    pix = pn->inverse ? ~0 : 0;
    tot = 0;
    while (tot < pn->width) {
	n = *r++;
	tot += n;
        /* Watch out for buffer overruns, e.g. when n == 65535.  */
        if (tot > pn->width)
            break;
	if (pix)
	    acc |= (~(t32bits)0 >> nacc);
	else if (nacc)
	    acc &= (~(t32bits)0 << (32 - nacc));
	else
	    acc = 0;
	if (nacc + n < 32) {
	    nacc += n;
	    pix = ~pix;
	    continue;
	}
	*p++ = acc;
	if (p1)
	    *p1++ = acc;
	n -= 32 - nacc;
	while (n >= 32) {
	    n -= 32;
	    *p++ = pix;
	    if (p1)
		*p1++ = pix;
	}
	acc = pix;
	nacc = n;
	pix = ~pix;
    }
    if (nacc) {
	*p++ = acc;
	if (p1)
	    *p1++ = acc;
    }
}

static int
GetPartImage(struct pagenode *pn, int n)
{
    unsigned char *Data = getstrip(pn, n);

    if (Data == 0)
	return 3;
    pn->stripnum = n;
    (*pn->expander)(pn, drawline);
    free(Data);
    return 1;
}

int GetImage(struct pagenode *pn){
    int i;

    if (pn->strips == 0) {

      /*printf("RAW fax file\n");*/

	/* raw file; maybe we don't have the height yet */
	unsigned char *Data = getstrip(pn, 0);
	if (Data == 0){

	  return 0;
	}
	pn->extra = NewImage(pn->width, pn->vres ?
			     pn->height : 2*pn->height, 0, 1);

//printf("height = %d\n",pn->height);
//printf("setting height to %d\n", pn->vres ? pn->height : 2*pn->height);

	if(pn->extra == 0)
	  return 0;

	(*pn->expander)(pn, drawline);
    }
    else {
	/* multi-strip tiff */
      /*printf("MULTI STRIP TIFF fax file\n");*/

	pn->extra = NewImage(pn->width, pn->vres ?
			     pn->height : 2*pn->height, 0, 1);

	if(pn->extra == 0)
	  return 0;
	pn->stripnum = 0;
	for (i = 0; i < pn->nstrips; i++){

	  int k =GetPartImage(pn, i); 
	  
	  if ( k == 3 ){
	    FreeImage(Pimage(pn));
	    return k;
	  }

	}
    }
    if (pn->orient & TURN_U)
	pn->extra = FlipImage(Pimage(pn));
    if (pn->orient & TURN_M)
	pn->extra = MirrorImage(Pimage(pn));
    if (pn->orient & TURN_L)
	pn->extra = RotImage(Pimage(pn));
    if (verbose) printf("\tmemused = %d\n", Memused);

/*
if(pn->extra)
  printf("pn->extra !=0 %s\n",pn->name);
else
  printf("pn->extra ==0 %s\n",pn->name);
  */

    return 1;
}



/* run this region through perl to generate the zoom table:
$lim = 1;
@c = ("0", "1", "1", "2");
print "static unsigned char Z[] = {\n";
for ($i = 0; $i < 16; $i++) {
    for ($j = 0; $j < 16; $j++) {
	$b1 = ($c[$j&3]+$c[$i&3]) > $lim;
	$b2 = ($c[($j>>2)&3]+$c[($i>>2)&3]) > $lim;
	printf " %X,", ($b2 << 1) | $b1;
    }
    print "\n";
}
print "};\n";
*/

static unsigned char Z[] = {
 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 2, 2, 3,
 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 2, 3, 3, 3,
 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 2, 3, 3, 3,
 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3,
 0, 0, 0, 1, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3,
 0, 1, 1, 1, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3,
 0, 1, 1, 1, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3,
 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
 0, 0, 0, 1, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3,
 0, 1, 1, 1, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3,
 0, 1, 1, 1, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3,
 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3,
 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3,
 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3,
 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
};

#define nib(n,w)	(((w)>>((n)<<2))&15)
#define zak(a,b)	Z[(a<<4)|b]

/* 2 -> 1 zoom, 4 pixels -> 1 pixel
   if #pixels <= $lim (see above), new pixel is white,
   else black.
*/

XImage *ZoomImage(XImage *Big){

    XImage *Small;
    int w, h;
    int i, j;

    XDefineCursor(Disp, Win, WorkCursor);
    XFlush(Disp);
    w = (Big->width+1) / 2;
    h = (Big->height+1) / 2;
    Small = NewImage(w, h, 0, Big->bitmap_bit_order);
    if(Small == 0)
      return 0;

    Small->xoffset = (Big->xoffset+1)/2;
    for (i = 0; i < Big->height; i += 2) {
	t32bits *pb0 = (t32bits *) (Big->data + i * Big->bytes_per_line);
	t32bits *pb1 = pb0 + ((i == Big->height-1) ? 0 : Big->bytes_per_line/4);
	t32bits *ps = (t32bits *) (Small->data + i * Small->bytes_per_line / 2);
	for (j = 0; j < Big->bytes_per_line/8; j++) {
	    t32bits r1, r2;
	    t32bits t0 = *pb0++;
	    t32bits t1 = *pb1++;
	    r1 = (zak(nib(7,t0),nib(7,t1))<<14) |
		 (zak(nib(6,t0),nib(6,t1))<<12) |
		 (zak(nib(5,t0),nib(5,t1))<<10) |
		 (zak(nib(4,t0),nib(4,t1))<<8) |
		 (zak(nib(3,t0),nib(3,t1))<<6) |
		 (zak(nib(2,t0),nib(2,t1))<<4) |
		 (zak(nib(1,t0),nib(1,t1))<<2) |
		 (zak(nib(0,t0),nib(0,t1)));
	    t0 = *pb0++;
	    t1 = *pb1++;
	    r2 = (zak(nib(7,t0),nib(7,t1))<<14) |
		 (zak(nib(6,t0),nib(6,t1))<<12) |
		 (zak(nib(5,t0),nib(5,t1))<<10) |
		 (zak(nib(4,t0),nib(4,t1))<<8) |
		 (zak(nib(3,t0),nib(3,t1))<<6) |
		 (zak(nib(2,t0),nib(2,t1))<<4) |
		 (zak(nib(1,t0),nib(1,t1))<<2) |
		 (zak(nib(0,t0),nib(0,t1)));
	    *ps++ = (Big->bitmap_bit_order) ?
		(r1<<16)|r2 : (r2<<16)|r1;
	}
	for ( ; j < Small->bytes_per_line/4; j++) {
	    t32bits r1;
	    t32bits t0 = *pb0++;
	    t32bits t1 = *pb1++;
	    r1 = (zak(nib(7,t0),nib(7,t1))<<14) |
		 (zak(nib(6,t0),nib(6,t1))<<12) |
		 (zak(nib(5,t0),nib(5,t1))<<10) |
		 (zak(nib(4,t0),nib(4,t1))<<8) |
		 (zak(nib(3,t0),nib(3,t1))<<6) |
		 (zak(nib(2,t0),nib(2,t1))<<4) |
		 (zak(nib(1,t0),nib(1,t1))<<2) |
		 (zak(nib(0,t0),nib(0,t1)));
	    *ps++ = (Big->bitmap_bit_order) ?
		(r1<<16) : r1;
	}
    }
    XDefineCursor(Disp, Win, ReadyCursor);
    return Small;
}

XImage *FlipImage(XImage *Image){

    XImage *New = NewImage(Image->width, Image->height,
			   Image->data, !Image->bitmap_bit_order);
    if(New == 0)
      return 0;

    t32bits *p1 = (t32bits *) Image->data;
    t32bits *p2 = (t32bits *) (Image->data + Image->height *
			     Image->bytes_per_line - 4);

    /* the first shall be last ... */
    while (p1 < p2) {
	t32bits t = *p1;
	*p1++ = *p2;
	*p2-- = t;
    }

    /* let Xlib twiddle the bits */
    New->xoffset = 32 - (Image->width & 31) - Image->xoffset;
    New->xoffset &= 31;

    Image->data = 0;
    FreeImage(Image);
    return New;
}

XImage *MirrorImage(XImage *Image){

    int i;
    XImage *New = NewImage(Image->width, Image->height,
			   Image->data, !Image->bitmap_bit_order);
    if(New == 0)
      return 0;

    /* reverse order of 32-bit words in each line */
    for (i = 0; i < Image->height; i++) {
	t32bits *p1 = (t32bits *) (Image->data + Image->bytes_per_line * i);
	t32bits *p2 = p1 + Image->bytes_per_line/4 - 1;
	while (p1 < p2) {
	    t32bits t = *p1;
	    *p1++ = *p2;
	    *p2-- = t;
	}
    }

    /* let Xlib twiddle the bits */
    New->xoffset = 32 - (Image->width & 31) - Image->xoffset;
    New->xoffset &= 31;

    Image->data = 0;
    FreeImage(Image);
    return New;
}

XImage *RotImage(XImage *Image){

    XImage *New;
    int w = Image->height;
    int h = Image->width;
    int i, j, k, shift;
    int order = Image->bitmap_bit_order;
    int offs = h+Image->xoffset-1;

    New = NewImage(w, h, 0, 1);
    if (New == 0)
      return 0;

    k = (32 - Image->xoffset) & 3;
    for (i = h - 1; i && k; i--, k--) {
	t32bits *sp = (t32bits *) Image->data + (offs-i)/32;
	t32bits *dp = (t32bits *) (New->data+i*New->bytes_per_line);
	t32bits d0 =0;
	shift = (offs-i)&31;
	if (order) shift = 31-shift;
	for (j = 0; j < w; j++) {
	    t32bits t = *sp;
	    sp += Image->bytes_per_line/4;
	    d0 |= ((t >> shift) & 1);
	    if ((j & 31) == 31)
		*dp++ = d0;
	    d0 <<= 1;;
	}
	if (j & 31)
	    *dp++ = d0<<(31-j);
    }
    for ( ; i >= 3; i-=4) {
	t32bits *sp = (t32bits *) Image->data + (offs-i)/32;
	t32bits *dp0 = (t32bits *) (New->data+i*New->bytes_per_line);
	t32bits *dp1 = dp0 - New->bytes_per_line/4;
	t32bits *dp2 = dp1 - New->bytes_per_line/4;
	t32bits *dp3 = dp2 - New->bytes_per_line/4;
	t32bits d0=0 , d1=0, d2 =0, d3 =0;
	shift = (offs-i)&31;
	if (order) shift = 28-shift;
	for (j = 0; j < w; j++) {
	    t32bits t = *sp >> shift;
	    sp += Image->bytes_per_line/4;
	    d0 |= t & 1; t >>= 1;
	    d1 |= t & 1; t >>= 1;
	    d2 |= t & 1; t >>= 1;
	    d3 |= t & 1; t >>= 1;
	    if ((j & 31) == 31) {
		if (order) {
		    *dp0++ = d3;
		    *dp1++ = d2;
		    *dp2++ = d1;
		    *dp3++ = d0;
		}
		else {
		    *dp0++ = d0;
		    *dp1++ = d1;
		    *dp2++ = d2;
		    *dp3++ = d3;
		}
	    }
	    d0 <<= 1; d1 <<= 1; d2 <<= 1; d3 <<= 1;
	}
	if (j & 31) {
	    if (order) {
		*dp0++ = d3<<(31-j);
		*dp1++ = d2<<(31-j);
		*dp2++ = d1<<(31-j);
		*dp3++ = d0<<(31-j);
	    }
	    else {
		*dp0++ = d0<<(31-j);
		*dp1++ = d1<<(31-j);
		*dp2++ = d2<<(31-j);
		*dp3++ = d3<<(31-j);
	    }
	}
    }
    for (; i >= 0; i--) {
	t32bits *sp = (t32bits *) Image->data + (offs-i)/32;
	t32bits *dp = (t32bits *) (New->data+i*New->bytes_per_line);
	t32bits d0=0;
	shift = (offs-i)&31;
	if (order) shift = 31-shift;
	for (j = 0; j < w; j++) {
	    t32bits t = *sp;
	    sp += Image->bytes_per_line/4;
	    d0 |= ((t >> shift) & 1);
	    if ((j & 31) == 31)
		*dp++ = d0;
	    d0 <<= 1;;
	}
	if (j & 31)
	    *dp++ = d0<<(31-j);
    }
    FreeImage(Image);
    return New;
}

/* release some non-essential memory or abort */
#define Try(n)								\
    if (n && n != thispage && n->extra) {				\
	FreeImage((XImage*)n->extra);	\
	n->extra = 0;						\
	return 1;							\
    }

static int
release(int quit)
{
  (void) quit;

    struct pagenode *pn;

    if (thispage) {
	/* first consider "uninteresting" pages */
	for (pn = firstpage->next; pn; pn = pn->next)
	    if (pn->extra && pn != thispage && pn != thispage->prev &&
		pn != thispage->next && pn != lastpage) {
		FreeImage(Pimage(pn));
		pn->extra = 0;
		return 1;
	    }
	Try(lastpage);
	Try(firstpage);
	Try(thispage->prev);
	Try(thispage->next);
    }

    return 0;

}

XImage *NewImage(int w, int h, char *data, int bit_order){

    XImage *newimage;
    /* This idea is taken from xwud/xpr.  Use a fake display with the
       desired bit/byte order to get the image routines initialised
       correctly */
    Display fake;

    fake = *Disp;
    if (data == 0)
	data = xmalloc(((w + 31) & ~31) * h / 8);
    fake.byte_order = ByteOrder;
    fake.bitmap_unit = 32;
    fake.bitmap_bit_order = bit_order;

    int returncode = -1;
    while ((newimage = XCreateImage(&fake, DefaultVisual(Disp, Default_Screen),
				    1, XYBitmap, 0, data, w, h, 32, 0)) == 0 ){
      
      returncode = release(1);
      if (returncode == 0)
	break;
    }

    if (returncode == 0){
      kfaxerror("Sorry","Can not allocate Memory for a new Fax Image\n");
      return 0;
    }

    Memused += newimage->bytes_per_line * newimage->height;
    /*printf("allocating %d bytes for %ld\n",
       newimage->bytes_per_line * newimage->height,
       newimage);*/


    return newimage;
}

void FreeImage(XImage *Image){

  if (Image->data){
	Memused -= Image->bytes_per_line * Image->height;
/*printf("deallocating %d bytes for %ld\n",
       Image->bytes_per_line * Image->height,
       Image);*/
  }
    XDestroyImage(Image);
}

#ifndef xmalloc
char *
xmalloc(unsigned int size)
{
    char *p;

    while (Memused + size > Memlimit && release(0))
	;
    while ((p = (char*) malloc(size)) == 0)
	(void) release(1);
    return p;
}
#endif
