/* gkrellmoon.c 
 * Copyright (C) 2001,2002, Dale P. Smith, Altus Technologies Corporation
 */

#include <gkrellm2/gkrellm.h>
#include <X11/X.h>

#include "moon_60.xpm"


/* #define SPINNING 1 */

#define IMAGE_WIDTH 48
#define IMAGE_HEIGHT 48
#define IMAGE_COUNT 60


#define STYLE_NAME "moon"
#define PLUGIN_CONFIG_KEYWORD    "moon"
#define MOONCLOCK_MAJOR_VERSION	0
#define MOONCLOCK_MINOR_VERSION 5

typedef struct {
    int longitude;
    int latitude;
    int age;
    int fraction;
    int illumination;
    int visible;
    int riseset;
#ifdef SPINNING
    int testing;
#endif
} Options;

static GkrellmMonitor *monitor;
static GkrellmPanel   *panel;
static Options options;
static gint    style_id;

static GtkWidget *longitude_spin_button;
static GtkWidget *latitude_spin_button;
static GtkWidget *age_button;
static GtkWidget *fraction_button;
static GtkWidget *illumination_button;
static GtkWidget *visible_button;
static GtkWidget *riseset_button;
#ifdef SPINNING
static GtkWidget *testing_button;
#endif

static GdkPixmap *moon_image = NULL;
static GdkBitmap *moon_mask = NULL;
static GkrellmDecal     *moon = NULL;

static GtkTooltips	*tooltip;
#include "CalcEphem.h"
#include "MoonRise.h"

typedef struct CTrans MoonData;

static MoonData moondata;

static void update_tooltip(MoonData *moon);

static void
update_moon_data(MoonData * moon)
{
    struct tm *time_struc;	/* The tm struct is defined in <time.h> */
    time_t current_gmt;
    gdouble local_std_time, univ_time, eot;
    glong date;
    gint day_of_month, month, year;

    current_gmt = time(CurrentTime);	/* CurrentTime defined in <X11/X.h> */

    time_struc = gmtime(&current_gmt);
    univ_time =
	time_struc->tm_hour + time_struc->tm_min / 60.0 +
	time_struc->tm_sec / 3600.0;

    /* The date needs to be the date in UTC, i.e. in greenwich, so
     * be sure not to call the localtime function until after date
     * has been set (there's only one tm structure).  */

    year = time_struc->tm_year + 1900;
    month = time_struc->tm_mon + 1;
    day_of_month = time_struc->tm_mday;

    date = year * 10000 + month * 100 + day_of_month;

    time_struc = localtime(&current_gmt);
    local_std_time =
	time_struc->tm_hour + time_struc->tm_min / 60.0 +
	time_struc->tm_sec / 3600.0;

    moon->Glat = options.latitude;
    moon->Glon = options.longitude;

    CalcEphem(date, univ_time, moon);

    moon->LST = local_std_time;
    moon->LMT = univ_time - moon->Glon / 15.0;
    if (moon->LMT < 0.0)
	moon->LMT += 24.0;
    if (moon->LMT > 24.0)
	moon->LMT -= 24.0;

    /* eot is the equation of time. gmst is Greenwich Sidereal
     * Time.  This equation below is correct, but confusing at
     * first.  It's easy to see when you draw the following
     * picture: A sphere with 0 and 180 degree longitude, North on
     * top, a meridian for the real sun, a meridian for a fictive
     * average sun, a meridian denoting the vernal equinox.  Note
     * that universal time is the hour angle between 180 degrees
     * and the fictive sun's meridian measured clockwise.  gmst is
     * the hour angle between 0 degrees and the meridian of the
     * vernal equinox measured clockwise.  RA_sun/15.0 is the hour
     * angle of the real sun measured counterclockwise from the
     * vernal equinox. eot is the difference between the real and
     * the fictive sun.  Looking at the picture, it's easy to see
     * that 12=RA_sun/15-gmst+eot+utc (12 hours = 180 deg.) */

    eot =
	12.0 - univ_time + moon->gmst - moon->RA_sun / 15.0;

    if (eot < 0.0)
	eot += 24.0;
    if (eot > 24.0)
	eot -= 24.0;

    moon->LAT = moon->LMT + eot;
    if (moon->LAT < 0.0)
	moon->LAT += 24.0;
    if (moon->LAT > 24.0)
	moon->LAT -= 24.0;

    update_tooltip(moon);
}


static int
moon_image_number(MoonData * moon)
{
    gdouble image_float;
    gint image_int;
    gint image_number;

    /* MoonPhase expresses phase of moon as fraction of 1; 0.5=full. */
    image_float = moon->MoonPhase * (gdouble) IMAGE_COUNT;
    image_int = (gint) image_float;

    if ((image_float - image_int) >= 0.5)
	image_number = (image_int + 1) % IMAGE_COUNT;
    else
	image_number = image_int % IMAGE_COUNT;

    return image_number;
}


static void
moon_update_plugin()
{
    static int image_number;

    /* Draw plugin specific data on the chart */
    /* Use xlib or gdk functions, or gkrellm_draw_chart() if applicable */

#ifdef SPINNING
    if (GK.minute_tick) {
	update_moon_data(&moondata);
    }

    if (options.testing) {
	++image_number;
	image_number %= IMAGE_COUNT;
    } else {
	image_number = moon_image_number(&moondata);
    }
#else
    if (GK.minute_tick) {
	update_moon_data(&moondata);
    }
    image_number = moon_image_number(&moondata);
#endif

    gkrellm_draw_decal_pixmap(panel, moon, image_number);
    gkrellm_draw_panel_layers(panel);
}

#define HOUR(time) ((gint) (time))
#define MINUTE(time) ((gint) ((ABS(time - HOUR(time))) * 60))

#define ANGLE_HOUR(angle) ((gint) (angle / 15.0))
#define ANGLE_MINUTE(angle) ((gint) ((ABS(angle - ANGLE_HOUR(angle) * 15.0)) * 4.0))

static void
calc_riseset_time(CTrans * c, const char * day, GString *mboxes)
{
    gchar	buf[128];
    gdouble rise, set;

    MoonRise(c, &rise, &set);

    snprintf(buf, sizeof(buf), "\n%s: ", day);
    g_string_append(mboxes, buf);

    if (abs(rise) > 24.0) {
	snprintf(buf, sizeof(buf), "no rise ");
	g_string_append(mboxes, buf);
    } else {
	snprintf(buf, sizeof(buf), "%02d:%02d ", HOUR(rise), MINUTE(rise));
	g_string_append(mboxes, buf);
    }

    if (abs(set) > 24.0) {
	snprintf(buf, sizeof(buf), "no set");
	g_string_append(mboxes, buf);
    } else {
	snprintf(buf, sizeof(buf), "%02d:%02d", HOUR(set), MINUTE(set));
	g_string_append(mboxes, buf);
    }
}

static void
update_tooltip(MoonData *moon)
{
    GString	*mboxes = NULL;
    gchar	buf[128];

    if (tooltip == NULL)
	return;

    mboxes = g_string_sized_new(512);

    g_string_append(mboxes, "MoonClock");

#if 0
    format_time("A: ", moon->A_moon);
    format_time("h: ", moon->h_moon);
    printf("EarthMoon Distance: %5.1f Re", moon->EarthMoonDistance);
#endif

#if 0
    format_angle_hours("RA:", buf, sizeof(buf), moon->RA_moon);
    format_time("Declination: ", moon->DEC_moon);
    printf("EarthMoon Distance: %5.1f Re", moon->EarthMoonDistance);
    printf("\n");
#endif

    if (options.age) {
	snprintf(buf, sizeof(buf), "\nAge: %2.2f Days", moon->MoonAge);
	g_string_append(mboxes, buf);
    }

    if (options.fraction) {
	snprintf(buf, sizeof(buf), "\nFrac: %5.1f%%", 100 * moon->MoonPhase);
	g_string_append(mboxes, buf);
    }
    
    if (options.illumination) {
	snprintf(buf, sizeof(buf), "\nIllum: %5.1f%%",
		 50.0 * (1.0 - cos(moon->MoonPhase * 6.2831853)));
	g_string_append(mboxes, buf);
    }

    if (options.visible) {

	snprintf(buf, sizeof(buf), "\nVisible: %s",
		 moon->Visible ? "Yes" : "No");
	g_string_append(mboxes, buf);
    }

    if (options.riseset) {
	snprintf(buf, sizeof(buf), "\n- Rise and Set times -");
	g_string_append(mboxes, buf);
	moon->day -= 1;
	calc_riseset_time(moon, "Yesterday", mboxes);
	moon->day += 1;
	calc_riseset_time(moon, "Today", mboxes);
	moon->day += 1;
	calc_riseset_time(moon, "Tomorrrow", mboxes);
	moon->day -= 1;
    }

    gtk_tooltips_set_tip(tooltip, panel->drawing_area, mboxes->str, NULL);
    gtk_tooltips_set_delay(tooltip, 750);
    gtk_tooltips_enable(tooltip);
    if (mboxes)
	g_string_free(mboxes, TRUE);
}


static gint
panel_expose_event(GtkWidget *widget, GdkEventExpose *ev)
{
    gdk_draw_pixmap(widget->window,
		    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
		    panel->pixmap, ev->area.x, ev->area.y, ev->area.x, ev->area.y,
		    ev->area.width, ev->area.height);
    return FALSE;
}

static void
panel_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
    if (event->button == 3)
	gkrellm_open_config_window(monitor);
}

static void
load_images()
{
    GkrellmPiximage *image = NULL;

    gkrellm_load_piximage(NULL, moon_60_xpm, &image, NULL);
    gkrellm_scale_piximage_to_pixmap(image, &moon_image, &moon_mask, 0, 0);
}

static void
moon_create_plugin(GtkWidget *vbox, gint first_create)
{
    GkrellmStyle	   *style = NULL;

    load_images();

    if (first_create)
        panel = gkrellm_panel_new0();
    else {
	gkrellm_destroy_decal_list(panel);
    }

    style = gkrellm_meter_style(style_id);

    moon = gkrellm_create_decal_pixmap(panel, moon_image, moon_mask,
				       IMAGE_COUNT, style, 0, 0);
    moon->x = (gkrellm_chart_width() - IMAGE_WIDTH) / 2;

    panel->textstyle = gkrellm_meter_textstyle(style_id);
    gkrellm_panel_configure(panel, NULL, style);
    gkrellm_panel_create(vbox, monitor, panel);

    if (first_create) {
        gtk_signal_connect(GTK_OBJECT(panel->drawing_area),
			   "expose_event", (GtkSignalFunc) panel_expose_event,
			   NULL);
        gtk_signal_connect(GTK_OBJECT(panel->drawing_area),
			   "button_release_event", (GtkSignalFunc) panel_button_event,
			   NULL);
	tooltip = gtk_tooltips_new();
    }
    update_moon_data(&moondata);
    gkrellm_draw_decal_pixmap(panel, moon, moon_image_number(&moondata));
}


static void
moon_create_tab(GtkWidget *tab_vbox)
{
    GtkWidget		*tabs;
    GtkWidget		*vbox;

    tabs = gtk_notebook_new();
    gtk_notebook_set_tab_pos(GTK_NOTEBOOK(tabs), GTK_POS_TOP);
    gtk_box_pack_start(GTK_BOX(tab_vbox), tabs, TRUE, TRUE, 0);
    
/* --Setup Tab */
    vbox = gkrellm_gtk_notebook_page(tabs, _("Setup"));

    gkrellm_gtk_spin_button(vbox, &longitude_spin_button,
			(gfloat) options.longitude,
			-180.0, 180.0, 1.0, 1.0, 0, 60, NULL, NULL,
			FALSE, _("Longitude (decimal degrees + = W, - = E)"));

    gkrellm_gtk_spin_button(vbox, &latitude_spin_button,
			    (gfloat) options.latitude,
			    -90.0, 90.0, 1.0, 1.0, 0, 60, NULL, NULL,
			    FALSE, _("Latitude (decimal degrees + = N, - = S)"));
    
    gkrellm_gtk_check_button(vbox, &age_button,
			     options.age,
			     TRUE, 0, _("Age"));
    
    gkrellm_gtk_check_button(vbox, &fraction_button,
			     options.fraction,
			     TRUE, 0, _("Fraction"));
    
    gkrellm_gtk_check_button(vbox, &illumination_button,
			     options.illumination,
			     TRUE, 0, _("Illumination"));

    gkrellm_gtk_check_button(vbox, &visible_button,
			     options.visible,
			     TRUE, 0, _("Visible"));
    gkrellm_gtk_check_button(vbox, &riseset_button,
			     options.riseset,
			     TRUE, 0, _("Rise and Set"));
#ifdef SPINNING
    gkrellm_gtk_check_button(vbox, &testing_button,
			     options.testing,
			     TRUE, 0, _("Spin images fast"));
#endif

/* ----------------- info text --------------------*/
    {
	GtkWidget *text;
	gchar *info_text[] = {
	    "<b>Gkrellm Moonclock Plugin\n\n",
	    "\"Ya gotta have a moon clock!\"\n\n",
	    "<b>Longitude and Latitude:\n",
	    "\tPostitive is North and West, Negative is South and East\n\n",
	    "The following options contol what is displayed in the tooltip:\n\n",
	    "<b>Age:\n",
	    "\tthe number of days since the last new moon\n",
	    "<b>Fraction:\n",
	    "\tthe fraction of the way through the lunar cycle (50 is full moon)\n",
	    "<b>Illumination:\n",
	    "\tthe fraction of the disk that's illuminated\n",
	    "<b>Visible:\n",
	    "\tIs the moon aboove the horizon\n",
	    "<b>Rise and Set:\n",
	    "\ttimes for Yesterday, Today, and Tomorrow\n"
	};

	vbox = gkrellm_gtk_notebook_page(tabs, _("Info"));
	text = gkrellm_gtk_scrolled_text_view(vbox, NULL,
					      GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	gkrellm_gtk_text_view_append_strings(text, info_text,
					     sizeof(info_text)/sizeof(gchar *));
    }

/* ----------------- about text --------------------*/

    {
	gchar *plugin_about_text;
	GtkWidget *label, *text;
	
	plugin_about_text = g_strdup_printf(
	    "MoonClock %d.%d\n"
	    "GKrellM MoonClock Plugin\n\n"
	    "Copyright (C) 2001,2002 Dale P. Smith\n"
	    "dsmith@altustech.com\n\n"
	    "Released under the GNU Public Licence",
	    MOONCLOCK_MAJOR_VERSION, MOONCLOCK_MINOR_VERSION);
	
	text = gtk_label_new(plugin_about_text); 
	label = gtk_label_new("About");
	gtk_notebook_append_page(GTK_NOTEBOOK(tabs), text, label);
	g_free(plugin_about_text);
    }
}

static void
moon_apply_config()
{
    options.longitude = gtk_spin_button_get_value_as_int(
	GTK_SPIN_BUTTON(longitude_spin_button));

    options.latitude = gtk_spin_button_get_value_as_int(
	GTK_SPIN_BUTTON(latitude_spin_button));

    options.age =          GTK_TOGGLE_BUTTON(age_button)->active;
    options.fraction =     GTK_TOGGLE_BUTTON(fraction_button)->active;
    options.illumination = GTK_TOGGLE_BUTTON(illumination_button)->active;
    options.visible =      GTK_TOGGLE_BUTTON(visible_button)->active;
    options.riseset =      GTK_TOGGLE_BUTTON(riseset_button)->active;
#ifdef SPINNING
    options.testing =      GTK_TOGGLE_BUTTON(testing_button)->active;
#endif

    update_tooltip(&moondata);
}

static void
moon_save_config (FILE *f)
{
    fprintf(f, "%s longitude %d\n", PLUGIN_CONFIG_KEYWORD, options.longitude);
    fprintf(f, "%s latitude %d\n", PLUGIN_CONFIG_KEYWORD, options.latitude);

    fprintf(f, "%s age %d\n", PLUGIN_CONFIG_KEYWORD, options.age);
    fprintf(f, "%s fraction %d\n", PLUGIN_CONFIG_KEYWORD, options.fraction);
    fprintf(f, "%s illumination %d\n", PLUGIN_CONFIG_KEYWORD, options.illumination);
    fprintf(f, "%s visible %d\n", PLUGIN_CONFIG_KEYWORD, options.visible);
    fprintf(f, "%s risefall %d\n", PLUGIN_CONFIG_KEYWORD, options.riseset);
#ifdef SPINNING
    fprintf(f, "%s testing %d\n", PLUGIN_CONFIG_KEYWORD, options.testing);
#endif
}

static void
moon_load_config (gchar *arg)
{
    gchar config[64], item[256];
    gint n;

    n = sscanf(arg, "%s %[^\n]", config, item);
    if (n != 2)
	return;

    if (strcmp(config, "longitude") == 0)
	sscanf(item, "%d\n", &(options.longitude));
    if (strcmp(config, "latitude") == 0)
	sscanf(item, "%d\n", &(options.latitude));

    if (strcmp(config, "age") == 0)
	sscanf(item, "%d\n", &(options.age));
    if (strcmp(config, "fraction") == 0)
	sscanf(item, "%d\n", &(options.fraction));
    if (strcmp(config, "illumination") == 0)
	sscanf(item, "%d\n", &(options.illumination));
    if (strcmp(config, "visible") == 0)
	sscanf(item, "%d\n", &(options.visible));
    if (strcmp(config, "risefall") == 0)
	sscanf(item, "%d\n", &(options.riseset));
#ifdef SPINNING
    if (strcmp(config, "testing") == 0)
	sscanf(item, "%d\n", &(options.testing));
#endif
}

static GkrellmMonitor  plugin_mon  = {
    "Moon Clock",		/* Name, for config tab.        */
    0,				/* Id,  0 if a plugin           */
    moon_create_plugin,		/* The create_plugin() function */
    moon_update_plugin,		/* The update_plugin() function */
    moon_create_tab,		/* The create_plugin_tab() config function */
    moon_apply_config,		/* The apply_plugin_config() function      */

    moon_save_config,		/* The save_plugin_config() function  */
    moon_load_config,		/* The load_plugin_config() function  */
    PLUGIN_CONFIG_KEYWORD,	/* config keyword                     */

    NULL,			/* Undefined 2  */
    NULL,			/* Undefined 1  */
    NULL,			/* private		*/

    MON_INSERT_AFTER|MON_CLOCK,	/* Insert plugin before this monitor.       */
    NULL,			/* Handle if a plugin, filled in by GKrellM */
    NULL			/* path if a plugin, filled in by GKrellM   */
};

GkrellmMonitor *
gkrellm_init_plugin(void)
{
    options.longitude = 81;	/* Where I live! */
    options.latitude = 44;

    style_id = gkrellm_add_meter_style(&plugin_mon, STYLE_NAME);

    return (monitor = &plugin_mon);
}
