/*
 * Convert BJC-printjobs to xbm files, one for each color channel
 *
 * Copyright 2001 Andy Thaller <thaller@ph.tum.de>
 *
 *   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, see <https://www.gnu.org/licenses/>.
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <gutenprint/util.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

char *efnlc= 0,*efnlm= 0,*efnc= 0,*efnm= 0,*efny= 0,*efnk= 0;

int lc_cnt= 0,lm_cnt= 0,ly_cnt= 0,c_cnt= 0,m_cnt= 0,y_cnt= 0,k_cnt= 0;

FILE *outfilelc= 0;
FILE *outfilelm= 0;
FILE *outfilely= 0;
FILE *outfilec= 0;
FILE *outfilem= 0;
FILE *outfiley= 0;
FILE *outfilek= 0;

int xsize,ysize,pixcnt;

typedef struct scanline_t_ {
  int size;
  int osize;
  unsigned char *buf;
  int xmin;
  int xmax;
  int width;
  int y;
  struct scanline_t_ *next;
} scanline_t;

typedef struct bitimage_t_ {
  unsigned char *buf;
  int y0;
  int width;
  int height;
  int xmin;
  int xmax;
} bitimage_t;

typedef struct level_t_ {
  /* Max number of bits per ink so far known is 8
     Thus max number of levels is 256 (including value 0)
  */
  int levelvals[256]; /* number of occurrences of each level */
  int numlevels; /* number of actual levels used */
  int maxlevel;  /* maximum value */
  int numbits;  /* deduced number bits used for numlevels */
} level_t;

bitimage_t *bitimage_new (void);
int last_bit (unsigned char i);
int first_bit (unsigned char i);
void rle_info (const unsigned char *inbuf, int n, int *first, int *last,
               int *width, int *length);
int rle_decode (unsigned char *inbuf, int n, unsigned char *outbuf, int max, level_t *levels);
scanline_t* scanline_new (void);
level_t* level_new (void);
int calc_bits(int numlevels);
scanline_t *scanline_store (scanline_t *line, int y, unsigned char *buf,
                            int size);
bitimage_t *scanlines2bitimage (scanline_t *slimg, level_t *levels);
char conv (char i);
void save2xbm (const char *filename,char col, bitimage_t *img,
               int xmin, int ymin, int xmax, int ymax);
int nextcmd (FILE *infile, unsigned char *inbuff, int *cnt);
int process(FILE *infile, scanline_t *sf[7], int *xmin_, int *xmax_,
            int *ymin_, int *ymax_, level_t *lv[7]);


bitimage_t *bitimage_new(void)
{
  bitimage_t *tmp= (bitimage_t*) stp_malloc (sizeof(bitimage_t));
  tmp->buf= 0;
  tmp->y0= 0;
  tmp->width= 0;
  tmp->height= 0;
  tmp->xmin= 0;
  tmp->xmax= 0;
  return tmp;
}

int last_bit(unsigned char i) {
  if (i & 0x1) return 0;
  if (i & 0x2) return 1;
  if (i & 0x4) return 2;
  if (i & 0x8) return 3;
  if (i & 0x10) return 4;
  if (i & 0x20) return 5;
  if (i & 0x40) return 6;
  if (i & 0x80) return 7;
  return 0;
}

int first_bit(unsigned char i) {
  if (i & 0x80) return 7;
  if (i & 0x40) return 6;
  if (i & 0x20) return 5;
  if (i & 0x10) return 4;
  if (i & 0x8) return 3;
  if (i & 0x4) return 2;
  if (i & 0x2) return 1;
  if (i & 0x1) return 0;
  return 0;
}

void rle_info(const unsigned char *inbuf, int n, int *first, int *last, int *width, int *length)
{
  const char *ib= (const char *)inbuf;
  char cnt;
  int o= 0;
  int i=0,j,num,f=-1,l=0;

  *first= 0;
  *last= 0;
  *width= 0;

  if (n<=0) return;
  while (i<n) {
    cnt= ib[i];
    if (cnt<0) {
      num= 1-cnt;
      if (inbuf[i+1]) {
	if (f<0) f=o*8 + first_bit(inbuf[i+1]);
	l= o*8 + last_bit(inbuf[i+1]);
      }
      o+= num;
      i+= 2;
    } else {
      num= cnt+1;
      for (j=0; j<num; j++) {
	if (inbuf[i+j+1]) {
	  if (f<0) f=(o+j)*8 + first_bit(inbuf[i+j+1]);
	  l= o*8 + last_bit(inbuf[i+j+1]);
	}
      }
      o+= num;
      i+= num+1;
    }
  }

  if (first) *first= f;
  if (last) *last= l;
  if (length) *length= o;
  if (width) *width= (f<0) ? 0 : l-f+1;
}

/* n is the size of the scanline */
int rle_decode(unsigned char *inbuf, int n, unsigned char *outbuf,int max, level_t *levels)
{
  const char *ib= (const char *)inbuf; /* input scanline */
  char cnt;
  int o= 0;
  int i=0,j,num;

  /* Results of the decoding are in the output buffer already.
     We want to organize the decoded numbers to see
     the total number of levels, and their values.
  */
  /*fprintf(stderr,"rle_decode: decoding scanline\n");*/
  if (n<=0) return 0;
  while (i<n) {
    cnt= ib[i];
    if (cnt<0) {
      num= 1-cnt;
      /*for (j=0; j<num; j++) outbuf[o+j]=inbuf[i+1];*/
      for (j=0; j<num; j++) {
	outbuf[o+j]=inbuf[i+1];
	/*fprintf(stderr," %d ",outbuf[o+j]);*/
	levels->levelvals[outbuf[o+j]] += 1;
	if ( outbuf[o+j] > levels->maxlevel ) levels->maxlevel = outbuf[o+j];
      }
      o+= num;
      i+= 2;
    } else {
      num= cnt+1;
      /* for (j=0; j<num; j++) outbuf[o+j]=inbuf[i+j+1]; */
      for (j=0; j<num; j++) {
	outbuf[o+j]=inbuf[i+j+1];
	/*fprintf(stderr," %d ",outbuf[o+j]);*/
	levels->levelvals[outbuf[o+j]] += 1;
	if ( outbuf[o+j] > levels->maxlevel ) levels->maxlevel = outbuf[o+j];
      }
      o+= num;
      i+= num+1;
    }
  }
  /*fprintf(stderr,"\n");*/
  return o;
}

scanline_t* scanline_new(void)
{
  scanline_t* tmp= (scanline_t*) stp_malloc (sizeof(scanline_t));
  tmp->size= 0;
  tmp->osize= 0;
  tmp->buf= 0;
  tmp->xmin= 0;
  tmp->xmax= 0;
  tmp->width= 0;
  tmp->y= 0;
  tmp->next= 0;
  return tmp;
}

level_t *level_new(void)
{
  int i;
  level_t* tmp = (level_t*) stp_malloc (sizeof(level_t));
  tmp->numlevels=0;
  tmp->maxlevel=0;
  tmp->numbits=0;
  for (i=0; i<256; i++) tmp->levelvals[i]=0;
  return tmp;
}

int calc_bits(int numlevels) {
  if (numlevels > 128) return 8;
  else if (numlevels > 64)  return 7;
  else if (numlevels > 32)  return 6;
  else if (numlevels > 16)  return 5;
  else if (numlevels > 8)   return 4;
  else if (numlevels > 4)   return 3;
  else if (numlevels > 2)   return 2;
  else if (numlevels > 1)   return 1;
  else return 0;
}

scanline_t *scanline_store(scanline_t *line, int y, unsigned char *buf, int size) {
  if (!line && !(line= scanline_new()))
    return 0;
  line->size= size; /* from cnt in nextcmd function */
  line->buf= (unsigned char *) stp_malloc (size);
  memcpy(line->buf,buf,size);
  rle_info(buf,size,&line->xmin,&line->xmax,&line->width,&line->osize);
  /*fprintf(stderr,"Scanline size %d, xmin %d, xmax %d, width %d\n",size,line->xmin,line->xmax,line->width);*/
  line->y= y;
  return line;
}

bitimage_t *scanlines2bitimage(scanline_t *slimg, level_t *level)
{
  bitimage_t *img= 0;
  scanline_t *sl;
  int w= 0;
  int y0= -1;
  int y= 0;
  int h,i;

  for (sl=slimg; sl!=0; sl=sl->next) {
    if (sl->width) {
      if (w<sl->osize) w= sl->osize;
      if (y0<0) y0= sl->y;
      y= sl->y;
    }
  }
  h= y-y0+1;

  if ((!w) || (!h))
    return 0;

  img= bitimage_new();

  img->buf= (unsigned char*) stp_malloc(h*w);
  memset(img->buf,0,h*w);
  img->width= w;
  img->height= h;
  img->y0= y0;

  for (sl=slimg; sl!=0; sl=sl->next) {
    y= sl->y- y0;
    if ((y>=0) && (y<h)) {
      rle_decode(sl->buf,sl->size,img->buf+y*w,w,level);
    }
  }

  /* print out stats of levels */
  for (i=0;i<256;i++) {
    fprintf(stderr,"level %d: %d\n",i,level->levelvals[i]);
    if (level->levelvals[i] > 0) level->numlevels += 1;
  }
  fprintf(stderr,"numlevels: %d\n",level->numlevels);
  fprintf(stderr,"maxlevel: %d\n",level->maxlevel);
  level->numbits = calc_bits(level->maxlevel);
  fprintf(stderr,"numbits: %d\n",level->numbits);

  return img;
}

char conv(char i) {
  char o= 0;
  if (i & 0x80) o|=0x01;
  if (i & 0x40) o|=0x02;
  if (i & 0x20) o|=0x04;
  if (i & 0x10) o|=0x08;
  if (i & 0x08) o|=0x10;
  if (i & 0x04) o|=0x20;
  if (i & 0x02) o|=0x40;
  if (i & 0x01) o|=0x80;
  return o;
}

void save2xbm(const char *filename,char col, bitimage_t *img,
	      int xmin, int ymin, int xmax, int ymax)
{
  char *outfilename= (char*) stp_malloc(strlen(filename)+16);
  FILE *o;
  int i,j,k,i0,i1,j0,j1,w,h;

  if (!img) return;

  if (col)
    sprintf(outfilename,"%s_%c.xbm",filename,col);
  else
    sprintf(outfilename,"%s.xbm",filename);

  if (!(o= fopen(outfilename,"w"))) {
    stp_free(outfilename);
    return;
  }

  i0= (ymin>0) ? ymin-img->y0 : 0;
  i1= (ymax>0) ? ymax-img->y0 : img->height;
  j0= (xmin>0 && xmin<img->width*8) ? xmin/8 : 0;
  j1= (xmax>0 && xmax<img->width*8) ? xmax/8 : img->width-1;

  w= j1-j0+1;
  h= i1-i0;

  if (col)
    fprintf(o,"#define %s_%c_width %d\n#define %s_%c_height %d\nstatic char %s_%c_bits[] = {",
	    outfilename,col,w*8,
	    outfilename,col,h,
	    outfilename,col);
  else
    fprintf(o,"#define %s_width %d\n#define %s_height %d\nstatic char %s_bits[] = {",
	    outfilename,w*8,
	    outfilename,h,
	    outfilename);

  fprintf(stderr,"%d %d %d %d\n",i0,h,j0,w);
  for (i=i0,k=0; i<i1; i++) {
    for (j=j0; j<=j1; j++) {
      if (k%16 == 0) fprintf(o,"\n");
      fprintf(o,"0x%02x, ",(i>=0 && i<img->height)? conv(img->buf[i*img->width+j])&0xff : 0);
      k++;
    }
  }

  fprintf(o," 0x00 };\n");

  fclose(o);
}

int nextcmd(FILE *infile,unsigned char *inbuff,int *cnt)
{
  unsigned char c1=0,c2=0;
  int c;
  int cmd;

  while (!feof(infile) && c1!=0x1b && (c2!=0x28 && c2!=0x5b)) {
    c1=c2; c2=fgetc(infile);
  }
  if (feof(infile)) return 0;

  cmd=fgetc(infile);
  if (feof(infile)) return 0;

  c1= fgetc(infile);
  if (feof(infile)) return 0;
  c2= fgetc(infile);
  if (feof(infile)) return 0;

  /* 2 bytes: seems to be the size+1 of one scanline in cases where command is a scanline */
  *cnt= c1+256*c2;

  if ((c=fread(inbuff,1,*cnt,infile) != *cnt)) {
    fprintf(stderr,"read error for 0x%02x - not enough data (%d/%d)!\n",cmd,
	    c,*cnt);
    return 0;
  }

  return cmd;
}

int process(FILE *infile,scanline_t *sf[7],int *xmin_,int *xmax_,int *ymin_,int *ymax_,level_t *lv[7])
{
  unsigned char inbuff[65540];
  scanline_t *sl[7], *nsl;

  int xres,yres;
  int xmin=1000000,xmax=0,ymin=-1,ymax=0,width=0;
  int col=0;
  int line=0;
  int i;
  int cnt;
  int cmd;

  for (i=0; i<7; i++) sf[i]= sl[i]= 0;

  for (i=0; i<7; i++) lv[i] = level_new();

  if (!infile) return 0;
  fseek(infile,0,SEEK_SET);

  while ((cmd= nextcmd(infile,inbuff,&cnt))) {
    switch(cmd) {
    case 0x64:
      yres=inbuff[0]*256+inbuff[1];
      xres=inbuff[2]*256+inbuff[3];
      fprintf(stderr,"res=%dx%ddpi\n",xres,yres);
      break;
    case 0x65:
      line+=(inbuff[0]*256+inbuff[1]);
      break;
    case 0x41:
      switch(inbuff[0]) {
      case 0x4b: col= 0; break; /* black        */
      case 0x59: col= 1; break; /* yellow       */
      case 0x4d: col= 2; break; /* magenta      */
      case 0x43: col= 3; break; /* cyan         */
      case 0x79: col= 4; break; /* lightyellow  */
      case 0x6d: col= 5; break; /* lightmagenta */
      case 0x63: col= 6; break; /* lightcyan    */
      default:
	fprintf(stderr,"unknown color component 0x%02x\n",inbuff[0]);
	exit(-1);
      }

      nsl= scanline_store(0,line,inbuff+1,cnt-1);

      if (nsl) {
	if (nsl->width) {
	  if (ymin<0) ymin=line;
	  ymax=line;
	  if (nsl->xmin<xmin) xmin=nsl->xmin;
	  if (nsl->xmax>xmax) xmax=nsl->xmax;
	  if (nsl->width>width) width=nsl->width;
	  if (!sf[col])
	    sf[col]=nsl;
	  else
	    sl[col]->next= nsl;
	  sl[col]= nsl;
	} else {
	  stp_free (nsl);
	  nsl= 0;
	}

	if (fgetc(infile)!=0x0d) {
	  fprintf(stderr,"COLOR LINE NOT TERMINATED BY 0x0d @ %lx!!\n",
		  ftell(infile));
	}
      }
      break;
    case 0x62:
      break;
    default:
      ;
    }
  }

  fprintf(stderr,"size=%d..%d:%d..%d = %dx%d\n",xmin,xmax,ymin,ymax,width,ymax-ymin+1);

  *xmin_= xmin;
  *xmax_= xmax;
  *ymin_= ymin;
  *ymax_= ymax;

  return 1;
}

/*
const char *fbin(char b) {
  static char bf[9];

  bf[0]= (b & 0x80) ? '1' : '0';
  bf[1]= (b & 0x40) ? '1' : '0';
  bf[2]= (b & 0x20) ? '1' : '0';
  bf[3]= (b & 0x10) ? '1' : '0';
  bf[4]= (b & 0x08) ? '1' : '0';
  bf[5]= (b & 0x04) ? '1' : '0';
  bf[6]= (b & 0x02) ? '1' : '0';
  bf[7]= (b & 0x01) ? '1' : '0';
  bf[8]=0;
  return bf;
}
*/

int main(int argc, char **argv)
{
  char *infilename= 0, *outfilename=0;
  FILE *infile= 0;
  scanline_t *scanlines[7];
  char colname[7] = { 'K', 'Y', 'M', 'C', 'y', 'm', 'c' };
  level_t *levels[7];
  int xmin,xmax;
  int ymin,ymax;

  if (argc>1) {
    if (argc>2)
      outfilename= argv[2];
    else
      outfilename= argv[1];

    infilename= argv[1];
    infile= fopen(infilename,"r");

    xsize=ysize=0;

    process(infile,scanlines,&xmin,&xmax,&ymin,&ymax,levels);

    fprintf(stderr,"Color %c\n",colname[0]);
    save2xbm(outfilename,colname[0],scanlines2bitimage(scanlines[0],levels[0]),xmin,ymin,xmax,ymax);
    fprintf(stderr,"Color %c\n",colname[1]);
    save2xbm(outfilename,colname[1],scanlines2bitimage(scanlines[1],levels[1]),xmin,ymin,xmax,ymax);
    fprintf(stderr,"Color %c\n",colname[2]);
    save2xbm(outfilename,colname[2],scanlines2bitimage(scanlines[2],levels[2]),xmin,ymin,xmax,ymax);
    fprintf(stderr,"Color %c\n",colname[3]);
    save2xbm(outfilename,colname[3],scanlines2bitimage(scanlines[3],levels[3]),xmin,ymin,xmax,ymax);
    fprintf(stderr,"Color %c\n",colname[4]);
    save2xbm(outfilename,colname[4],scanlines2bitimage(scanlines[4],levels[4]),xmin,ymin,xmax,ymax);
    fprintf(stderr,"Color %c\n",colname[5]);
    save2xbm(outfilename,colname[5],scanlines2bitimage(scanlines[5],levels[5]),xmin,ymin,xmax,ymax);
    fprintf(stderr,"Color %c\n",colname[6]);
    save2xbm(outfilename,colname[6],scanlines2bitimage(scanlines[6],levels[6]),xmin,ymin,xmax,ymax);

  } else {
    fprintf(stderr,"\nusage: bjc-unprint INFILE [OUTFILE]\n\n");
  }

  return 0;
}
