/* rmap - vector based global map generating program
 * Copyright (C) 2000 Reza Naima <reza@reza.net>
 *
 * http://www.reza.net/rmap/
 *
 *
 * 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 "rmap.h"
#include "cities.h"
#include "rgb.h"

//The following subroutine is the only one borrowed from 
//the original mapping code -- strait forward bit flipping stuff
//which was contributed by Joe Dellinger (no email for me to
//contact him) 
//
// If you have a better way of accomplishing this, please
// Let me know as I'm sure this can't be supper efficient

#ifdef WORDS_BIGENDIAN
bitflip (BIT32 *x) {
	BIT32 y=0;
	
	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 */


void lc(char *str) {
	// this assumes a null terminating string
	int i; 
	for (i=0;str[i];i++) 
		str[i] = tolower(str[i]);
	
}

// return a pointer to the color struct that 
// contains the rgb values for a color
// or null if nothing is found that matches
struct color* get_color (char* c) {
	int i=0;
	lc(c);
	
	while (colors[i].name != NULL) {
		if (!strcmp(colors[i].name,c))
			return &colors[i];
		i++;
	}
	return NULL;
}


void image_init (struct imageLayout *s, char *filename) {
	FILE *f;
	struct color *c;
	int i,j,pos=0;
	int ret, line_number=0;
	int cat, type;
	char string[256];
	char color_name[100];
	

	// create a page
	s->im = gdImageCreate(s->width, s->height);

	//reset some variables

	bzero(string,256);
	for (i=0; i< 5; i++)
		for (j=0; j<16; j++)
			s->colormap[i][j] = NOPRINT; //no print is default

	// read in the color config file and allocate colors
	f = fopen(filename, "r");
	if (f == NULL) {
		fprintf(stderr,"Couldn't open %s (colormap file)\n", filename);
		exit(-2);
	}

	//now parse the file
	while ((ret = fread(string, 1, 255, f)) != 0) {
		line_number++;
		pos = (int) ftell(f) - ret;
		for (i=0; i<255; i++) {
			if (string[i] == '\n') {
				string[i] = '\0';
				fseek(f,pos+i+1, SEEK_SET);
				break;
			}
		}
		for (j=0; j<i; j++) {
			if (string[j] == '#')  {
				string[j] = '\0';
				break;
			}
		} 
		while (j>0) {
			j--;
			if (string[j] == ' ' || string [j] == '\t') {
				string[j] = '\0';
			} else {
				break;
			}
		}
		if (strlen(string) == 0) 
			continue;

		ret = sscanf(string, "%d %d %s", &cat, &type, color_name);
		if (ret != 3) {
			fprintf(stderr,"Unable to parse the string [%s] on line %d in %s\n", string,line_number,filename);	
			continue;
		}
		if (!strcmp(color_name, "none")) {
			s->colormap[cat][type] = NOPRINT;
		} else {	
			c = get_color(color_name);
			if (c == NULL) {
				fprintf(stderr,"The color %s was not found on line %d in %s\n",
					color_name,line_number,filename);
				continue;
			}
			if (cat > 4 || type > 15) 
				fprintf(stderr,"Declaration value out of range (%d,%d) on line %d in %s\n",
					cat,type, line_number, filename);
			else
			s->colormap[cat][type] = gdImageColorAllocate(s->im, c->r, c->g, c->b);
		}
	}
	
	fclose(f);

	// setup the colors
}


void drawLine (struct imageLayout *s, vector a, vector b, int color_index) {
  	int x1,x2,y1,y2;
		
        //lets try it witout perspective
        x1 = a.item[X] + s->width/2;
        y1 = a.item[Y] + s->height/2;
        x2 = b.item[X] + s->width/2;
        y2 = b.item[Y] + s->height/2;


        // draw the point
        gdImageLine(s->im, x1,y1, x2,y2, color_index);
}   


void makePicture (struct imageLayout *s, char* filename) {
	FILE *f;

	/* Default is to make the picture interlaced */	
	gdImageInterlace(s->im, 1);	

	f = fopen(filename, "wb");
	if (f  == NULL) {
		fprintf(stderr, "Unable to open output file %s\n", filename);
		return;
	}

/* even though gd is cool, they're fucking' lame
** for removing gif support */
#ifdef GD_GIF
	gdImageGif(s->im, f);
#else
#ifdef GD_PNG
	gdImagePng(s->im, f);
#else 
#ifdef GD_JPEG
	gdImageJpeg(s->im, f);
#endif /* GD_JPEG */
#endif /* GD_PNG */
#endif /* GD_GIF */

	fclose(f);
}

/* input is in degrees, output is the matrix */
static matrix rotate(double x, double y, double z) {
	matrix m;
	double p,t,r;

	r = CONV * x; //rho
	p = CONV * y; //psi
	t = CONV * z; //theta
	
	m.item[0][0] =  cos(p)*cos(t);
	m.item[0][1] =  cos(p)*sin(t);
	m.item[0][2] = -sin(p);
	m.item[1][0] =  sin(r)*sin(p)*cos(t) - sin(t)*cos(r);
	m.item[1][1] =  sin(r)*sin(p)*sin(t) + cos(r)*cos(t);
	m.item[1][2] =  sin(r)*cos(p);
	m.item[2][0] =  sin(p)*cos(r)*cos(t) + sin(r)*sin(t);
	m.item[2][1] =  sin(p)*cos(r)*sin(t) - sin(r)*cos(t);
	m.item[2][2] =  cos(r)*sin(p);

	return m;
}

static matrix rotateZ (double deg) {
	matrix m;
	double d;

	d = CONV * deg;

	m.item[0][0] =  cos(d);
	m.item[0][1] =  sin(d);
	m.item[0][2] =  0.;
	m.item[1][0] = -sin(d);
	m.item[1][1] =  cos(d);
	m.item[1][2] =  0.;
	m.item[2][0] =  0.;
	m.item[2][1] =  0.;
	m.item[2][2] =  1.;
	
	return m;
}
static matrix rotateX (double deg) {
	matrix m;
	double d;

	d = CONV * deg;

	m.item[0][0] =  1.;
	m.item[0][1] =  0.;
	m.item[0][2] =  0.;
	m.item[1][0] =  0.;
	m.item[1][1] =  cos(d);
	m.item[1][2] = -sin(d);
	m.item[2][0] =  0.;
	m.item[2][1] =  sin(d);
	m.item[2][2] =  cos(d);
	
	return m;
}
static matrix rotateY (double deg) {
	matrix m;
	double d;

	d = CONV * deg;

	m.item[0][0] =  cos(d);
	m.item[0][1] =  0.;
	m.item[0][2] =  sin(d);
	m.item[1][0] =  0.;
	m.item[1][1] =  1.;
	m.item[1][2] =  0.;
	m.item[2][0] = -sin(d);
	m.item[2][1] =  0.;
	m.item[2][2] =  cos(d);
	
	return m;
}

static vector multiply23 (vector a, matrix b) {
	vector v;
	
	v.item[0] = a.item[0]*b.item[0][0] + a.item[1]*b.item[1][0] + a.item[2]*b.item[2][0];
	v.item[1] = a.item[0]*b.item[0][1] + a.item[1]*b.item[1][1] + a.item[2]*b.item[2][1];
	v.item[2] = a.item[0]*b.item[0][2] + a.item[1]*b.item[1][2] + a.item[2]*b.item[2][2];

	return v;
}

static matrix multiply33 (matrix a, matrix b) {
	matrix m;

	m.item[0][0] = a.item[0][0]*b.item[0][0] + a.item[0][1]*b.item[1][0] + a.item[0][2]*b.item[2][0];
	m.item[0][1] = a.item[0][0]*b.item[0][1] + a.item[0][1]*b.item[1][1] + a.item[0][2]*b.item[2][1];
	m.item[0][2] = a.item[0][0]*b.item[0][2] + a.item[0][1]*b.item[1][2] + a.item[0][2]*b.item[2][2];
	m.item[1][0] = a.item[1][0]*b.item[0][0] + a.item[1][1]*b.item[1][0] + a.item[1][2]*b.item[2][0];
	m.item[1][1] = a.item[1][0]*b.item[0][1] + a.item[1][1]*b.item[1][1] + a.item[1][2]*b.item[2][1];
	m.item[1][2] = a.item[1][0]*b.item[0][2] + a.item[1][1]*b.item[1][2] + a.item[1][2]*b.item[2][2];
	m.item[2][0] = a.item[2][0]*b.item[0][0] + a.item[2][1]*b.item[1][0] + a.item[2][2]*b.item[2][0];
	m.item[2][1] = a.item[2][0]*b.item[0][1] + a.item[2][1]*b.item[1][1] + a.item[2][2]*b.item[2][1];
	m.item[2][2] = a.item[2][0]*b.item[0][2] + a.item[2][1]*b.item[1][2] + a.item[2][2]*b.item[2][2];
	
	return m;
}


static vector sphere2cart (spherePt s) {
        vector v;

        v.item[X] = s.item[RADIUS] * cos(s.item[LATITUDE] * CONV) * sin(-1*s.item[LONGITUDE] * CONV);
        v.item[Y] = s.item[RADIUS] * sin(s.item[LATITUDE] * CONV);
        v.item[Z] = s.item[RADIUS] * cos(s.item[LATITUDE] * CONV) * cos(-1*s.item[LONGITUDE] * CONV);
        return v;
}

static matrix scale (double x, double y, double z) {
	matrix m;
	bzero(&m, sizeof(m));

	m.item[X][X] = x;
	m.item[Y][Y] = y;
	m.item[Z][Z] = z;

	return m;
}

	

static int segment_in_scene (struct imageLayout *s,struct segment_index *i, matrix rot) {
	spherePt max, min;
	vector pt1, pt2;
	int bit_number[9] = {0,1,2,0,3,0,0,0,4};

	
	// first verify if we want this printed
	if (s->colormap[bit_number[i->category]][i->type] == NOPRINT)
		return 0;

	max.item[LONGITUDE] = i->maxlong/3600.;
	max.item[LATITUDE] = i->maxlat/3600.;
	max.item[RADIUS] = EARTH_RADIUS;

	min.item[LONGITUDE] = i->minlong/3600.;
	min.item[LATITUDE] = i->minlat/3600.;
	min.item[RADIUS] = EARTH_RADIUS;

	pt1 = multiply23(sphere2cart(max), rot);
	pt2 = multiply23(sphere2cart(min), rot);

	// check to see if it is z-clipped
	if (s->transparent == 0 && (pt1.item[Z] < 0 || pt2.item[Z] < 0)) {
		return 0; 
	}
	
	if (  (abs(pt1.item[X]) > s->width/2 && abs(pt2.item[X]) > s->width/2) 
	   || (abs(pt1.item[Y]) > s->height/2 && abs(pt2.item[Y]) > s->height/2)) {
		return 0;
	}

	return  1;
}

void draw_circles( struct imageLayout *s, matrix rot) {
	int i,j; 
	spherePt spt1,spt2;
	vector pt1, pt2;	
	int inc=15; //draw line ever so many degrees

	spt1.item[RADIUS] = EARTH_RADIUS;
	spt2.item[RADIUS] = EARTH_RADIUS;

	for (i=0; i<360; i+=inc) {
		for (j=0; j<360; j+=5) {
			spt1.item[LONGITUDE] = i;
			spt1.item[LATITUDE]  = j;
			spt2.item[LONGITUDE] = i;
			spt2.item[LATITUDE]  = j+5;
			pt1 = multiply23(sphere2cart(spt1), rot);
			pt2 = multiply23(sphere2cart(spt2), rot);
			if (pt1.item[Z] > 0) 
				drawLine(s, pt1, pt2, s->colormap[CONF][C_LAT_GRID]);
		}
	}
	for (i=0; i<360; i+=inc) {
		for (j=0; j<360; j+=5) {
			spt1.item[LONGITUDE] = j;
			spt1.item[LATITUDE]  = i;
			spt2.item[LONGITUDE] = j+5;
			spt2.item[LATITUDE]  = i;
			pt1 = multiply23(sphere2cart(spt1), rot);
			pt2 = multiply23(sphere2cart(spt2), rot);
			if (pt1.item[Z] > 0) 
				drawLine(s, pt1, pt2, s->colormap[CONF][C_LONG_GRID]);
		}
	}
}
		
		
void addText (struct imageLayout *s, spherePt spt, matrix rot, char* text) {
	vector pt;
	int x,y;
	int text_x, text_y;
	int circle_size = 8;
	
	//determine the point, post transformation
	pt = multiply23(sphere2cart(spt), rot);

	if (pt.item[Z] < 0) 
		return;

	x = pt.item[X] + s->width/2;
	y = pt.item[Y] + s->height/2;

	//verify that the point is on the map, return if not
	if (x > s->width || x < 0 || y > s->height || y < 0)
		return;
	
	// draw a circle around the point in question
	gdImageArc(s->im, x,y, circle_size, circle_size, 0,360, s->colormap[CONF][C_TEXT_CIRCLE]);

	// determine the text location relative to the circle
	text_x = (x > s->width/2) ? (x - strlen(text)*gdFontSmall->w - circle_size/2) : 
				  (x + circle_size/2);
	text_y = (y > s->height/2) ? (y - circle_size/2) : 
				   (y + gdFontSmall->h + circle_size/2);
	text_y = y;
	// add text
	gdImageString(s->im, gdFontSmall, text_x, text_y, text, s->colormap[CONF][C_TEXT]);
}

void generate(struct imageLayout *scene, struct options *o) {
	// misc variables
	spherePt s1,s2;
	matrix rot;
	int i,j;
	char* string;
	//filehandle
	int fh; 
	//filenames
	struct header header_data;
	struct segment_index segment_index_data;
	struct segment_header segment_header_data;
	struct stroke stroke_data;
	//simple mapping array
	int bit_number[9] = {0,1,2,0,3,0,0,0,4};

	
	// generate the proper rotation&scale matrix
	rot = multiply33 (
		multiply33( 
			multiply33( 
				rotateZ(180), multiply33( 
					rotateY(o->yrot), 
					rotateX(o->xrot)
					)
				), 
				scale(o->zoom,o->zoom,o->zoom)
		),
		rotateZ(o->zrot)
		);
	
	// setup the image
	image_init(scene, o->colorfile);
	if (o->gridlines) 
		draw_circles(scene, rot);
	
	//Open the datafile and find graph the appropriate sectors
	fh = open(o->mapfile, O_RDONLY, 0);
	if (fh == -1) {
		fprintf(stderr,"Couldn't open input vector file %s.\n", o->mapfile);
		exit (-1);
	}
	// read the header
	read(fh, &header_data, sizeof(header_data));
#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 != HEADER_MAGIC) {
		fprintf(stderr, "The magic was not valid for the input file.\n");
		fprintf(stderr, "This could be an endian problem, or you\n"); 
		fprintf(stderr, "could be pointing to the wrong file.\n");
		fprintf(stderr, "(%s's magic = %X and it's supposed to be %X)\n", 
			o->mapfile, header_data.magic, HEADER_MAGIC);
		exit(-2);
	}
	// itterate through all the segment_indexes to see which ones are interesting
	for (i=0; i<header_data.segment_index_count; i++) {
		lseek(fh, sizeof(header_data) + sizeof(segment_index_data)*i, SEEK_SET);
		read(fh, &segment_index_data, sizeof(segment_index_data));
#ifdef WORDS_BIGENDIAN
		bitflip( &(segment_index_data.maxlat) );
		bitflip( &(segment_index_data.minlat) );
		bitflip( &(segment_index_data.maxlong) );
		bitflip( &(segment_index_data.minlong) );
		bitflip( &(segment_index_data.segment_address) );
		bitflip( &(segment_index_data.continent) );
		bitflip( &(segment_index_data.category) );
		bitflip( &(segment_index_data.type) );
#endif /* WORDS_BIGENDIAN */

		if ( (segment_index_data.continent & o->desired_continents)
		 && (segment_index_data.category & o->desired_categories) ) { 
			//it has interesting information, let's look to see if the
			//data is in our viewport
			if (segment_in_scene(scene, &segment_index_data, rot)) {
				//iterate through the data and pass it to draw
				lseek(fh, segment_index_data.segment_address, SEEK_SET);
				read(fh, &segment_header_data, sizeof(segment_header_data));
#ifdef WORDS_BIGENDIAN
				bitflip( &(segment_header_data.orgx) );
				bitflip( &(segment_header_data.orgy) );
				bitflip( &(segment_header_data.nstrokes) );
#endif /* WORDS_BIGENDIAN */
				s1.item[LONGITUDE] = (segment_header_data.orgx/3600.0);
				s1.item[LATITUDE]  = (segment_header_data.orgy/3600.0);
				s1.item[RADIUS]=EARTH_RADIUS;
				s2.item[RADIUS]=EARTH_RADIUS;
				for (j=0; j<segment_header_data.nstrokes; j++) {
					vector pt1, pt2;

					// itterate and draw all the appropriate points
					read(fh, &stroke_data, sizeof(stroke_data));
#ifdef WORDS_BIGENDIAN
					bitflip( &(stroke_data.dx) );
					bitflip( &(stroke_data.dy) );
#endif /* WORDS_BIGENDIAN */
					s2.item[LONGITUDE] = s1.item[LONGITUDE]+(stroke_data.dx/3600.0);
					s2.item[LATITUDE]  = s1.item[LATITUDE] +(stroke_data.dy/3600.0);

					pt1 = multiply23(sphere2cart(s1), rot);
					pt2 = multiply23(sphere2cart(s2), rot);
	
					drawLine(scene, pt1, pt2, 
						scene->colormap[bit_number[segment_index_data.category]][segment_index_data.type]);

					s1.item[LONGITUDE] = s2.item[LONGITUDE];
					s1.item[LATITUDE]  = s2.item[LATITUDE];
				}
			}
		}
	}

	// iterate through data file and add lables
	if (o->cities) {
		i = 0;
		s1.item[RADIUS]=EARTH_RADIUS;
		while (cities[i].name != NULL) {
			s1.item[LONGITUDE] = cities[i].longitude;
			s1.item[LATITUDE] = cities[i].latitude;
			addText(scene, s1, rot, cities[i].name);
			i++;
		}
	}
	makePicture(scene, o->outfile);
}



syntax highlighted by Code2HTML, v. 0.9.1