/*
 *   Copyright (C) 1991-2000 by Jonathan Naylor HB9DRD/G4KLX
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>

#include <gtk/gtk.h>

#include "global.h"

static struct {
	char   *Name;
	char   *File_Name;
	double Min_Longitude;
	double Min_Latitude;
	double Max_Longitude;
	double Max_Latitude;
} MapInfo[] = {
	{"Africa",      "africa.pbm",      -29.4440, -54.7660,  74.0970,  41.5830},
	{"Asia",        "asia.pbm",         32.6750, -11.1440, 180.3679,  79.6949},
	{"Australasia", "australasia.pbm", 112.9570, -50.9990, 184.5289, -10.7920},
	{"Europe",      "europe.pbm",      -25.0520,  31.0960, 105.0430,  81.4910},
	{"N.America",   "namerica.pbm",   -178.0709,  25.2050, -52.8140,  83.1390},
	{"Pacific",     "pacific.pbm",    -177.7920, -27.5060, -91.1169,  28.3850},
	{"S.America",   "samerica.pbm",   -177.7920, -55.7040, -34.7290,  32.4820},
	{"USA",         "usa.pbm",        -124.6833,  25.1326, -67.0096,  49.3810},
	{"World",       "world.pbm",      -179.1379, -85.5060, 180.3679,  83.7540}
};

static double Sat_Long  = -1.0;
static double Sat_Lat   = -1.0;
static double Sat_Angle = -1.0;

GdkGC *BackgroundGC;
GdkGC *CitiesGC;
GdkGC *LandGC;
GdkGC *FootprintGC;
GdkGC *SatelliteGC;
GdkGC *UserGC;

static GdkGC *TextFgGC;
static GdkGC *TextBgGC;

static GdkFont *MapFont;
static GdkFont *TextFont;

static GdkPixmap *Map;
static GdkPixmap *Text;

static void     Init_Map_Data(void);
static void     Init_Text_Data(void);
static void     Redraw_Text(int, int, int, int);
static double   Calc_X_Scale(void);
static double   Calc_Y_Scale(void);
static int      Long_To_X(double, double);
static int      Lat_To_Y(double, double);
static void     Draw_Line(int, int, int, int);

void MapCb(GtkWidget *w, gpointer data)
{
	int n = (int)data;
	
	if (User_Data.Map_No != n) {
		User_Data.Map_No = n;
		Load_Map();
		Redraw_Map(0, 0, MAP_WIDTH, MAP_HEIGHT);
	}
}

gint MapAreaCb(GtkWidget *w, GdkEventExpose *event)
{
	static int First_Time = TRUE;

	if (First_Time) {
		Init_Map_Data();
		First_Time = FALSE;
		Map = gdk_pixmap_new(MapArea->window, MAP_WIDTH, MAP_HEIGHT, -1);
		gdk_draw_rectangle(Map, BackgroundGC, TRUE, 0, 0, MAP_WIDTH, MAP_HEIGHT);
		Load_Map();
	}

	Redraw_Map(event->area.x, event->area.y, event->area.width, event->area.height);

	return FALSE;
}

void Clear_Text(void)
{
	gdk_draw_rectangle(Text, TextBgGC, TRUE, 0, 0, MAP_WIDTH, MAP_TEXT_HEIGHT);
}

void Refresh_Text(void)
{
	Redraw_Text(0, 0, MAP_WIDTH, MAP_TEXT_HEIGHT);
}

void Write_Text(int pos, char *text)
{
	int x, y;

	switch (pos) {
		case TEXT_SAT:
			x = (MAP_WIDTH / 12) * 0 + 5;
			y = (MAP_TEXT_HEIGHT / 3) * 1 - 5;
			break;
		case TEXT_DATETIME:
			x = (MAP_WIDTH / 12) * 3;
			y = (MAP_TEXT_HEIGHT / 3) * 1 - 5;
			break;
		case TEXT_AZIMUTH:
			x = (MAP_WIDTH / 12) * 8;
			y = (MAP_TEXT_HEIGHT / 3) * 1 - 5;
			break;
		case TEXT_ELEVATION:
			x = (MAP_WIDTH / 12) * 10;
			y = (MAP_TEXT_HEIGHT / 3) * 1 - 5;
			break;
		case TEXT_MA:
			x = (MAP_WIDTH / 12) * 10;
			y = (MAP_TEXT_HEIGHT / 3) * 2 - 5;
			break;
		case TEXT_MODE:
			x = (MAP_WIDTH / 12) * 0 + 5;
			y = (MAP_TEXT_HEIGHT / 3) * 2 - 5;
			break;
		case TEXT_RANGE:
			x = (MAP_WIDTH / 12) * 3;
			y = (MAP_TEXT_HEIGHT / 3) * 2 - 5;
			break;
		case TEXT_ORBIT:
			x = (MAP_WIDTH / 12) * 6;
			y = (MAP_TEXT_HEIGHT / 3) * 2 - 5;
			break;
		case TEXT_SQUINT:
			x = (MAP_WIDTH / 12) * 8;
			y = (MAP_TEXT_HEIGHT / 3) * 2 - 5;
			break;
		case TEXT_FREQ1:
			x = (MAP_WIDTH / 12) * 0 + 5;
			y = (MAP_TEXT_HEIGHT / 3) * 3 - 5;
			break;
		case TEXT_FREQ2:
			x = (MAP_WIDTH / 12) * 4;
			y = (MAP_TEXT_HEIGHT / 3) * 3 - 5;
			break;
		case TEXT_FREQ3:
			x = (MAP_WIDTH / 12) * 8;
			y = (MAP_TEXT_HEIGHT / 3) * 3 - 5;
			break;
		default:
			g_error("mtrack: unknown text position %d\n", pos);
			return;
	}

	gdk_draw_string(Text, TextFont, TextFgGC, x, y, text);
}

gint MapTextCb(GtkWidget *w, GdkEventExpose *event)
{
	static int First_Time = TRUE;

	if (First_Time) {
		Init_Text_Data();
		First_Time = FALSE;
		Text = gdk_pixmap_new(MapText->window, MAP_WIDTH, MAP_TEXT_HEIGHT, -1);
		gdk_draw_rectangle(Text, TextBgGC, TRUE, 0, 0, MAP_WIDTH, MAP_TEXT_HEIGHT);
	}

	Redraw_Text(event->area.x, event->area.y, event->area.width, event->area.height);

	return FALSE;
}

static void Init_Map_Data(void)
{
	Allocate_Colours();

	MapFont = gdk_font_load("-adobe-helvetica-medium-r-normal-*-*-80-*-*-p-*-iso8859-1");

	BackgroundGC = gdk_gc_new(MapArea->window);
	gdk_gc_set_foreground(BackgroundGC, &User_Data.Colours[COLOUR_BACKGROUND]);

	CitiesGC = gdk_gc_new(MapArea->window);
	gdk_gc_set_foreground(CitiesGC, &User_Data.Colours[COLOUR_CITIES]);

	FootprintGC  = gdk_gc_new(MapArea->window);
	gdk_gc_set_foreground(FootprintGC, &User_Data.Colours[COLOUR_FOOTPRINT]);

	LandGC       = gdk_gc_new(MapArea->window);
	gdk_gc_set_foreground(LandGC, &User_Data.Colours[COLOUR_LAND]);

	SatelliteGC  = gdk_gc_new(MapArea->window);
	gdk_gc_set_foreground(SatelliteGC, &User_Data.Colours[COLOUR_SATELLITE]);
	gdk_gc_set_font(SatelliteGC, MapFont);

	UserGC       = gdk_gc_new(MapArea->window);
	gdk_gc_set_foreground(UserGC, &User_Data.Colours[COLOUR_USER]);
	gdk_gc_set_font(UserGC, MapFont);
}

static void Init_Text_Data(void)
{
	Allocate_Colours();

	TextFont = gdk_font_load("-adobe-helvetica-medium-r-normal-*-*-120-*-*-p-*-iso8859-1");

	TextBgGC     = gdk_gc_new(MapText->window);
	gdk_gc_set_foreground(TextBgGC, &Text_Bg_Colour);

	TextFgGC     = gdk_gc_new(MapText->window);
	gdk_gc_set_foreground(TextFgGC, &Text_Fg_Colour);
	gdk_gc_set_font(TextFgGC, TextFont);
}

void Redraw_Map(int x, int y, int Width, int Height)
{
	gdk_draw_pixmap(MapArea->window, BackgroundGC, Map, x, y, x, y, Width, Height);

	Plot_Cities();

	Plot_User();

	Redraw_Satellite();
}

static void Redraw_Text(int x, int y, int Width, int Height)
{
	gdk_draw_pixmap(MapText->window, TextBgGC, Text, x, y, x, y, Width, Height);
}

void Load_Map(void)
{
	unsigned char Buffer[98];
	FILE *fp;
	int  i;
	int  x;
	int  y = 0;

	sprintf(Buffer, "%s/%s", MAPDIR, MapInfo[User_Data.Map_No].File_Name);

	gdk_draw_rectangle(Map, BackgroundGC, TRUE, 0, 0, MAP_WIDTH, MAP_HEIGHT);

	if ((fp = fopen(Buffer, "r")) == NULL) {
		sprintf(Buffer, "Cannot open map file %s", MapInfo[User_Data.Map_No].File_Name);
		Error_Box(Buffer);
		return;
	}

	fgets(Buffer, 40, fp);
	
	if (strcmp(Buffer, "P4\n") != 0) {
		sprintf(Buffer, "Map file %s is corrupt", MapInfo[User_Data.Map_No].File_Name);
		Error_Box(Buffer);
		return;
	}

	fgets(Buffer, 40, fp);
	
	if (strcmp(Buffer, "780 400\n") != 0) {
		sprintf(Buffer, "Map file %s is corrupt", MapInfo[User_Data.Map_No].File_Name);
		Error_Box(Buffer);
		return;
	}

	while (fread(Buffer, 98, 1, fp) == 1) {
		for (x = 0; x < 98; x++)
			for (i = 0; i < 8; i++)
				if ((Buffer[x] & (0x80 >> i)) != 0x00)
					gdk_draw_point(Map, LandGC, x * 8 + i, y);

		y++;
	}

	fclose(fp);
}

void Plot_User(void)
{
	int x, y;

	x = Long_To_X(User_Data.Longitude, Calc_X_Scale());
	y = Lat_To_Y(User_Data.Latitude, Calc_Y_Scale());

	if (x > 0 && y > 0 && x < MAP_WIDTH && y < MAP_HEIGHT)
		gdk_draw_rectangle(MapArea->window, UserGC, TRUE, x - 2, y - 2, 4, 4);

	x -= gdk_string_width(MapFont, User_Data.Callsign) / 2;
	y += gdk_string_height(MapFont, User_Data.Callsign) + 3;

	gdk_draw_string(MapArea->window, MapFont, UserGC, x, y, User_Data.Callsign);
}

void Plot_Cities(void)
{
	GSList *Loc;
	struct Loc_Struct *Data;
	int x, y;

	Loc = Loc_List;

	while (Loc != NULL) {
		Data = (struct Loc_Struct *)Loc->data;

		x = Long_To_X(Data->Longitude, Calc_X_Scale());
		y = Lat_To_Y(Data->Latitude, Calc_Y_Scale());

		if (x > 0 && y > 0 && x < MAP_WIDTH && y < MAP_HEIGHT)
			gdk_draw_point(MapArea->window, CitiesGC, x, y);

		Loc = g_slist_next(Loc);
	}
}

void Plot_Satellite(double Latitude, double Longitude, double Angle)
{
	Sat_Long  = Longitude;
	Sat_Lat   = Latitude;
	Sat_Angle = Angle;

	Redraw_Map(0, 0, MAP_WIDTH, MAP_HEIGHT);
}

void Redraw_Satellite(void)
{
	int No_Points;
	double Theta;
	double Increment;
	double Lat;
	double Long;
	double cla, sla;
	double clo, slo;
	double sra, cra;
	double X, Y, Z;
	double x, y, z;
	double x_scale = Calc_X_Scale();
	double y_scale = Calc_Y_Scale();
	int last_x1, last_x2, last_y1, last_y2;
	int plot_x, plot_y;

	if (Sat_Angle == -1.0) return;

	plot_x = Long_To_X(Sat_Long, x_scale);
	plot_y = Lat_To_Y(Sat_Lat, y_scale);

	if (plot_x > 0 && plot_y > 0 && plot_x < MAP_WIDTH && plot_y < MAP_HEIGHT) {
		gdk_draw_rectangle(MapArea->window, SatelliteGC, TRUE, plot_x - 3, plot_y - 3, 6, 6);

		plot_x -= gdk_string_width(MapFont, Sat_Chosen->Name) / 2;
		plot_y += gdk_string_height(MapFont, Sat_Chosen->Name) + 5;

		gdk_draw_string(MapArea->window, MapFont, SatelliteGC, plot_x, plot_y, Sat_Chosen->Name);
	}

	No_Points = (int)(Sat_Angle + 0.5) + 3;
	Increment = PI / (double)(No_Points - 1);

	cla = cos(RAD(Sat_Lat));
	sla = sin(RAD(Sat_Lat));
	clo = cos(RAD(Sat_Long));
	slo = sin(RAD(Sat_Long));
	cra = cos(RAD(Sat_Angle));
	sra = sin(RAD(Sat_Angle));

	Long = Sat_Long;
	Lat  = Sat_Lat + Sat_Angle;

	if (Lat > 90.0) {
		Long += 180.0;
		Lat  =  180.0 - Lat;

		if (Long >= 360.0) Long -= 360.0;
	}

	last_x1 = last_x2 = Long_To_X(Long, x_scale);
	last_y1 = last_y2 = Lat_To_Y(Lat, y_scale);

	for (Theta = Increment; Theta < PI; Theta += Increment) {
		X = cra;
		Y = sra * sin(Theta);
		Z = sra * cos(Theta);

		x = X * cla - Z * sla;
		y = Y;
		z = X * sla + Z * cla;

		X = x * clo - y * slo;
		Y = x * slo + y * clo;

		Lat  = DEG(asin(z));

		Long = DEG(atan2(Y, X));

		plot_x = Long_To_X(Long, x_scale);
		plot_y = Lat_To_Y(Lat, y_scale);

		Draw_Line(last_x1, last_y1, plot_x, plot_y);

		last_x1 = plot_x;
		last_y1 = plot_y;

		X = x * clo + y * slo;
		Y = x * slo - y * clo;

		Long = DEG(atan2(Y, X));

		plot_x = Long_To_X(Long, x_scale);
		plot_y = Lat_To_Y(Lat, y_scale);

		Draw_Line(last_x2, last_y2, plot_x, plot_y);

		last_x2 = plot_x;
		last_y2 = plot_y;
	}

	Long = Sat_Long;
	Lat  = Sat_Lat - Sat_Angle;

	if (Lat < -90.0) {
		Long += 180.0;
		Lat  = -180.0 - Lat;

		if (Long >= 360.0) Long -= 360.0;
	}

	plot_x = Long_To_X(Long, x_scale);
	plot_y = Lat_To_Y(Lat, y_scale);

	Draw_Line(last_x1, last_y1, plot_x, plot_y);
	Draw_Line(last_x2, last_y2, plot_x, plot_y);
}

static void Draw_Line(int x1, int y1, int x2, int y2)
{
	/* Too long */
	if (abs(x1 - x2) > 100)
		return;

	/* Both ends are outside the map */
	if ((x1 < 0 || x1 >= MAP_WIDTH || y1 < 0 || y1 >= MAP_HEIGHT) &&
	    (x2 < 0 || x2 >= MAP_WIDTH || y2 < 0 || y2 >= MAP_HEIGHT))
		return;

	gdk_draw_line(MapArea->window, FootprintGC, x1, y1, x2, y2);
}

static double Calc_X_Scale(void)
{
	double x_scale;

	if (MapInfo[User_Data.Map_No].Max_Longitude > 0.0)
		x_scale = (double)MAP_WIDTH / (MapInfo[User_Data.Map_No].Max_Longitude - MapInfo[User_Data.Map_No].Min_Longitude);
	else
		x_scale = (double)MAP_WIDTH / fabs(MapInfo[User_Data.Map_No].Min_Longitude - MapInfo[User_Data.Map_No].Max_Longitude);

	return x_scale;
}

static double Calc_Y_Scale(void)
{
	double y_scale;

	if (MapInfo[User_Data.Map_No].Max_Latitude > 0.0)
		y_scale = (double)MAP_HEIGHT / (MapInfo[User_Data.Map_No].Max_Latitude - MapInfo[User_Data.Map_No].Min_Latitude);
	else
		y_scale = (double)MAP_HEIGHT / fabs(MapInfo[User_Data.Map_No].Min_Latitude - MapInfo[User_Data.Map_No].Max_Latitude);

	return y_scale;
}

static int Long_To_X(double Longitude, double X_Scale)
{
	int x;

	if (Longitude > 180.0)  Longitude -= 360.0;
	if (Longitude < -180.0) Longitude += 360.0;

	x = (int)(X_Scale * (-Longitude - MapInfo[User_Data.Map_No].Min_Longitude) + 0.5);

	return x;
}

static int Lat_To_Y(double Latitude, double Y_Scale)
{
	int y;

	y = MAP_HEIGHT - (int)(Y_Scale * (Latitude - MapInfo[User_Data.Map_No].Min_Latitude) + 0.5);

	return y;
}
