static char rcsid[] = "$Id: ciamap.c,v 3.10 1993/01/05 01:37:03 putz Exp $";
/*****************************************************************************
 * ciamap.c - This program reads a map database file in the CIA World Base
 *	format and produces a netmap-format output file.
 *
 * Originally created by Brian Reid (reid@pa.dec.com)
 * Modified March 1991 by Steve Putz (putz@parc.xerox.com)
 *****************************************************************************/

#include <stdio.h>
#include <math.h>
#include <ctype.h>
#include <strings.h>
#include <sys/file.h>
#include <sys/param.h>		/* for MAXPATHLEN */
#include "cbdmap.h"

#define	BIT32	int		/* change these definitions to suit your */
#define	BIT16	short		/* own machine				 */
#define	BIT8	char
#define	TRUE	1
#define	FALSE	0
/*
 * The data-compression scheme uses integer seconds to represent lat/long,
 * and stores each stroke as a [dx,dy] from the previous point. If dy will
 * fit into 8 bits and dx into 7 bits, then the entire [dx,dy] is stored in a
 * 16-bit field with bit 0x4000 turned on as a flag. If either value is too
 * large for that scheme, then both are stored as 32-bit values, with the
 * 0x40000000 bit turned off (even in negative numbers) in the first of them.
 */

#define	MAXSEG	20000		/* maximum strokes in a segment */
#define	MAXSEGS 10000

#define abs(x)	(x<0 ? -(x) : x)
#define sign(x) ((x>=0) ? 1 : -1)

struct seghead  sb;

BIT16           segbuf[MAXSEG];

struct segdict  sd[MAXSEGS];
static long	acodes[MAXSEGS];
static long	acodeCounts[MAXSEGS];
static char	CodeTable[100];

int             MapDetail = 99;

int             rightShift = 0;
int             filterBits = 0;
int             paethFlag = 0;
int             inrec_bytes = 0;
int             debug = 0;

#define PUTSHORT(val, file) { putc((val)>>8, file); putc((val)&0xFF, file); }
#define PUTCOORD(seconds, file) PUTSHORT((seconds)/20, file)

/* ------------------------------------------------------------------------ */

main(argc, argv)
    int             argc;
    char          **argv;
{
    extern int      optind;
    extern char    *optarg;
    extern char    *strcpy();
    int             c, initdone, badopts;
    char	    *outdir = NULL, *outfname = NULL;

    for (c = 0; c < 100; c++) CodeTable[c] = c;

    initdone = 0;
    badopts = 0;
    while ((c = getopt(argc, argv, "D:d:pn:s:f:t:T:O:o:m")) != EOF)
	switch (c) {
	case 'D':
	    /* -D: debug. Argument is an integer debuging flags mask. */
	    debug = strtol(optarg, NULL, 0);
	    break;
	case 'd':
	    /*
	     * -d: detail. Argument is an integer giving map detail to
	     * include.
	     */
	    sscanf(optarg, "%d", &MapDetail);
	    break;
	case 'p':
	    /* -p: Paeth format. Produce Paeth format map files.  */
	    paethFlag = TRUE;
	    break;
	case 'n':
	    /*
	     * -n: no newline chars in input. Argument is bytes/record.
	     */
	    sscanf(optarg, "%d", &inrec_bytes);
	    break;
	case 's':
	    /*
	     * -s: Scale data. Argument is number of bits to right shift
	     * coordinate data.
	     */
	    sscanf(optarg, "%d", &rightShift);
	    break;
	case 'f':
	    /* -f: Filter data. Argument is number of bits to smooth. */
	    sscanf(optarg, "%d", &filterBits);
	    break;
	case 't':
	    /* -t: translate codes. Argument is feature code file name. */
	    ReadCodes(optarg, CodeTable);
	    break;
	case 'T':
	    /* -T: translate codes. warn of features not in code file. */
	    for (c = 0; c < 100; c++) CodeTable[c] = -1;
	    ReadCodes(optarg, CodeTable);
	    break;
	case 'O':
	    /* -O: specify output directory */
	    outdir = optarg;
	    break;
	case 'o':
	    /* -o: specify output file */
	    outfname = optarg;
	    break;
	case 'm':
	    /* -m: Ascii map files follow. "-m f2 f2 f3" */
	    for (; optind < argc && argv[optind][0] != '-'; optind++) {
		if (!initdone);
		initdone++;
		TranslateMap(argv[optind], outdir, outfname);
	    }
	    break;
	default:
	    badopts++;
	}
    fflush(stdout);
    if (badopts) {
	exit(2);
    }
}


/* ------------------------------------------------------------------------ */

ReadCodes(name, codetable)
    char           *name;
    char	*codetable;
{
    FILE *df;
    int code1, code2, res;
    char desc[100];

    if (!(df = fopen(name, "r"))) {
	perror(name);
	exit(1);
    }
    while (!feof(df)) {
	*desc = '\0';
	res = fscanf(df, "%d %d %[^\n]\n", &code1, &code2, desc);
	if (res < 2) {
	    fscanf(df, "%[^\n]\n", desc);
	    if (debug & 0x1000)
		fprintf(stdout, "%s\n", desc);
	} else {
	    if (debug & 0x2000)
		fprintf(stdout, "%d -> %d %s\n", code1, code2, desc);
	    codetable[code1%100] = code2;
	}
    }

    fclose(df);

} /* ReadCodes */


/* ------------------------------------------------------------------------ */

TranslateMap(name, outdir, outfname)
    char           *name, *outdir, *outfname;
{
    FILE           *df;
    char            line[100], of[MAXPATHLEN], *bname, *ext,
		    lad,	/* 'N' or 'S' */
                    lod;	/* 'E' or 'W' */
    int             Ofile = -1, first, lsn, irnk, nop, ladg, lam, las,
                    lodg, lom, los,
		    ipn,	/* polygon number */
		    acode, acount,
                    big = 0, small = 0, medium = 0, skippedSegs = 0,
		    totalStrokes = 0, skippedStrokes = 0, lnum, nc,
                    i, j, strokecount, segmax, segsize, segcount, bytecount;
    BIT32           jlat,	/* integer latitude in seconds */
                    jlong,	/* integer longitude in seconds */
                    haveLat = 0, haveLong = 0, dx, dy;
    struct cbdhead  header;
    FILE           *paethIndex = NULL, *paethData = NULL;
    int             paethCount, paethTotal = 0;
    int             headerSize = CBD_HEADSIZE2;
    long            roundBit = 1 << (rightShift - 1);

    if (!(df = fopen(name, "r"))) {
	perror(name);
	exit(1);
    }
    bname = rindex(name, '/');
    if (bname) bname++;
    else bname = name;

    if (outdir) sprintf(of, "%s/", outdir);
    else *of = '\0';

    if (outfname) strcat(of, outfname);
    else strcat(of, bname);

    ext = index(of, '.');
    if (ext)
	*ext = '\0';
    else
	ext = of + strlen(of);

    if (paethFlag) {
	strcpy(ext, ".index");
	paethIndex = fopen(of, "w");
	strcpy(ext, ".map");
	paethData = fopen(of, "w");
	if (paethIndex == NULL || paethData == NULL) {
	    perror(of);
	    exit(1);
	}
    } else {
	if (!outfname) {
	    if (rightShift) {
		sprintf(ext, "%+d", -rightShift);
		ext += strlen(ext);
	    }
	    if (filterBits) {
		sprintf(ext, "f%d", filterBits);
		ext += strlen(ext);
	    }
	    strcpy(ext, ".cbd");
	}
	Ofile = open(of, O_RDWR | O_CREAT | O_TRUNC, 0666);
	if (Ofile < 0) {
	    perror(of);
	    exit(1);
	}
    }

    fprintf(stdout, "Reading %s\nWriting %s\n", name, of);

    bzero(&header, sizeof(header));
    /* note that header.magic is zero until we are done */
    header.scale_shift = -rightShift;
    header.minlat = DEG90_SECS;
    header.maxlat = -DEG90_SECS;
    header.minlong = DEG180_SECS;
    header.maxlong = -DEG180_SECS;

    if (Ofile >= 0) {		/* reserve space for header */
	write(Ofile, &header, headerSize);
    }
    acount = 0;
    segcount = 0;
    segmax = 0;
    line[inrec_bytes] = '\0';

    /* The header line tells us  the serial number, rank, and path size */
    lnum = 0;
    for (;;) {
	if (inrec_bytes) {
	    if (fread(line, inrec_bytes, 1, df) != 1) break;
	} else {
	    if (fgets(line, 60, df) == NULL) break;
	}
	lnum++;
	for (nc = 0; nc < 20 && line[nc] != NULL; nc++)
	   if (line[nc] == ' ') line[nc] = '0';

	if (nc < 18)
	    continue;

	sscanf(line, "%7d%2d%6d%5d", &lsn, &irnk, &nop, &acode);
	if (debug & 0x100)
	    fprintf(stdout, "%d: ID=%d, rank=%d, NP=%d, code=%d\n",
		segcount, lsn, irnk, nop, acode);
	acode = acode * 100 + irnk;
	if (CodeTable[irnk] > 32 || CodeTable[irnk] < 1) {
	    fprintf(stdout, "WARNING: file %s, seg %d: ID=%d, rank=%d, NP=%d, code=%d -> %d\n",
		name, segcount, lsn, irnk, nop, acode, CodeTable[irnk]);
	    irnk = 32;
	} else {
	    irnk = CodeTable[irnk];
	}

	if (irnk > MapDetail) {
	    for (i = 1; i <= nop; i++) {
		if (inrec_bytes) {
		    if (fread(line, inrec_bytes, 1, df) != 1) break;
		} else {
		    if (fgets(line, 60, df) == NULL) break;
		}
		lnum++;
	    }
	} else {
	    sb.id = lsn;
	    first = FALSE;
	    if (segcount > MAXSEGS) {
		fprintf(stdout, "Segment count overflow.\n");
		close(Ofile);
		fclose(df);
		return;
	    }
	    if (Ofile >= 0)
		sd[segcount].absaddr = tell(Ofile);
	    sd[segcount].segid = lsn;
	    sd[segcount].rank = irnk;

	    sd[segcount].minlat = DEG90_SECS;
	    sd[segcount].maxlat = -DEG90_SECS;
	    sd[segcount].minlong = DEG180_SECS;
	    sd[segcount].maxlong = -DEG180_SECS;

	    {   int found = 0;
		for (i = 0; i < acount; i++) {
		    if (acodes[i] == acode) {
			acodeCounts[i]++;
			found = 1;
			break;
		    }
		}
		if (!found) {
		    acodeCounts[acount] = 1;
		    acodes[acount++] = acode;
		    if (debug & 0x800)
			fprintf(stdout, "code %d\n", acode);
		}
	    }

	    bytecount = 0;
	    segsize = 0;
	    paethCount = 0;
	    for (i = 1; i <= nop; i++) {
		if (inrec_bytes) {
		    if (fread(line, inrec_bytes, 1, df) != 1) break;
		} else {
		    if (fgets(line, 60, df) == NULL) break;
		}
		lnum++;
		for (j = 0; j <= 19; j++) {
		    if (line[j] == ' ')
			line[j] = '0';
		}
#define DIG(x) ((int)(x)-48)
		ladg = 10 * DIG(line[0]) + DIG(line[1]);
		lam = 10 * DIG(line[2]) + DIG(line[3]);
		las = 10 * DIG(line[4]) + DIG(line[5]);
		lad = line[6];
		lodg = 100 * DIG(line[7]) + 10 * DIG(line[8]) + DIG(line[9]);
		lom = 10 * DIG(line[10]) + DIG(line[11]);
		los = 10 * DIG(line[12]) + DIG(line[13]);
		lod = line[14];
		ipn = 1000 * DIG(line[16]) + 100 * DIG(line[17]) + 10 *
			DIG(line[18]) + DIG(line[19]);

		if (debug & 0x200)
		    fprintf(stdout, "%d: %02d %02d %02d %c %03d %02d %02d %c %05d\n",
			segcount, ladg, lam, las, lad, lodg, lom, los, lod, ipn);

		if (ipn != i) {
		    fprintf(stdout, "Data error at line %d, sequence %d, segment %d: expected %d but found: %d\n%s\n",
			lnum, lsn, segcount, i, ipn, line);
		    if (ipn > 0 && ipn < nop)
			i = ipn;
		}
		jlat = 3600 * ladg + 60 * lam + las;
		if (lad == 'S')
		    jlat = -jlat;
		jlong = 3600 * lodg + 60 * lom + los;
		if (lod == 'W')
		    jlong = -jlong;
		if (debug & 0x04)
		    fprintf(stdout, "(%.4f %.4f) = (%d %d) [%x %x]",
		     jlong / 3600.0, jlat / 3600.0, jlong, jlat, jlong, jlat);
		if (rightShift > 0) {
		    if (jlong & roundBit) {
			jlong = (jlong >> rightShift) + 1;
			if (debug & 0x20) fprintf(stdout, " x%+1 ");
		    } else {
			jlong >>= rightShift;
		    }
		    if (jlat & roundBit) {
			jlat = (jlat >> rightShift) + 1;
			if (debug & 0x20) fprintf(stdout, " y%+1 ");
		    } else {
			jlat >>= rightShift;
		    }
		    if (debug & 0x10)
			fprintf(stdout, "-> (%d %d) [%x %x]", jlong, jlat, jlong, jlat);
		}
		if (debug & 0x04)
		    fprintf(stdout, "\n");
		if (sd[segcount].maxlat < jlat)
		    sd[segcount].maxlat = jlat;
		if (sd[segcount].minlat > jlat)
		    sd[segcount].minlat = jlat;
		if (sd[segcount].maxlong < jlong)
		    sd[segcount].maxlong = jlong;
		if (sd[segcount].minlong > jlong)
		    sd[segcount].minlong = jlong;
		if (!first) {
		    first = TRUE;
		    sb.orgx = jlong;
		    sb.orgy = jlat;
		    strokecount = 0;
		} else {
		    dx = jlong - haveLong;
		    dy = jlat - haveLat;
		    if (dx == 0 && dy == 0 ||
				i < nop && abs(dx) + abs(dy) <= filterBits) {
			skippedStrokes++;
			if (debug & 0x02)
			    fprintf(stdout, "skipped: (%d %d)\n", dx, dy);
			continue;	/* don't set haveLat, haveLong */
		    } else {
			totalStrokes++;
			strokecount++;
			if (abs(dx) > MAX8x || abs(dy) > MAX8y) {
			    /*
			     * Encode this transition as a pair of 32-bit
			     * values.
			     */
			    big++;
			    if (abs(dx) <= 0x3FFF && abs(dy) <= 0x7FFF)
				medium++;
			    bytecount += 8;
			    if (segsize + 4 > MAXSEG) {
				fprintf(stdout, "segsize overflow.\n");
				close(Ofile);
				fclose(df);
				return;
			    }
			    segbuf[segsize++] = (dx >> 16) & ~SHORTFLAG;
			    segbuf[segsize++] = dx & 0xFFFF;
			    segbuf[segsize++] = (dy >> 16);
			    segbuf[segsize++] = dy & 0xFFFF;
			    if (debug & 0x02)
				fprintf(stdout, "%4d: long (%d %d): %04x %04x %04x %04x\n", strokecount, dx, dy, segbuf[segsize - 4] & 0xFFFF, segbuf[segsize - 3] & 0xFFFF, segbuf[segsize - 2] & 0xFFFF, segbuf[segsize - 1] & 0xFFFF);
			} else {
			    /*
			     * Encode this transition as a pair of 8-bit
			     * values.
			     */
			    small++;
			    bytecount += 2;
			    if (segsize + 1 > MAXSEG) {
				fprintf(stdout, "segsize overflow.\n");
				close(Ofile);
				fclose(df);
				return;
			    }
			    segbuf[segsize++] =
				SHORTFLAG |
				(dx << 8) |
				(dy & 0xFF);
			    if (debug & 0x02)
				fprintf(stdout, "%4d: short (%d %d): %04x\n", strokecount, dx, dy, segbuf[segsize - 1] & 0xFFFF);
			}	/* short encoding */
			if (paethFlag) {
			    PUTCOORD(jlat, paethData);
			    PUTCOORD(jlong, paethData);
			    paethCount++;
			}
		    }		/* dx,dy != 0 */
		}		/* not first */
		haveLat = jlat;
		haveLong = jlong;
	    }

	    if (strokecount == 0) {
		skippedSegs++;	/* reuse same segment */
	    } else if (Ofile >= 0) {
		sd[segcount].nbytes = bytecount;
		sb.nstrokes = strokecount;
		write(Ofile, &sb, sizeof sb);
		write(Ofile, segbuf, bytecount);
		if (debug & 0x01)
		    fprintf(stdout, "seg %d: (%.4f %.4f)=(%d %d) %d strokes %d bytes, code %03d %04d\n", sb.id, (sb.orgx << rightShift) / 3600.0, (sb.orgy << rightShift) / 3600.0, sb.orgx, sb.orgy, sb.nstrokes, bytecount, acode/10000, acode % 10000);
		if (debug & 0x0400)
		    fprintf(stdout, "seg NSEW = (%.4f %.4f %.4f %.4f)\n",
			    (sd[segcount].maxlat << rightShift) / 3600.0,
			    (sd[segcount].minlat << rightShift) / 3600.0,
			    (sd[segcount].maxlong << rightShift) / 3600.0,
			    (sd[segcount].minlong << rightShift) / 3600.0);
		header.features |= (1 << (sd[segcount].rank - 1));
		if (header.maxlat < sd[segcount].maxlat)
		    header.maxlat = sd[segcount].maxlat;
		if (header.minlat > sd[segcount].minlat)
		    header.minlat = sd[segcount].maxlat;
		if (header.maxlong < sd[segcount].maxlong)
		    header.maxlong = sd[segcount].maxlong;
		if (header.minlong > sd[segcount].minlong)
		    header.minlong = sd[segcount].minlong;
		segcount++;
	    } else if (paethFlag) {
		PUTCOORD(sd[segcount].maxlat, paethIndex);
		PUTCOORD(sd[segcount].minlat, paethIndex);
		PUTCOORD(sd[segcount].maxlong, paethIndex);
		PUTCOORD(sd[segcount].minlong, paethIndex);
		PUTSHORT(paethCount, paethIndex);
		paethTotal += paethCount;
	    }
	}
	/* end of each segment */

	if (segsize > segmax)
	    segmax = segsize;
    }
    /* end of file */
    header.magic = headerSize == CBD_HEADSIZE2 ? CBD_MAGIC2 : CBD_MAGIC;
    header.dictaddr = tell(Ofile);
    header.segcount = segcount;
    header.segsize = segcount * sizeof(struct segdict);
    header.segmax = segmax;

    fprintf(stdout,
	    "Segdict has %d entries (%d skipped), %d bytes, starts at %d.\n\
	%d strokes (%d skipped)\n\
	Largest segment has %d entries (%d bytes)\n\
	%d small entries, %d big (%.2f%% of space), %d medium\n",
	    segcount, skippedSegs, header.segsize, header.dictaddr,
	    totalStrokes, skippedStrokes,
	    segmax, 2 * segmax,
	    small, big, (double) 100 * big * 4 / (big * 4 + small), medium);
    {	int i;
	fprintf(stdout, "features:");
	for (i = 1; i <= 32; i++)
	    if (header.features & (1<<(i-1))) fprintf(stdout, " %d", i);
	fprintf(stdout, "\n");
	fprintf(stdout, "%d codes:\n", acount);
	for (i = 0; i < acount; i++)
	    fprintf(stdout, "code %07d %5d\n", acodes[i], acodeCounts[i]);
    }
    if (paethFlag) {
	fprintf(stdout, "\t%d paeth points\n", paethTotal);
	fclose(paethData);
	fclose(paethIndex);
    } else {
	write(Ofile, sd, (int) header.segsize);
	lseek(Ofile, 0L, L_SET);
	write(Ofile, &header, headerSize);
	close(Ofile);
    }
    fclose(df);
}
