/*  GNOME canvas based interface to a map using a simple cylindrical proj */
/*                                                                        */
/* Copyright (C) 1999 Red Hat, Incorportated                              */
/* Original work by Michael Fulbright <drmike@redhat.com> */

#include <gnome.h>
#include <math.h>
#include "gnome-map.h"

/* look in <gdk/gdkcursors.h> for valid values */
#define MAP_CURSOR GDK_LEFT_PTR

/**
 * gnome_map_set_image:
 * @map: Map to apply image to.
 * @imagefile: Filename of image for map.
 *
 * This function sets the image used for the map to @imagefile.  It is 
 * assumed that this image is a simple cylindrical projection. 
 *
 * Return value: 0 on success, -1 if image could not be loaded
 **/
static gint
gnome_map_set_image ( GnomeMap *map, gchar *imagefile )
{

    g_return_val_if_fail ( map != NULL, -1 );
    g_return_val_if_fail ( map->image == NULL, -1 );

    /* load map image */
    if (map->aa)
	map->image = gnome_canvas_load_alpha (imagefile);
    else
	map->image = gdk_imlib_load_image (imagefile);

    if (!map->image)
	return -1;

    return 0;
}


static void
canvas_realize_event (GtkWidget *canvas, gpointer *data)
{

    GdkCursor   *ptrcursor;

    ptrcursor = gdk_cursor_new (MAP_CURSOR);
    if (!ptrcursor) {
	g_warning ("Unable to load new cursor %d for map\n", MAP_CURSOR);
	return;
    }

    gdk_window_set_cursor (canvas->window, ptrcursor);

    gdk_cursor_destroy (ptrcursor);
}

/**
 * gnome_map_new:
 * @imagefile: File to be used map image.
 * @width: Width of map view in pixels. 
 * @height: Height of map view in pixels.
 * @antialias: Boolean used to set map to use antialias canvas or not.
 *
 * Creates a new map, using anti-aliased or normal canvas based on the
 * value of @antialias.  Of anti-aliased maps the image file must be
 * in PNG format (this is a gnome-canvas limitation as of gnome-libs 1.0.10).
 * If @width and @height are both <= 0, then the map image size is used.
 * If only one of  @width or @height is > 0, then the unspecified
 * dimension is scaled (perserving the aspect of the original image).
 *
 * Return value: The newly-created map structure or NULL if image couldn't
 *               be loaded.
 **/
GnomeMap *
gnome_map_new ( gchar *imagefile, gint width, gint height, gboolean aa )
{
    GnomeCanvasGroup *canvas_root;
    GnomeMap    *map;
    gint        h, w;

    map = g_new0 ( GnomeMap, 1 );
    map->aa = aa;

    if ( gnome_map_set_image ( map, imagefile ) < 0 ) {
	g_free (map);
	return NULL;
    }

    /* create a canvas */
    if (aa) {
	gtk_widget_push_visual (gdk_rgb_get_visual ());
	gtk_widget_push_colormap (gdk_rgb_get_cmap ());
	map->canvas = gnome_canvas_new_aa ();
    } else {
	gtk_widget_push_visual (gdk_imlib_get_visual ());
	gtk_widget_push_colormap (gdk_imlib_get_colormap ());
	map->canvas = gnome_canvas_new ();
    }

    /* set map size and scaling */
    if ( width <= 0 && height <= 0 ) {
	w = map->image->rgb_width;
	h = map->image->rgb_height;
    } else if ( width > 0 && height <= 0 ) {
	w = width;
	h = (int)(((float)w/(float)map->image->rgb_width)*map->image->rgb_height);
    } else if ( width <= 0 && height > 0 ) {
	h = height;
	w = (int)(((float)h/(float)map->image->rgb_height)*map->image->rgb_width);
    } else {
	w = width;
	h = height;
    }

    map->width = w;
    map->height = h;
    map->long1 = -180.0;
    map->lat1  = -90.0;
    map->long2 = 180.0;
    map->lat2  =  90.0;

    gtk_widget_set_usize (map->canvas, w, h);
    gnome_canvas_set_pixels_per_unit (GNOME_CANVAS (map->canvas), 1.0);
    gnome_canvas_set_scroll_region (GNOME_CANVAS (map->canvas), 
				    0.0, 0.0,
				    (double)w, (double)h);

    gtk_widget_show(map->canvas);

    /* Setup canvas items */
    canvas_root = gnome_canvas_root (GNOME_CANVAS (map->canvas));
    map->image_item = gnome_canvas_item_new (canvas_root,
					gnome_canvas_image_get_type (),
					"image", map->image,
					"x", 0.0,
					"y", 0.0,
					"width", (double) w,
					"height", (double) h,
					"anchor", GTK_ANCHOR_NW,
					NULL);

    /* grap realize signal so we can set cursor for map */
    gtk_signal_connect (GTK_OBJECT (map->canvas), "realize",
			 (GtkSignalFunc) canvas_realize_event,
			 NULL);

    /* restore to original */
    gtk_widget_pop_colormap ();
    gtk_widget_pop_visual ();

    return map;
}


/**
 * gnome_map_get_image_size: Get the unscaled image size of map image.
 * @map: The map which image size is desired for.
 * @width: Width of map in pixels.
 * @height: Height of map in pixels.
 *
 * Given the GnomeMap @map, this function returns the dimensions of the
 * original unscaled map image.
 *
 * Return value: None.
 **/
void
gnome_map_get_image_size ( GnomeMap *map, gint *width, gint *height )
{
    g_return_if_fail ( map != NULL || map->image != NULL);
    g_return_if_fail ( width != NULL || height != NULL );

    *width  = map->image->rgb_width;
    *height = map->image->rgb_width;
}

/**
 * gnome_map_set_size: Set screen dimensions (in pixels) of map view.
 * @map: Map to apply dimensions to.
 * @width: Desired width of map view.
 * @height: Desired height of map view.
 *
 * Given a map which has an image associated with it via the
 * gnome_map_set_image() call, set the onscreen size of the map view widget.
 *
 * Return value: None.
 **/
void
gnome_map_set_size ( GnomeMap *map, gint width, gint height )
{
    g_return_if_fail ( map != NULL );
    g_return_if_fail ( map->canvas != NULL );
    g_return_if_fail ( map->image != NULL );
    g_return_if_fail ( width > 0 );
    g_return_if_fail ( height > 0 );
}
    

    
/**
 * gnome_map_xlat_map2screen: Convert from map coordinates to screen coordinates.
 * @map: Map to apply dimensions to.
 * @longitude: Longitude coordinate to convert (in degrees). 
 * @latitude: Latitude coordinate to convert (in degrees).
 * @sx: Converted screen coordinate.
 * @sy: Converted screen coordinate.
 *
 * A (longitude, latitude) pair is converted to a (x, y) canvas coordinate.
 * (An obvious improvement to the gnome-map would be to just let the
 * canvas do the mapping internally.  If this change is made this function
 * will still work but will be a small stub).
 *
 * Return value: None.
 **/
void
gnome_map_xlat_map2screen ( GnomeMap *map, 
			    double longitude, double latitude,
			    double *sx, double *sy )
{
    g_return_if_fail ( map != NULL );

    *sx = (map->width/2.0 + (map->width/2.0)*longitude/180.0);
    *sy = (map->height/2.0 - (map->height/2.0)*latitude/90.0);
}

    
/**
 * gnome_map_xlat_screen2map: Convert from screen coordinates to map coordinates.
 * @map: Map to apply dimensions to.
 * @sx: Screen coordinate to convert.
 * @sy: Screen coordinate to convert.
 * @longitude: Converted longitude coordinate (in degrees). 
 * @latitude: Converted latitude coordinate (in degrees).
 *
 * A (x, y) canvas coordinate is converted to a (longitude, latitude) pair.
 * (An obvious improvement to the gnome-map would be to just let the
 * canvas do the mapping internally.  If this change is made this function
 * will still work but will be a small stub).
 *
 * Return value: None.
 **/
void
gnome_map_xlat_screen2map ( GnomeMap *map, 
			    double sx, double sy,
			    double *longitude, double *latitude)
{
    g_return_if_fail ( map != NULL );

    *longitude = ( sx - (double)map->width/2.0)/((double)map->width/2.0)*180.0;
    *latitude  = ((double)map->height/2.0-sy)/((double)map->height/2.0)*90.0;
}

/**
 * gnome_map_set_view: Set view of map in map coordinates.
 * @map: Map to apply dimensions to.
 * @longitude1: Longitude of corner 1.
 * @latitude1: Latitude of corner 1.
 * @longitude2: Longitude of corner 2.
 * @latitude2: Latitude of corner 2.
 *
 * Sets view of map to a box defined by the two corners given in map
 * coordinates.
 *
 * Return value: None.
 **/
void
gnome_map_set_view ( GnomeMap *map, 
			    double longitude1, double latitude1,
			    double longitude2, double latitude2)
{
    double x1, y1, x2, y2;
    double scale;

    g_return_if_fail ( map != NULL );
    g_return_if_fail ( longitude1 >= -180.0 && longitude1 <= 180.0 );
    g_return_if_fail ( longitude2 >= -180.0 && longitude2 <= 180.0 );
    g_return_if_fail ( latitude1 >= -90.0 && latitude1 <= 90.0 );
    g_return_if_fail ( latitude2 >= -90.0 && latitude2 <= 90.0 );


    gnome_map_xlat_map2screen (map, longitude1, latitude1, &x1, &y1);
    gnome_map_xlat_map2screen (map, longitude2, latitude2, &x2, &y2);

    gnome_canvas_set_scroll_region (GNOME_CANVAS(map->canvas),
				    x1, y1, x2, y2);

    if (longitude1 < longitude2) {
	map->long1 = longitude1;
	map->long2 = longitude2;
    } else {
	map->long1 = longitude2;
	map->long2 = longitude1;
    }

    if (latitude1 < latitude2) {
	map->lat1 = latitude1;
	map->lat2 = latitude2;
    } else {
	map->lat1 = latitude2;
	map->lat2 = latitude1;
    }	

    scale = ((double)map->width)/fabs(x1-x2);
    gnome_canvas_set_pixels_per_unit (GNOME_CANVAS(map->canvas), scale);

}
			    
/**
 * gnome_map_is_loc_in_view: Test to see if location is on current view 
 * @map: Map to apply dimensions to.
 * @longitude: Longitude of location to test.
 * @latitude: Latitude of location to test.
 *
 * Tests whether (longitude, latitude) is within current map view.
 *
 * Return value: TRUE is visible, FALSE if not.
 **/
gboolean
gnome_map_is_loc_in_view (GnomeMap *map, double longitude, double latitude)
{

    return !(longitude < map->long1 || longitude > map->long2 ||
	     latitude < map->lat1  || latitude > map->lat2);
}