/*
    sff2jpeg - convert sff group 3 fax files to jpeg.
    Copyright (C) 2002,2003  Matthias Kramm <kramm@quiss.org>

    This program 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tables.h"
#include "sff.h"

typedef unsigned char byte;
typedef unsigned short int word;
typedef unsigned long int dword;

struct hufflookup {
    int len;
    int runlen;
    int type; // 0 = makeup
};

static int tables_ok = 0;
static struct hufflookup white[8192];
static struct hufflookup black[8192];

static unsigned char line[2048];
static int lines = 0;
static int currentline = 0;

static char gfxline[4096];

static int pixlen = 1728;

static int currentpage = 0;

static char debug;

int xmax=-1,ymax=-1;

static struct FaxPage destfaxpage;

static void decodeline(int len)
{
    int bitpos = 0;
    char color = 1; //white
    struct hufflookup*table = white;
    int runlen = 0;
    int x = 0;
    line[len] = line[len+1]=line[len+2] = 0;
    while(bitpos>>3 < len)
    {
	int bytepos = bitpos>>3;
	dword data = ((line[bytepos]+(line[bytepos+1]<<8)+(line[bytepos+2]<<16)) >> (bitpos&7))&8191;
	if(table[data].len < 0) {
	    printf("decoding error 1 in line %d, x%d %08x(%d) of %d [%08x]\n", lines, x, bitpos, bytepos, len, data);
	    return;
	}
	if(table[data].type==2) {
	    printf("eol in line %d, x%d %08x(%d) of %d\n", lines, x, bitpos, bytepos, len);
	    return;
	}
	
	//printf("%08x col:%d run:%d\n", data, color, table[data].runlen);

	bitpos += table[data].len;
	runlen += table[data].runlen;
	if(!table[data].type) // makeup code
	    continue;
	if(runlen)
	do
	{
	    if(x>=pixlen) {
		printf("decoding error 2 in line %d, x%d %08x(%d) of %d\n", lines, x, bitpos, bytepos, len);
		return;
	    }
	    gfxline[x] = color;
	    x++;
	}
	while(--runlen);
	color = 1 - color;
	if(color) table = white;
	else      table = black;
	if(x>=pixlen)
	    break;
    }
}

static void handleline()
{
    if(currentpage && destfaxpage.pagenr == currentpage-1)
    {
	memcpy(&destfaxpage.data[destfaxpage.scanline*lines], gfxline, pixlen);
    }
}

static void readline(FILE*fi, int bytes)
{
    if(bytes > 2047) {
	printf("line to long:%d\n", lines);
	exit(1);
    }
    fread(line,bytes,1,fi);
    decodeline(bytes);
    handleline();
    //printf("decoded line %d\n", lines);
    lines++;
}
static void skipline()
{
    memset(gfxline, 1, pixlen);
    handleline();
    lines++;
}

static void processtable(struct huffcode*src, struct hufflookup*dest, int type)
{

    int t=0;
    while(src[t].code)
    {
	int s=0,d=0,k=1,l=strlen(src[t].code);
	if(l != src[t].len || l>13 || l<=0) {
	    printf("Error in tables\n");
	    exit(1);
	}
	for(s=0;s<l;s++) {
	    if(src[t].code[s]=='1')
		d|=k;
	    k<<=1;
	}
	k = 1<<(13-l);
	for(s=0;s<k;s++)
	{
	    int pos = d|(s<<l);
	    if(dest[pos].len>=0) {
		printf("Duplicate entry\n");
		exit(1);
	    }
	    dest[pos].len = src[t].len;
	    dest[pos].runlen = src[t].run;
	    dest[pos].type = type;
	}
	t++;
    }
}

static int readfax(char * filename)
{
    FILE*fi;
    char a[4];
    byte version;
    byte reserved;
    word user_info;
    word num_pages;
    word first_page;
    dword last_page;
    dword file_size;
    int t;
    currentpage = 0;

    if(!tables_ok)
    {
	for(t=0;t<8192;t++) {
	    white[t].len = -1;
	    white[t].runlen = -1;
	    black[t].len = -1;
	    black[t].runlen = -1;
	}
	processtable(makeupwhite, white,0);
	processtable(termwhite, white,1);
	processtable(makeupblack, black,0);
	processtable(termblack, black,1);
	processtable(extmakeup, white,0);
	processtable(extmakeup, black,0);
	processtable(term, white,2);
	processtable(term, black,2);
	tables_ok = 1;
    }

    fi = fopen(filename, "rb");

    fread(a,4,1,fi);
    if(strncmp(a,"Sfff",4))
	fprintf(stderr, "Not a fax file!\n");
    fread(&version,1,1,fi);
    fread(&reserved,1,1,fi);
    fread(&user_info,2,1,fi);
    fread(&num_pages,2,1,fi);
    fread(&first_page,2,1,fi);
    fread(&last_page,4,1,fi);
    fread(&file_size,4,1,fi);
    if(debug) {
	printf("Version:%d\n",version);
	printf("Reserved:%02x\n",reserved);
	printf("User Info:%04x\n",user_info);
	printf("%d pages, %d/%d, %d bytes\n",
		num_pages, first_page, last_page, file_size);
    }

    fseek(fi,first_page, SEEK_SET);

    /* currentpage header */
    do
    {
	lines = 0;

	while(1)
	{
	    byte b;
	    fread(&b,1,1,fi);
	    if(!b) {
		dword len;
		fread(&len,4,1,fi);
		if(len<=216 || len>=32768) {
		    printf("reserved(1)\n");
		    exit(1);
		}
		readline(fi,len);
	    } else if(b>=1 && b<=216) {
		readline(fi,b);
	    } else if(b>=217 && b<=253) {
		int t;
		for(t=0;t<b-216;t++)
		    skipline();
	    } else if(b==254) {
		if(debug && lines>0) printf("End of fax page\n");
		break;
	    } else if(b==255) {
		char c;
		fread(&c,1,1,fi);
		if(!c) {
		    printf("255-0 line!\n");
		    exit(1);
		} else {
		    int t;
		    char a;
		    for(t=0;t<c;t++)
			fread(&a,1,1,fi); //skip c bytes (user data)
		}
	    }
	}
	if(lines>0) {
	    if(debug) printf("fax page %d has %d lines.\n", currentpage, lines);
	}
	if(lines > ymax) {
	    ymax = lines;
	}

	{
	    byte vert_res;
	    byte horiz_res;
	    byte coding;
	    byte specials;
	    word linelen;
	    word pagelen;
	    dword prev_page;
	    dword next_page;
	    byte len;
	    int pos;

	    fread(&len,1,1,fi);
	    
	    pos = ftell(fi);

	    if(len == 0) 
		break; // last currentpage
	
	    if(debug) printf(" === page %d === \n", currentpage+1);

	    fread(&vert_res,1,1,fi);
	    fread(&horiz_res,1,1,fi);
	    fread(&coding,1,1,fi);
	    fread(&specials,1,1,fi);
	    fread(&linelen,2,1,fi);
	    fread(&pagelen,2,1,fi);
	    fread(&prev_page,4,1,fi);
	    fread(&next_page,4,1,fi);

	    if(linelen>xmax)
		xmax = linelen;

	    if(debug)
	    {
		printf("Vertical resolution: [code %d] %s\n",
		        vert_res, vert_res==0?"98":(vert_res==1?"196":"unknown"));
		printf("Horizontal resolution: [code %d] %s\n",
			horiz_res, horiz_res==0?"98":"unknown");
		printf("coding:%d (%s), specials:%d (%s), linelen:%d (%s)\n",
			coding, coding?"unknown":"MH", 
			specials, specials?"unknown":"none", 
			linelen, (linelen==1728)?"Standard":((linelen==2048)?"B4":((linelen==2432)?"A3":"unknown")));
	    }


	    pixlen = linelen; // set global variable 

	    if(debug)
	    {
		printf("pagelen:%d (%s), prevpage:%04x (%s), nextpage:%04x (%s)\n",
		pagelen,  pagelen?"":"not given",
		prev_page, !prev_page?"not given":((prev_page==1)?"not existing":""),
		next_page, next_page?"":"not given");
		printf("seeking to %04x - reading page %d\n", pos+len, currentpage+1);
	    }

	    fseek(fi, pos+len, SEEK_SET);
	}
    }
    while(++currentpage);

    fclose(fi);

    return currentpage;
}

int sff_countpages(char* filename)
{
    destfaxpage.pagenr = -1;
    debug = 0;
    return readfax(filename);
}

void sff_printinfo(char* filename)
{
    destfaxpage.pagenr = -1;
    debug = 1;
    printf("============= SFF INFO ================\n");
    readfax(filename);
    printf("=======================================\n");
}

struct FaxPage sff_getpage(char* filename, int page)
{
    if(xmax<0 || ymax<0) {
	readfax(filename);
	if(xmax<0 || ymax<0) {
	    printf("Error in fax\n");
	    exit(1);
	}
    }
    destfaxpage.pagenr = page;
    destfaxpage.lenx = xmax;
    destfaxpage.scanline = xmax;
    destfaxpage.leny = ymax;
    destfaxpage.data = malloc(xmax*ymax);

    debug = 0;
    readfax(filename);
    destfaxpage.pagenr = -1;
    return destfaxpage;
}

