/*
 * Usage: rez2else [-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 <unistd.h>
#include <stdlib.h>
#include <sys/file.h>

#if __BYTE_ORDER == __BIG_ENDIAN
#define WORDS_BIGENDIAN
#endif

#ifdef ZLIB
#include <zlib.h>
#endif

#define BIT32 int
#define BIT16 short

#ifdef WORDS_BIGENDIAN
void bitflip (BIT32 *x) {
	BIT32 y=0;
	
	y |= 0xFF000000 & ((0x000000FF & *x) << 24);
	y |= 0x00FF0000 & ((0x0000FF00 & *x) << 8);
	y |= 0x0000FF00 & ((0x00FF0000 & *x) >> 8);
	y |= 0x000000FF & ((0xFF000000 & *x) >> 24);
	*x = y;
}
#endif /* WORDS_BIGENDIAN */

#define DEFAULT_REZ_FILE "/usr/share/rmap/CIA_map.rez"
#define MAXSTROKES 6000000
/* conservative upper estimate */

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

#define SHORTFLAG 0x4000
#define REZ_MAGIC 0x86460346
#define JPD_MAGIC "!JPD1.0\n"

#define DEBUG 1

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

struct header
{
  BIT32 magic;
  BIT32 segment_index_address;
  BIT32 segment_index_count;
};

struct segment_index
{
  BIT32 maxlat;
  BIT32 minlat;
  BIT32 maxlong;
  BIT32 minlong;
  BIT32 segment_address;
  BIT32 continent;
  BIT32 category;
  BIT32 type;
};

struct segment_header
{
  BIT32 orgx;
  BIT32 orgy;
  BIT32 nstrokes;
};

struct stroke
{
  BIT32 dx;
  BIT32 dy;
};

char *cont_name[] = { "Africa", "Asia", "Europe", "North America", 
                      "South America" };
char *type_name[] = { "int", "nat", "riv", "cil" };
int jpd2rez_type_code[4] = { 2, 0, 1, 3 };

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 usage()
{
   fprintf(stderr, 
      "Usage: rez2else [-rezfile name] [-output name] [-format type]\n"
      "                [-continents list] [-types list]\n"
      "                [-ascii type] [-verbose] [-silent]\n"
      "where format type is one of NONE (default), TEXT, 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;
{
#ifdef ZLIB
gzFile *ff;
#else
int ff;
#endif

FILE *fd;
char *rez_file = NULL;
char *list_cont=NULL, *list_types=NULL;
int u, v, up, vp, w, i, j, k;
int dm, quot, xp, yp, xt, yt, s;
int format, numseg, rejected, verbose;
int rez_length, jpd_length, rez_total_strokes;
int maxwidth, maxheight;
int text_output_type, count, addr;
char magic[20], dx[MAXSTROKES], dy[MAXSTROKES];
/* +10 just to be safe */
int rez_nstrokes[REZ_TOTALSEG+10],
    rez_orgx[REZ_TOTALSEG+10], rez_orgy[REZ_TOTALSEG+10];
int jpd_count[REZ_TOTALSEG+10]; 

char * output;

struct segment_index * segment_index_data = NULL;
struct header header_data;
struct segment_header segment_header_data;
struct stroke stroke_data;

#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 },           /* int 2^2 */
  { U, 3, U, 4, U, U, U, U, U, U, U, U, U, U, U, U },           /* nat 2^0 */
  { U, 5, 6, 7, 8, 9, 10, 11, 12, U, 13, 14, U, 15, U, U },     /* riv 2^1 */
  { U, 16, 17, 18, 19, U, 20, 21, 22, 23, 24, U, U, 25, 26, 27 }/* cil 2^3 */
};
int jpd_min[4] = { 0, 3, 5, 16 };
int jpd_max[4] = { 2, 4, 15, 27 };
int jpd_seg_count[5][28];

    format = TEXT;
    text_output_type = DX_DY;
    verbose = 1;
 
    for (j=1; j<argc; j++) {
        if (j<argc-1 && !strcmp(argv[j],"-rezfile"))
	   rez_file = (char *)strdup(argv[++j]);
	else
        if (j<argc-1 && !strcmp(argv[j],"-output"))
	   output = (char *)strdup(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], "jpd")) format = JPD;
	}
        else
	if (j<argc-1 && !strcmp(argv[j], "-ascii")) {
	   ++j;
	   if (!strcasecmp(argv[j], "dxdy")) text_output_type = DX_DY;
	   if (!strcasecmp(argv[j], "int")) text_output_type = INT;
	   if (!strcasecmp(argv[j], "real")) text_output_type = REAL;
        }
	else
	if (j<argc-1 && !strcmp(argv[j], "-continents")) {
	   ++j;
	   list_cont = strdup(argv[j]);
	}
	else
	if (j<argc-1 && !strcmp(argv[j], "-types")) {
	   ++j;
	   list_types = strdup(argv[j]);
	}
	else
	   if (!strcmp(argv[j], "-verbose")) verbose = 1;
	else
	   if (!strcmp(argv[j], "-silent")) verbose = 0;
	else
	   usage();
    }

    if (!output) {
       fprintf(stderr, 
             "No -output specified, using stdout as default\n\n");
       fd = stdout;
    } else
       fd = fopen(output, "w");

    numseg = 0;
    rez_length = 0;
    rez_total_strokes = 0;
    jpd_length = 0;
    maxwidth = 0;
    maxheight = 0;
    rejected = 0;

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

    if (!rez_file) {
	fprintf(stderr, "No input REZ file has been specified\n"
		        "Trying default %s\n\n", DEFAULT_REZ_FILE);
        rez_file = DEFAULT_REZ_FILE;
    }        

    #ifdef ZLIB
    ff = gzopen(rez_file, "r");
    if (!ff) {
       fprintf(stderr, "Cannot open REZ file %s !!\n", rez_file);
       exit(-1);
    }
    #else
    ff = open(rez_file, O_RDONLY, 0);
    if (ff==-1) {
       fprintf(stderr, "Cannot open REZ file %s !!\n", rez_file);
       exit(-1);
    }
    #endif

    for (u=0; u<5; u++)
       for (w=0; w<28; w++)
          jpd_seg_count[u][w] = 0;

#ifdef ZLIB
    gzread(ff, &header_data, sizeof(header_data));
#else
    read(ff, &header_data, sizeof(header_data));
#endif
#ifdef WORDS_BIGENDIAN
    bitflip( &(header_data.magic) );	
    bitflip( &(header_data.segment_index_address) );	
    bitflip( &(header_data.segment_index_count) );	
#endif /* WORDS_BIGENDIAN */
    if (header_data.magic != REZ_MAGIC) {
       fprintf(stderr, "File %s has wrong magic number %X\n"
                       "instead of %X . Aborting !!\n",
	               rez_file, header_data.magic, REZ_MAGIC);
       
       exit(-2);
    }

    segment_index_data = (struct segment_index *)
        malloc(header_data.segment_index_count*sizeof(struct segment_index));
    
    for (i=0; i<header_data.segment_index_count; i++) {
        #ifdef ZLIB
	gzseek(ff, sizeof(header_data) + sizeof(struct segment_index)*i, 
                   SEEK_SET);
	gzread(ff, &segment_index_data[i], sizeof(struct segment_index));
        #else
	lseek(ff, sizeof(header_data) + sizeof(struct segment_index)*i,
                         SEEK_SET);
	read(ff, &segment_index_data[i], sizeof(struct segment_index));
	#endif

#ifdef WORDS_BIGENDIAN
	bitflip( &(segment_index_data[i].maxlat) );
	bitflip( &(segment_index_data[i].minlat) );
	bitflip( &(segment_index_data[i].maxlong) );
	bitflip( &(segment_index_data[i].minlong) );
	bitflip( &(segment_index_data[i].segment_address) );
	bitflip( &(segment_index_data[i].continent) );
	bitflip( &(segment_index_data[i].category) );
	bitflip( &(segment_index_data[i].type) );
#endif /* WORDS_BIGENDIAN */
	xp = segment_index_data[i].maxlong-segment_index_data[i].minlong;
	yp = segment_index_data[i].maxlat-segment_index_data[i].minlat;
	if (xp>maxwidth) maxwidth = xp;
	if (yp>maxheight) maxheight = yp;
    }

    count = 0;
    jpd_count[0] = 0;
    for (i=0; i<header_data.segment_index_count; i++) {
        /* 
         * Awful work-around for bug in chunk 3532 in CIA_map.rez !! 
         * associated address is 7554516, and nstrokes = 410
	 * point j=38 obviously wrong, maybe orgx, orgy as well
         */
	if (i == 3532) {
	  segment_index_data[i].continent = -1;
          jpd_count[i+1] = jpd_count[i];
	  ++rejected;
	  continue;
	}
	++numseg;
	#ifdef ZLIB
	gzseek(ff, segment_index_data[i].segment_address, SEEK_SET);
	gzread(ff, &segment_header_data, sizeof(struct segment_header));
	#else
        lseek(ff, segment_index_data[i].segment_address, SEEK_SET);
        read(ff, &segment_header_data, sizeof(struct segment_header));
	#endif
#ifdef WORDS_BIGENDIAN
        bitflip( &(segment_header_data.orgx) );
        bitflip( &(segment_header_data.orgy) );
    	bitflip( &(segment_header_data.nstrokes) );
#endif /* WORDS_BIGENDIAN */
	rez_orgx[i] = segment_header_data.orgx;
	rez_orgy[i] = segment_header_data.orgy;
	rez_nstrokes[i] = segment_header_data.nstrokes;
	rez_total_strokes += segment_header_data.nstrokes;

        for (j=0; j<segment_header_data.nstrokes; j++) {
	    #ifdef ZLIB
            gzread(ff, &stroke_data, sizeof(struct stroke));
	    #else
            read(ff, &stroke_data, sizeof(struct stroke));
	    #endif
#ifdef WORDS_BIGENDIAN
            bitflip( &(stroke_data.dx) );
            bitflip( &(stroke_data.dy) );
#endif /* WORDS_BIGENDIAN */
	    quot = 0;
	  retry:
            dm = stroke_data.dx;
	    if (dm<0) dm = -dm;
	    if (stroke_data.dy>dm) dm = stroke_data.dy;
	    if (-stroke_data.dy>dm) dm = -stroke_data.dy;
	    if (dm>8000) {
	       if (stroke_data.dx<-650000) 
                  stroke_data.dx +=1296000;
	       else
	       if (stroke_data.dx>650000) 
                  stroke_data.dx -=1296000;
	       ++quot;
	       if (quot==1) goto retry;
	       fprintf(stderr, "Strange values dx = %d , dy = %d\n", 
		               stroke_data.dx, stroke_data.dy);
	    }
	    if (dm<128) {
	       dx[count] = (stroke_data.dx)&255;
	       dy[count] = (stroke_data.dy)&255;
	       ++count;
	    } else {
	       quot = 1+(dm/128);
	       xp = yp = 0;
	       for (s=1; s<=quot; s++) {
		  xt = xp;
		  yt = yp;
                  xp = (s*stroke_data.dx)/quot;
                  yp = (s*stroke_data.dy)/quot;
		  dx[count] = (xp-xt)&255;
		  dy[count] = (yp-yt)&255;
		  ++count;
	       }
	    }
	}
	if (verbose && (i%500)==499)
	   fprintf(stderr, "Read segments %d -- %d from REZ file\n", i-499, i);
	jpd_count[i+1] = count;
	if (segment_header_data.nstrokes>rez_length) 
           rez_length = segment_header_data.nstrokes;
	xt = jpd_count[i+1]-jpd_count[i];
	if (xt>jpd_length) jpd_length = xt;
    }

    fprintf(stderr, "Scanned %d REZ segments, %d rejected\n\n", 
                    numseg, rejected);
    fprintf(stderr, "Max length of REZ segments %d\n", rez_length);
    fprintf(stderr, "Max width of REZ segments %d\n", maxwidth);
    fprintf(stderr, "Max height of REZ segments %d\n", maxheight);
    fprintf(stderr, "Total number of REZ segment strokes %d\n\n", 
                    rez_total_strokes);

    fprintf(stderr, "Max length of JPD segments after refragmentation %d\n", 
                    jpd_length);
    fprintf(stderr, "Total number of JPD segment strokes %d\n\n", count);

    if (format == JPD) {
       bzero(magic, 16);
       strcpy(magic, JPD_MAGIC);
       /* Write 16 bytes = magic(12) + max length of segments(4) */
       for (u=0; u<12; u++) fprintf(fd, "%c", magic[u]);
       fpr4(fd, jpd_length);

       /* Write segment counts for the 5*28=140 continents * types
        * 560 bytes in total
        */
       count = 0;
       for (u=0; u<5; u++)
       for (v=0; v<4; v++) {
	  up = 1<<u;
	  vp = 1<<jpd2rez_type_code[v];
          for (w=jpd_min[v]; w<=jpd_max[v]; w++) {
	     for (i=0; i<header_data.segment_index_count; i++) {
                if (segment_index_data[i].continent!=up ||
                    segment_index_data[i].category!=vp ||
	            jpd_encod[v][segment_index_data[i].type]!= w) continue;
                jpd_seg_count[u][w] += 1;
	        targets[v][segment_index_data[i].type] = '1';
	     }
	     if (verbose)
	        fprintf(stderr, "Section %d %02d :  %d  segments\n",
                        u, w, jpd_seg_count[u][w]);
	     count += jpd_seg_count[u][w];
             fpr4(fd, count);
	  }
       }

       if (verbose)
          for (v=0; v<4; v++)
             fprintf(stderr, "%s : %s\n", type_name[v], targets[v]);

       /* Write numseg headers of 16 bytes; 
	* the segments themselves will start at address  576+numseg*16
        */
       addr = 576+numseg*16;
       for (u=0; u<5; u++)
       for (v=0; v<4; v++) {
	  up = 1<<u;
	  vp = 1<<jpd2rez_type_code[v];
          for (w=jpd_min[v]; w<=jpd_max[v]; w++) {
	     for (i=0; i<header_data.segment_index_count; i++) {
                if (segment_index_data[i].continent!=up ||
                    segment_index_data[i].category!=vp ||
	            jpd_encod[v][segment_index_data[i].type]!= w) continue;
                fpr3(fd, segment_index_data[i].minlong);
                fpr3(fd, segment_index_data[i].maxlong);
                fpr3(fd, segment_index_data[i].minlat);
                fpr3(fd, segment_index_data[i].maxlat);
		/* 6 bytes for orgx, orgy + 2 bytes for each stroke */
                addr += 6+2*(jpd_count[i+1]-jpd_count[i]);
                fpr4(fd, addr);
	     }
	  }
       }
       if (verbose)
	  fprintf(stderr, 
             "Wrote JPD segment headers, %d such segments\n", numseg);

       count = 0;
       for (u=0; u<5; u++)
       for (v=0; v<4; v++) {
	  up = 1<<u;
	  vp = 1<<jpd2rez_type_code[v];
          for (w=jpd_min[v]; w<=jpd_max[v]; w++) {
	     for (i=0; i<header_data.segment_index_count; i++) {
                if (segment_index_data[i].continent!=up ||
                    segment_index_data[i].category!=vp ||
	            jpd_encod[v][segment_index_data[i].type]!= w) continue;
		fpr3(fd, rez_orgx[i]);
		fpr3(fd, rez_orgy[i]);
                for (j=jpd_count[i]; j<jpd_count[i+1]; j++) {
                   fprintf(fd, "%c%c", dx[j], dy[j]);
		   ++count;
		}
	     }
	     if (verbose)
	        fprintf(stderr, 
                   "Wrote JPD data for continent = %d, type = %d\n", u, w);
	  }
       }
       if (verbose)
          fprintf(stderr, "Wrote %d segment strokes in total.\n\n", count);
    }

    if (format==TEXT) {
       k = 0;
       count = 0;
       addr = 576+numseg*16;
       for (u=0; u<5; u++)
       for (v=0; v<4; v++) {
	  up = 1<<u;
	  vp = 1<<jpd2rez_type_code[v];
          for (w=jpd_min[v]; w<=jpd_max[v]; w++) {
	     for (i=0; i<header_data.segment_index_count; i++) {
                if (segment_index_data[i].continent!=up ||
                    segment_index_data[i].category!=vp ||
	            jpd_encod[v][segment_index_data[i].type]!= w) continue;
		addr += 6+2*(jpd_count[i+1]-jpd_count[i]);
	        if ( (list_cont && !index(list_cont, '0'+u)) ||
	             (list_types && !index(list_types, '0'+v)) ) {
		   ++k;
		   continue;
		}
		fprintf(fd, "<%d> %s (%s %d)\nJPD address: %d\n", 
                   k, cont_name[u], type_name[v], w-jpd_min[v], addr);
		++k;
                if (text_output_type == REAL) {
		   fprintf(fd,"Box [%4.6f %4.6f] [%3.6f %3.6f]\nStrokes %d\n", 
		      segment_index_data[i].minlong/3600.0, 
                      segment_index_data[i].maxlong/3600.0,
		      segment_index_data[i].minlat/3600.0, 
                      segment_index_data[i].maxlat/3600.0,
		      jpd_count[i+1]-jpd_count[i]);
		   fprintf(fd, "Origin %4.6f %3.6f\n", 
		      rez_orgx[i]/3600.0, rez_orgy[i]/3600.0);
		} else {
		   fprintf(fd, "Box [%d %d] [%d %d]\nStrokes %d\n", 
		      segment_index_data[i].minlong, 
                      segment_index_data[i].maxlong,
		      segment_index_data[i].minlat, 
                      segment_index_data[i].maxlat,
		      jpd_count[i+1]-jpd_count[i]);
		   fprintf(fd, "Origin %d %d\n", rez_orgx[i], rez_orgy[i]);
		}
                xp = rez_orgx[i];
	        yp = rez_orgy[i];
                for (j=jpd_count[i]; j<jpd_count[i+1]; j++) {
		   if (text_output_type == DX_DY)
                      fprintf(fd, "%d %d\n", dx[j], dy[j]);
		   else {
		      xp += (signed char)dx[j];
		      yp += (signed char)dy[j];
   		      if (text_output_type == INT)
                         fprintf(fd, "%d %d\n", xp, yp);
	              else
                         fprintf(fd, "%4.6f %3.6f\n", xp/3600.0, yp/3600.0);
                   }
		   ++count;
		}
	     }
	     if (verbose)
	        fprintf(stderr, 
                   "Wrote JPD data for continent = %d, type = %d\n", u, w);
	  }
       }
       if (verbose)
          fprintf(stderr, "Wrote %d segment strokes in total.\n\n", count);
    }

    fclose(fd);

    return 0;
}

