/*
 * Usage: cbd2else [-rezfile name] [-output name] [-format type]
 *                 [-continents list] [-types list]
 *                 [-ascii type] [-verbose] [-silent]
 * where format type is one of NONE (default), TEXT, JPD
 * and TXT ascii type is one of DXDY (default), INT, REAL
 *
 */

#include <endian.h>

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

#define TOTALSEG 31844
/* number of segments in WDB is really 31845, but one river showing up in
   Uganda is faulty and must be deleted ! */

#define CBD_MAGIC 0x20770002
#define REZ_MAGIC 0x86460346
#define JPD_MAGIC "!JPD1.0\n"

#define SHORTFLAG 0x4000
#define BIT32 int
#define BIT16 short

#define DEBUG 1

enum { AFRICA=0, ASIA, EUROPE, NAMERICA, SAMERICA };
enum { BDY=0, PBY, RIV, CIL };
enum {NONE=0, TEXT, REZ, JPD };
enum {INT=0, REAL, BOTH};

struct cbdhead
{
    BIT32           magic;	/* Magic number */
    BIT32           dictaddr;	/* Offset of segment dictionary in file */
    BIT32           segcount;	/* Number of segments in file */
    BIT32           segsize;	/* Size of segment dictionary */
    BIT32           segmax;	/* Size of largest segment's strokes, /2 */
    BIT32           fill[(40 / sizeof (BIT32)) - 5];	/* Filler */
};

struct segdict
{
    BIT32           segid;
    BIT32           maxlat, minlat, maxlong, minlong;
    BIT32           absaddr;
    BIT16           nbytes;
    BIT16           rank;
};

struct segment
{
    BIT32           orgx, orgy;
    BIT32           id;
    BIT16           nstrokes;
    BIT16           dummy;
};

/*
 * Change the two routines below to accomodate the byte ordering on your
 * machine! - Joe Dellinger
 */

#if __BYTE_ORDER == __BIG_ENDIAN
void twiddle32 (i32)
    BIT32          *i32;
{
BIT32           temp;

    temp = 0;
    temp |= 0xFF000000 & ((0x000000FF & *i32) << 24);
    temp |= 0x00FF0000 & ((0x0000FF00 & *i32) << 8);
    temp |= 0x0000FF00 & ((0x00FF0000 & *i32) >> 8);
    temp |= 0x000000FF & ((0xFF000000 & *i32) >> 24);
    *i32 = temp;
}

void twiddle16 (i16)
    BIT16          *i16;
{
BIT16           temp;

    temp = 0;
    temp |= 0xFF00 & ((0x00FF & *i16) << 8);
    temp |= 0x00FF & ((0xFF00 & *i16) >> 8);
    *i16 = temp;
}
#else
void twiddle32 (i32)
    BIT32          *i32;
{
}

void twiddle16 (i16)
    BIT16          *i16;
{
}
#endif

void fpr2(FILE * f, BIT32 n)
{
    fprintf(f, "%c%c", n&255, (n>>8)&255);
}

void fpr3(FILE * f, BIT32 n)
{
    fprintf(f, "%c%c%c", n&255, (n>>8)&255, (n>>16)&255);
}

void fpr4(FILE * f, BIT32 n)
{
    fprintf(f, "%c%c%c%c", n&255, (n>>8)&255, (n>>16)&255, n>>24);
}

void prascii(int u, int v, int text_output_type)
{
            if (text_output_type == INT)
                printf("%7d %7d\n", u, v);
	    else
            if (text_output_type == REAL)
                printf("%3.6f %3.6f\n", 
                      (double)u/3600.0, (double)v/3600.0);
	    else
            if (text_output_type == BOTH)
                printf("%7d %7d | %3.6f %3.6f\n", u, v, 
                      (double)u/3600.0, (double)v/3600.0);
}

void usage()
{
   fprintf(stderr, 
      "Usage: cbd2else [-datadir name] [-outdir name] [-numseg number]\n"
      "                [-format type] [-ascii type]\n"
      "where format type is one of NONE (default), TEXT, REZ, JPD,\n"
      "and TEXT ascii type is one of DXDY (default), INT, REAL\n\n");
   exit(-1);
}

int main (argc, argv)
    int             argc;
    char          **argv;
{
FILE * fi, * fh, * fd;
char filename[512];
char * data_dir = NULL, * out_dir = NULL;
char magic[20];
int dx[20000], dy[20000];
int  Ifile; 
int  u, v, w, j, k, oln, olt, idx, idy, rejected, text_output_type;
int format, numseg, totalseg, addr, maxlength, maxwidth, maxheight;
#if DEBUG
int w1, w2, z1, z2;
#endif
int  segbufsize, segcount, jstroke, iseg, ignore;

char *cont_name[] = { "africa", "asia", "europe", "namer", "samer" };

char *type_name[] = { "bdy", "pby", "riv", "cil" };
int rez_type_code[4] = { 2, 0, 1, 3 };

char *comment[3] = { 
  "((int) lat, long)",
  "(lat, long °decimal)",
  "((int) lat, long | lat, long °)"
};

#define U -1
char targets[4][20];

int jpd_encod[4][16] = {
  { U, 0, 1, 2, U, U, U, U, U, U, U, U, U, U, U, U },
  { U, 3, U, 4, U, U, U, U, U, U, U, U, U, U, U, U },
  { U, 5, 6, 7, 8, 9, 10, 11, 12, U, 13, 14, U, 15, U, U },
  { U, 16, 17, 18, 19, U, 20, 21, 22, 23, 24, U, U, 25, 26, 27 }
};
int jpd_min[4] = { 0, 3, 5, 16 };
int jpd_max[4] = { 2, 4, 15, 27 };
int jpd_seg_count[5][28];

BIT32 i32;
BIT16 *segbuf;
struct cbdhead  header;
struct segdict *sd, *sdbuf;
struct segment sb;

#if __BYTE_ORDER == __BIG_ENDIAN
    fprintf(stderr, "Detected bigendian processor...\n");
#endif

/* 
 * Parse options
 */

    format = TEXT;
    totalseg = TOTALSEG;
    text_output_type = REAL;
 
    for (j=1; j<argc; j++) {
        if (j<argc-1 && !strncmp(argv[j],"-datadir",8))
	   data_dir = (char *)strdup(argv[++j]);
	else
        if (j<argc-1 && !strncmp(argv[j],"-outdir",7))
	   out_dir = (char *)strdup(argv[++j]);
	else
        if (j<argc-1 && !strncmp(argv[j],"-numseg",7))
	   totalseg = atoi(argv[++j]);
	else
	  if (j<argc-1 && !strcmp(argv[j], "-format")) {
	     ++j;
	     if (!strcasecmp(argv[j], "none")) format = NONE;
	     if (!strcasecmp(argv[j], "text")) format = TEXT;
	     if (!strcasecmp(argv[j], "rez")) format = REZ;
	     if (!strcasecmp(argv[j], "jpd")) format = JPD;
	  }
        else
	  if (j<argc-1 && !strcmp(argv[j], "-ascii")) {
	     ++j;
	     if (!strcasecmp(argv[j], "int")) text_output_type = INT;
	     if (!strcasecmp(argv[j], "real")) text_output_type = REAL;
	     if (!strcasecmp(argv[j], "both")) text_output_type = BOTH;
	  }
	else
	  usage();
    }

    numseg = 0;
    maxlength = 0;
    maxwidth = 0;
    maxheight = 0;
    if (totalseg<0) totalseg = 0;
    rejected = 0;
    addr = 0;
    if (format == REZ)
       addr = 12+totalseg*32;
    if (format == JPD)    
       addr = 576+totalseg*16;

    for (v=0; v<4; v++)
        sprintf(targets[v], "000000000000000000");

    if (!data_dir) {
	fprintf(stderr, "The WDB data directory has not been specified !!\n");
        exit(-1);
    }        
/*
 * Read in the file header, check it for the correct magic number,
 * and learn the address of the segment dictionary
 */

    if (format == REZ) {
       if (!out_dir) {
	  fprintf(stderr, 
             "Output in REZ format requires -outdir to be specified!!\n");
          exit(-1);
       }        
       sprintf(filename, "%s/rez_header", out_dir);
       fh = fopen(filename, "w");
       sprintf(filename, "%s/rez_data", out_dir);
       fd = fopen(filename, "w");
       fpr4(fh, REZ_MAGIC);
       fpr4(fh, 12);
       fpr4(fh, totalseg);
    }
    if (format == JPD) {
       if (!out_dir) {
	  fprintf(stderr, 
             "Output in JPD format requires -outdir to be specified!!\n");
          exit(-1);
       }        
       sprintf(filename, "%s/jpd_ini", out_dir);
       fi = fopen(filename, "w");
       sprintf(filename, "%s/jpd_header", out_dir);
       fh = fopen(filename, "w");
       sprintf(filename, "%s/jpd_data", out_dir);
       fd = fopen(filename, "w");
       bzero(magic, 16);
       strcpy(magic, JPD_MAGIC);
       for (u=0; u<12; u++) fprintf(fi, "%c", magic[u]);
    }

    if (format==JPD)
        for (u=0; u<5; u++)
        for (w=0; w<28; w++)
	    jpd_seg_count[u][w] = 0;
    else
        for (v=0; v<4; v++)
	    jpd_min[v] = jpd_max[v] = 0;

    for (u=0; u<5; u++)
      for (v=0; v<4; v++)
	for (w=jpd_min[v]; w<=jpd_max[v]; w++) {

        sprintf(filename, "%s/%s.Map/%s.cbd", 
                data_dir, cont_name[u], type_name[v]);

        Ifile = open (filename, O_RDONLY, 0);
	if (Ifile==-1) {
	   fprintf(stderr, "File %s does not exist !!\n", filename);
	   continue;
	}
	fprintf(stderr, "Scanning [%d]  %s ...\n", w, filename);

	read (Ifile, &header, sizeof(header));

	twiddle32 (&(header.magic));
	twiddle32 (&(header.dictaddr));
	twiddle32 (&(header.segcount));
	twiddle32 (&(header.segsize));
	twiddle32 (&(header.segmax));

	if (header.magic != CBD_MAGIC)
	{
	    fprintf (stderr, "File has bad magic number %d != %d\n",
		     header.magic, CBD_MAGIC);
	    exit (2);
	}

/* allocate space for the segment buffer */
	segbufsize = 2 * header.segmax;
	segbuf = (BIT16 *) malloc (50 + segbufsize);

/* allocate space for the segment dictionary */
	sdbuf = (struct segdict *) malloc (100 + header.segsize);
	sd = sdbuf;
	sd++;

/* Go read in the segment dictionary (it's at the end of the file) */
	lseek (Ifile, header.dictaddr, L_SET);
	j = read (Ifile, sd, header.segsize);

	if (j < header.segsize)
	{
	    fprintf (stderr, "File segment dictionary expecting %d got %d\n",
		     header.segsize, j);
	    close (Ifile);
	    free (sdbuf);
	    free (segbuf);
	    return -1;
	}

/*
 * Now look at each segment
 */
	segcount = header.segcount;

	sd = sdbuf;
	for (iseg = 1; iseg <= segcount; iseg++)
	{
	    sd++;		/* does this really work? wow! */

	    twiddle32 (&(sd->segid));
	    twiddle32 (&(sd->maxlat));
	    twiddle32 (&(sd->minlat));
	    twiddle32 (&(sd->maxlong));
	    twiddle32 (&(sd->minlong));
	    twiddle32 (&(sd->absaddr));
	    twiddle16 (&(sd->nbytes));
	    twiddle16 (&(sd->rank));

            if (format==JPD) {
               if (jpd_encod[v][sd->rank]!=w) continue;
	    }

	    lseek (Ifile, sd->absaddr, L_SET);
	    read (Ifile, &sb, sizeof sb);
	    twiddle32 (&(sb.orgx));
	    twiddle32 (&(sb.orgy));
	    twiddle32 (&(sb.id));
	    twiddle16 (&(sb.nstrokes));

	    if (sd->nbytes > segbufsize)
	    {
		fprintf (stderr, "Segment %d needs %d bytes; buffer limit is %d.\n", iseg, sd->nbytes, segbufsize);
		close (Ifile);
		free (sdbuf);
		free (segbuf);
		return -1;
	    }
	    read (Ifile, segbuf, sd->nbytes);

	    k = 0;

	    oln = sb.orgx;
	    olt = sb.orgy;

            if (format == TEXT) {
                printf("Continent [%s], type [%s]\n", 
                                        cont_name[u], type_name[v]);
	        printf("segid   = %d\n", sd->segid);
	        /* printf("sb.id   = %d\n", sb.id); */
	        printf("minlat   = %d\n", sd->minlat);
	        printf("maxlat   = %d\n", sd->maxlat);
	        printf("minlong  = %d\n", sd->minlong);
	        printf("maxlong  = %d\n", sd->maxlong);
	        printf("absaddr  = %d\n", sd->absaddr);
	        printf("nbytes   = %d\n", sd->nbytes);
	        printf("rank     = %d\n", sd->rank);
                printf("nstrokes = %d   %s\n", 
                       sb.nstrokes, comment[text_output_type]);
		prascii(olt, oln, text_output_type);
	    }

	    ignore = 0;

	    for (jstroke = 1; jstroke <= sb.nstrokes; jstroke++)
	    {
		twiddle16 (&segbuf[k]);
		if (segbuf[k] & SHORTFLAG)
		{
/* Flag bit on: unpack a 16-bit field into dx and dy */
		    i32 = segbuf[k++];
		    if (i32 > 0)
			i32 &= ~SHORTFLAG;
		    idy = i32 & 0xFF;
		    if (idy & 0x80)
			idy |= ~0xFF;	/* extend sign */
		    idx = i32 >> 8;
		    if (idx & 0x80)
			idx |= ~0xBF;	/* extend sign */
		}
		else
		{
/* Flag bit off: take dx and dy from 32-bit fields. */
		    idx = segbuf[k++];
		    if (idx < 0)
			idx |= SHORTFLAG;
		    else
		        idx &= ~SHORTFLAG;
#if DEBUG		    
		    w1 = idx;
#endif
		    twiddle16 (&segbuf[k]);

/* Modification by J.-P. Demailly to correct calculation of cobal data 
   for which there is a big jump from longitude -180 to longitude +180
   Notice that 14720 = 20*65536-1296000 where 1296000=360*60*60
 */
		    if (idx==19)
		       idx = 14720 + segbuf[k];
		    else
		    if (idx==-20)
		       idx = segbuf[k] - 14720;
		    else
		       idx = (idx << 16) | segbuf[k];
#if DEBUG
		    w2 = segbuf[k];
#endif
		    k++;

		    twiddle16 (&segbuf[k]);
		    idy = segbuf[k];
#if DEBUG
		    z1 = idy;
#endif
		    k++;
		    if (idy < 0)
			idy |= SHORTFLAG;
		    twiddle16 (&segbuf[k]);
#if DEBUG
		    z2 = segbuf[k];
#endif
		    idy = (idy << 16) | segbuf[k];
		    k++;
		    if (abs(idx)>8000 || abs(idy)>8000) {
#if DEBUG
		       fprintf(stderr, 
                           "Segment %d, index %d : %d %d !!! %d %d %d %d\n", 
                           iseg, jstroke, idx, idy, w1, w2, z1, z2);
#endif
		       ignore = 1;
		    }

		}
	        dx[jstroke] = idx;
		dy[jstroke] = idy;
		oln = oln + idx;
		olt = olt + idy;
		if (format == TEXT) prascii(olt, oln, text_output_type);
	    }

	    if (ignore) {
	       ++rejected;
               continue;
	    }

	    targets[v][sd->rank] = '1';

	    if (format == REZ) {
	        fpr4(fh, sd->maxlat);
	        fpr4(fh, sd->minlat);
	        fpr4(fh, sd->maxlong);
	        fpr4(fh, sd->minlong);
	        fpr4(fh, addr);
	        fpr4(fh, 1<<u);
	        fpr4(fh, 1<<rez_type_code[v]);
	        fpr4(fh, sd->rank);
	        addr += 12 + 8*sb.nstrokes;

		fpr4(fd, sb.orgx);
		fpr4(fd, sb.orgy);
		fpr4(fd, sb.nstrokes);
		
	        for (jstroke = 1; jstroke <= sb.nstrokes; jstroke++) {
		    fpr4(fd, dx[jstroke]);
		    fpr4(fd, dy[jstroke]);
		}
	    }
	    if (format == JPD) {
	        int dm, count, quot, xp, yp, xt, yt, s;

		jpd_seg_count[u][w] += 1;
		/*
		if (jpd_seg_count[u][w]%100==0) 
                   fprintf(stderr, "Reached seg[%d,%d] = %d\n",
                           u, w, jpd_seg_count[u][w]);
		*/
		fpr3(fd, sb.orgx);
		fpr3(fd, sb.orgy);
		count = 0;
	        for (jstroke = 1; jstroke <= sb.nstrokes; jstroke++) {
		    dm = dx[jstroke];
		    if (dm<0) dm = -dm;
		    if (dy[jstroke]>dm) dm = dy[jstroke];
		    if (-dy[jstroke]>dm) dm = -dy[jstroke];
		    if (dm<128) {
		        fprintf(fd, "%c%c", 
                           (dx[jstroke])&255, (dy[jstroke])&255 );
			++count;
		    } else {
		        quot = 1+(dm/128);
			xp = yp = 0;
			for (s=1; s<=quot; s++) {
			    xt = xp;
			    yt = yp;
                            xp = (s*dx[jstroke])/quot;
                            yp = (s*dy[jstroke])/quot;
		            fprintf(fd, "%c%c", (xp-xt)&255, (yp-yt)&255 );
			}
			count += quot;
		    }
		}
	        fpr3(fh, sd->minlong);
	        fpr3(fh, sd->maxlong);
	        fpr3(fh, sd->minlat);
	        fpr3(fh, sd->maxlat);
	        addr += 6+2*count;
	        fpr4(fh, addr);
	    }
	    numseg += 1;

	    if (sb.nstrokes>maxlength) maxlength = sb.nstrokes;
	    i32 = sd->maxlong-sd->minlong;
	    if (i32>maxwidth) maxwidth = i32;
	    i32 = sd->maxlat-sd->minlat;
	    if (i32>maxheight) maxheight = i32;
	}
	close (Ifile);
	free (sdbuf);
	free (segbuf);
      }

    if (format == REZ) {
       fclose(fh);
       fclose(fd);
    }
    if (format == JPD) {
       fclose(fh);
       fclose(fd);
    }
    if (format==JPD) {
        int count = 0;
	fpr4(fi, maxlength);
        for (u=0; u<5; u++)
          for (w=0; w<28; w++) {
	    count += jpd_seg_count[u][w];
	    fpr4(fi, count);
            fprintf(stderr, "Segment index [cont=%d, type=%d] = %d\n",
	            u, w, count);
      }
      fclose(fi);
    }
    fprintf(stderr, "Scanned %d segments, %d rejected\n", numseg, rejected);
    fprintf(stderr, "Max length of segments %d\n", maxlength);
    fprintf(stderr, "Max width of segments %d\n", maxwidth);
    fprintf(stderr, "Max height of segments %d\n", maxheight);
    for (v=0; v<4; v++)
        fprintf(stderr, "%s : %s\n", type_name[v], targets[v]);
    return 0;
}
