/*
 * 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 3 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 Free Software Foundation,
 * Inc. 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <gtk/gtk.h>
#include <cairo.h>
#include <gst/gst.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <glib/gprintf.h>

#include <locale.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <math.h>
#include <time.h>


typedef struct {
    GstElement *elt;
    gboolean repeat;
} closure_t;

typedef struct {
    gfloat red;
    gfloat green;
    gfloat blue;
} color_t;

typedef struct {
    gchar *str;
    gboolean is_set;
    gint spike_count;
    gint inner_radius;
    gint outer_radius;
    gint mx;
    gint my;
    color_t color;
} star_t;

typedef struct {
    gint xold;
    gint yold;
    star_t star;
    color_t color;
    color_t linecolor;
    GstBus *bus;
    GtkWidget *drawing_area;
    cairo_t *context;
} gamine_t;

gint xold;
gint yold;
gint linewidth;
gint objectweight;
gint fontweight;
gdouble timer_color_change;
gchar *background_music;
clock_t last_color_change;
guint lastkeyval;

static void
load_conf ()
{
	gchar *filename;
	GKeyFile *conffile;
	GError *error;
	
	error = NULL;
	conffile = g_key_file_new();
	filename = g_build_filename(SYSCONFDIR, "gamine.conf", NULL);
	
	if (g_key_file_load_from_file(conffile, filename, G_KEY_FILE_NONE, &error)) {
		/*Line width*/
		if (g_key_file_has_key(conffile, "gamine", "line_width", NULL)) {
			linewidth = g_key_file_get_integer(conffile, "gamine", "line_width", NULL);
			if (linewidth == 0) {
				linewidth = 10;
			}
		} else {
			linewidth = 10;
		}
		/*Object weight*/
		if (g_key_file_has_key(conffile, "gamine", "object_weight", NULL)) {
			objectweight = g_key_file_get_integer(conffile, "gamine", "object_weight", NULL);
			if (objectweight == 0) {
				objectweight = 15;
			}
		} else {
			objectweight = 15;
		}
		/*Font weight*/
		if (g_key_file_has_key(conffile, "gamine", "font_weight", NULL)) {
			fontweight = g_key_file_get_integer(conffile, "gamine", "font_weight", NULL);
			if (fontweight == 0) {
				fontweight = 50;
			}
		} else {
			fontweight = 50;
		}
		/*Timer color change*/
		if (g_key_file_has_key(conffile, "gamine", "timer_color_change", NULL)) {
			timer_color_change = g_key_file_get_double(conffile, "gamine", "timer_color_change", NULL);
			if (timer_color_change == 0.0) {
				timer_color_change = 100.0;
			}
		} else {
			timer_color_change = 100.0;
		}
		/*Background music*/
		if (g_key_file_has_key(conffile, "gamine", "background_music", NULL)) {
			background_music = g_key_file_get_string(conffile, "gamine", "background_music", NULL);
			if (background_music == NULL) {
				background_music = g_strdup("BachJSBrandenburgConcertNo2inFMajorBWV1047mvmt1.ogg");
			}
			g_key_file_free(conffile);
		} else {
			background_music = g_strdup("BachJSBrandenburgConcertNo2inFMajorBWV1047mvmt1.ogg");
		}
	} else {
		/*Set default settings*/
		linewidth = 10;
		objectweight = 15;
		fontweight = 50;
		timer_color_change = 100.0;
		background_music = "BachJSBrandenburgConcertNo2inFMajorBWV1047mvmt1.ogg";
		/*Show waring message*/
		if (error != NULL) {
			g_warning("%s - %s", filename, error->message);
			g_error_free(error);
		}
	}
	
	g_free(filename);
}

static void
eos_message_received (GstBus * bus, GstMessage * message, closure_t *closure)
{
    if (closure->repeat == TRUE)
    {
        gst_element_set_state (GST_ELEMENT(closure->elt), GST_STATE_NULL);
        gst_element_set_state (GST_ELEMENT(closure->elt), GST_STATE_PLAYING);
    } else {
        gst_element_set_state (GST_ELEMENT(closure->elt), GST_STATE_NULL);
        gst_object_unref (GST_OBJECT(closure->elt));
        closure->elt = NULL;
    }
}

static void
gst_play_background (GstBus *bus, gchar *filesnd, gboolean repeat)
{
    gchar *filename;
    GstElement *pipeline;
    pipeline = gst_element_factory_make("playbin", "playbin");
    if (pipeline != NULL)
    {
        closure_t *closure = g_new (closure_t, 1);
        closure->elt = pipeline;
        closure->repeat = repeat;
        bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
        gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
        g_signal_connect (bus, "message::eos", 
            (GCallback) eos_message_received, closure);
        gst_object_unref (bus);
        filename = g_build_filename(DATADIR, "sounds", filesnd, NULL);
        if (!g_file_test (filename, G_FILE_TEST_EXISTS))
            g_printf(_("*** Error: %s does not exist ***\n"), filename);
        else {
            filename = g_strdup_printf("file://%s", filename);
            g_object_set (G_OBJECT(pipeline), "uri", filename, NULL);
            gst_element_set_state (GST_ELEMENT(pipeline), GST_STATE_PLAYING);
        }
        g_free(filename);
    }
}

static void
play_random_sound (GstBus *bus)
{
    static gchar *tab[] = {"bonus.wav", "crash.wav", "eat.wav", "flip.wav",
        "level.wav", "plick.ogg", "tri.ogg", "brick.wav", "darken.wav",
        "eraser1.wav", "gobble.wav", "line_end.wav", "prompt.wav", "tuxok.wav",
        "bleep.wav", "bubble.wav", "drip.wav", "eraser2.wav", "grow.wav",
        "paint1.wav", "receive.wav", "youcannot.wav"};
    gint snd = rand() % (sizeof tab / sizeof *tab);
    gst_play_background (bus, tab[snd], FALSE);
}
static void
build_string (cairo_t *context, star_t star)
{
    cairo_move_to (context, star.mx - 15, star.my + 15);
    cairo_show_text (context, star.str);
    cairo_stroke (context);
}
static void
build_star (cairo_t *context, star_t star)
{
    gdouble x, y;
    gint i;
    
    cairo_new_path (context);
    for (i = 0; i < star.spike_count + 1; i++) {
        x = star.mx + cos ((i * 2) * M_PI / star.spike_count) * 
            star.inner_radius;
        y = star.my + sin ((i * 2) * M_PI / star.spike_count) * 
            star.inner_radius;
        if (i == 0)
            cairo_move_to (context, x, y);
        else
            cairo_line_to (context, x, y);
    
        x = star.mx + cos ((i * 2 + 1) * M_PI / star.spike_count) * 
            star.outer_radius;
        y = star.my + sin ((i * 2 + 1) * M_PI / star.spike_count) * 
            star.outer_radius;
    
        cairo_line_to (context, x, y);
    }
    cairo_fill (context);
}

static cairo_t *
get_cairo_context (gamine_t *cb, 
        gboolean new_color, 
        gboolean is_line)
{
	if (((cb->context != NULL) && (cairo_status (cb->context) != CAIRO_STATUS_SUCCESS)) || (cb->context == NULL)) {
		cb->context = gdk_cairo_create(gtk_widget_get_window(cb->drawing_area));
		cairo_select_font_face (cb->context, "Sans", CAIRO_FONT_SLANT_NORMAL,
			CAIRO_FONT_WEIGHT_BOLD);
		cairo_set_font_size (cb->context, fontweight);
	}
	
	if (is_line && (&cb->star)->is_set == TRUE) {
        gint width = objectweight + linewidth;
        if ( cb->star.mx - width > cb->xold ||
                cb->star.mx + width < cb->xold ||
                cb->star.my - width > cb->yold ||
                cb->star.my + width < cb->yold) {
            cb->star.is_set = FALSE;
        }
        cairo_set_source_rgb (cb->context, cb->star.color.red,
                cb->star.color.green, cb->star.color.blue);
        g_debug("%s\n", cb->star.str);
        if (strcmp(cb->star.str, "none") == 0) {
			build_star(cb->context, cb->star);
		} else {
			build_string(cb->context, cb->star);
		}
        cairo_set_source_rgb (cb->context, cb->color.red, cb->color.green,
                cb->color.blue);
    }

    color_t color;
    if (is_line) {
        if ( new_color ) {
            color.red = ((((rand() % 128) + 127) /255.0) + 1.0) / 2.0;
            color.green = ((((rand() % 128) + 127) /255.0) + 1.0) / 2.0;
            color.blue = ((((rand() % 128) + 127) /255.0) + 1.0) / 2.0;
            cb->linecolor = color;
        } else {
            color = cb->linecolor;
        }
    } else {
        color.red = random() % 10 * 0.1;
        color.green = random() % 10 * 0.1;
        color.blue = random() % 10 * 0.1;
        cb->color = color;
    }
    cairo_set_source_rgb (cb->context, color.red, color.green,
            color.blue);
    return cb->context;
}

static void
draw_line (GtkWidget      *widget,
    GdkEventButton *event,
    gamine_t *cb)
{
    cairo_t *context;
    gint mx;
    gint my;
    clock_t current_time = clock();
    gdouble elapsed_ms = ((gdouble) (current_time - last_color_change)) / 100.0;
    mx = event->x+1;
    my = event->y;
    //do not change color at every move
    if(elapsed_ms > timer_color_change)
    {
        last_color_change = current_time;
        context = get_cairo_context(cb, TRUE, TRUE);
    } else {
        context = get_cairo_context(cb, FALSE, TRUE);
    }
    if ((cb->xold != -1) && (cb->yold != -1)) {
		cairo_new_path (context);
		cairo_set_line_width (context, linewidth);
		//Use rounded lines to avoid cuts in the path
		cairo_set_line_cap(context, CAIRO_LINE_CAP_ROUND);
		//move the cursor
		cairo_move_to (context, cb->xold, cb->yold);
		//line for old cursor to new one's
		cb->xold = mx;
		cb->yold = my;
		cairo_line_to (context, cb->xold, cb->yold);
		//draw
		cairo_stroke (context);
	} else {
		cb->xold = mx;
		cb->yold = my;
	}
}

static void
draw_string (gamine_t *cb,
        gchar *str)
{
    GdkDisplay *display;
    GdkDeviceManager *devmanager;
    GdkDevice *device;
    star_t star;
    gint mx;
    gint my;
    cairo_t *context;
    
    display = gdk_window_get_display(gtk_widget_get_window(cb->drawing_area));
    devmanager = gdk_display_get_device_manager(display);
    if (devmanager != NULL) {
		device = gdk_device_manager_get_client_pointer(devmanager);
		gdk_window_get_device_position(gtk_widget_get_window(cb->drawing_area), device, &mx, &my, NULL);
		star.mx = mx;
		star.my = my;
		star.str = str;
		star.is_set = TRUE;
		star.inner_radius = objectweight;
		star.outer_radius = 20;
		cb->star = star;
		cb->star.color = cb->color;
		play_random_sound(cb->bus);
		context = get_cairo_context(cb, TRUE, FALSE);
		build_string(context, star);
	}
}

static void
draw_star (GtkWidget    *widget,
       GdkEventButton *event,
       gamine_t *cb)
{
    star_t star;
    cairo_t *context;

    star.spike_count = random() % 6 + 2;
    star.inner_radius = objectweight;
    star.outer_radius = 20;
    star.mx = event->x;
    star.my = event->y;
    play_random_sound(cb->bus);
    context = get_cairo_context(cb, TRUE, FALSE);

    star.str = "none";
    star.color = cb->color;
    star.is_set = TRUE;
    cb->star = star;
    build_star(context, star);
}

static void
save_picture(gamine_t *cb)
{
    struct stat st;
    gchar *dirname;
    gchar *filename;
    gchar buf[24];
    time_t timestamp;
    struct tm * t;
    cairo_surface_t *surface;
    timestamp = time(NULL);
    t = localtime(&timestamp);

    dirname = g_build_filename(g_get_home_dir(), "gamine", NULL);
    //if dirname not exists
    if(stat(dirname,&st) != 0)
        if (mkdir(dirname, 0750) < 0)
            g_printf(_("*** Error: failed to create directory %s ***\n"),
                    dirname);
    sprintf(buf, "%d-%d-%d_%d-%d-%d.png", 1900 + t->tm_year,
            t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
    filename = g_build_filename(dirname, buf, NULL);
    surface = cairo_get_target (cb->context);
    if (cairo_surface_write_to_png(surface, filename) < 0)
        g_printf(_("*** Error: failed to create file %s ***\n"), filename);
    g_free(filename);
    g_free(dirname);
}

static gboolean
key_press (GtkWidget *pWidget,
    GdkEventKey* pKey,
    gamine_t *cb)
{
	guint32 unichar;
	static gchar utf8string[10];
	gsize utf8stringlen;
		
    if ((pKey->type == GDK_KEY_PRESS) && (pKey->keyval != lastkeyval)) {
        switch (pKey->keyval)
        {
            case GDK_KEY_Escape :
                gtk_main_quit ();
            break;
            case GDK_KEY_space :
                gtk_widget_queue_draw(pWidget);
            break;
            case GDK_KEY_Print:
                save_picture(cb);
            break;
            default:
		/*Get unicode string*/
		unichar = gdk_keyval_to_unicode(pKey->keyval);
		utf8stringlen = g_unichar_to_utf8(unichar, utf8string);
		utf8string[utf8stringlen] = '\0';
		/*Disable repeats*/
		lastkeyval = pKey->keyval;
		/*Draw it*/
		draw_string(cb, utf8string);
            break;
        }
    }
    
    return TRUE;
}

static gboolean
key_release (GtkWidget *pWidget,
    GdkEventKey* pKey,
    gamine_t *cb)
{
	if (pKey->type == GDK_KEY_RELEASE) {
		lastkeyval = 0;
	}
	
	return TRUE;
}

static void
display_help (GtkWidget *widget, 
			cairo_t *cr,
			gpointer data)
{
	PangoLayout *layout;
	PangoFontDescription *desc;
	GtkAllocation allocation;
    gint width, height;
    
    layout = pango_cairo_create_layout(cr);
	pango_layout_set_text(layout, _("Quit: esc | Clear: space | Save: printscr"), -1);
	
	desc = pango_font_description_from_string("Sans Bold 15");
	pango_layout_set_font_description(layout, desc);
	pango_font_description_free(desc);
	
	pango_layout_get_pixel_size(layout, &width, &height);
	gtk_widget_get_allocation(widget, &allocation);
	cairo_translate(cr, 0, allocation.height - height); 
	
	pango_cairo_update_layout(cr, layout);
	pango_cairo_show_layout(cr, layout);
	g_object_unref(layout);
}

gint
main (gint argc, gchar *argv[])
{
    gamine_t cb;
    GtkWidget *window;
    GdkWindow *gdkwindow;
    GtkWindow *gtkwindow;
    GdkPixbuf *cursor_pixbuf, *scaled_cursor_pixbuf;
    GdkPixbuf *icon_pixbuf;
    GdkCursor *cursor;
    GdkRGBA color;
    gchar *cursorfile;
    gchar *iconfile;
    gint imageheight, imagewidth;
    guint cursorheight, cursorwidth;
    star_t star;
    GdkScreen *screen;
    GdkDisplay *display;
    GdkDeviceManager *devicemanager;
    GdkDevice *mouse, *keyboard;
     
    //gettext
    bindtextdomain("gamine", LOCALDIR);
    bind_textdomain_codeset("gamine", "UTF8");
    textdomain("gamine");
    setlocale(LC_ALL, "");

    last_color_change = clock();
    gtk_init (&argc, &argv);
    gst_init (&argc, &argv);
    load_conf();

    star.is_set = FALSE;
    star.str = "none";
    cb.star = star;
    gst_play_background (cb.bus, background_music, TRUE);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Create the drawing area and configuration */
    cb.drawing_area = gtk_drawing_area_new ();
    color.red = 1.0;
    color.green = 1.0;
    color.blue = 1.0;
    color.alpha = 1.0;
    gtk_widget_override_background_color(cb.drawing_area, GTK_STATE_FLAG_NORMAL, (const GdkRGBA *)&color);
    gtk_container_add (GTK_CONTAINER (window), cb.drawing_area);

    gtkwindow = GTK_WINDOW(window);
    gtk_window_set_title (gtkwindow, "Gamine");
    gtk_window_set_wmclass(gtkwindow, "gamine", "Gamine");
    gtk_container_set_border_width (GTK_CONTAINER (gtkwindow), 0);
    
    /* Event signals */
    g_signal_connect (gtkwindow, "destroy",
         G_CALLBACK (gtk_main_quit), &gtkwindow);
    g_signal_connect (G_OBJECT (cb.drawing_area), "draw",
        G_CALLBACK (display_help), NULL);
    g_signal_connect (cb.drawing_area, "motion_notify_event",
        G_CALLBACK (draw_line), &cb);
    g_signal_connect (cb.drawing_area, "button_press_event",
        G_CALLBACK (draw_star), &cb);
    g_signal_connect (gtkwindow, "key-press-event",
        G_CALLBACK (key_press), &cb);
    g_signal_connect (gtkwindow, "key-release-event",
        G_CALLBACK (key_release), &cb);
 
    gtk_widget_set_events (cb.drawing_area,
            gtk_widget_get_events (cb.drawing_area)
        | GDK_LEAVE_NOTIFY_MASK
        | GDK_BUTTON_PRESS_MASK
        | GDK_BUTTON_RELEASE_MASK
        | GDK_POINTER_MOTION_MASK
        | GDK_POINTER_MOTION_HINT_MASK);

    gtk_window_present (gtkwindow);
    gtk_window_fullscreen (gtkwindow);
    gtk_widget_show_all (window);
    gdkwindow = gtk_widget_get_window(window);
    gdk_window_fullscreen(gdkwindow);
    gdk_window_raise(gdkwindow);

/* Set fullscreen, grab mouse/keyboard, ...*/
	screen = gtk_widget_get_screen(window);
    gdk_window_move_resize(gdkwindow, 0, 0, gdk_screen_get_width(screen), gdk_screen_get_height(screen));
    
    display = gdk_display_get_default();
    devicemanager = gdk_display_get_device_manager(display);
    mouse = gdk_device_manager_get_client_pointer(devicemanager);
    keyboard = gdk_device_get_associated_device(mouse);
    
    if (mouse != NULL) {
		gdk_device_grab(mouse,
                    gdk_get_default_root_window(),
                    GDK_OWNERSHIP_WINDOW,
                    TRUE,
                    GDK_POINTER_MOTION_MASK |
					GDK_BUTTON_PRESS_MASK |
					GDK_BUTTON_RELEASE_MASK,
                    NULL,
                    GDK_CURRENT_TIME);
    }
    
    if (keyboard != NULL) {    
		gdk_device_grab(keyboard,
                    gdk_get_default_root_window(),
                    GDK_OWNERSHIP_WINDOW,
                    TRUE,
                    GDK_KEY_PRESS_MASK |
                    GDK_KEY_RELEASE_MASK,
                    NULL,
                    GDK_CURRENT_TIME);
    }
/*cursor*/
    if ((gdk_display_supports_cursor_color(display)) && (gdk_display_supports_cursor_alpha(display))) {
		cursorfile = g_build_filename(DATADIR, "pencil.png", NULL);
		if (!g_file_test (cursorfile, G_FILE_TEST_EXISTS)) {
			g_printf(_("*** Error: %s does not exist ***\n"), cursorfile);
		} else {
			cursor_pixbuf = gdk_pixbuf_new_from_file(cursorfile, NULL);
			if (cursor_pixbuf != NULL) {
				/*Determine image size*/
				imageheight = gdk_pixbuf_get_height((const GdkPixbuf *)cursor_pixbuf);
				imagewidth = gdk_pixbuf_get_width((const GdkPixbuf *)cursor_pixbuf);
				gdk_display_get_maximal_cursor_size(display, &cursorwidth, &cursorheight);
				/*Correct image size if needed*/
				if ((imageheight > cursorheight) || (imagewidth > cursorwidth)) {
					scaled_cursor_pixbuf = gdk_pixbuf_scale_simple(cursor_pixbuf, cursorwidth, cursorheight, GDK_INTERP_BILINEAR);
					if (scaled_cursor_pixbuf != NULL) {
						g_object_unref(cursor_pixbuf);
						cursor_pixbuf = scaled_cursor_pixbuf;
						imageheight = cursorheight;
						imagewidth = cursorwidth;
					}
				}
				/*Set cursor*/
				cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), cursor_pixbuf, 0, imageheight-1);
				gdk_window_set_cursor(gdkwindow, cursor);
				g_object_unref(cursor);
				g_object_unref(cursor_pixbuf);
			}
		}
		g_free(cursorfile);
	} else {
		g_printf(_("*** Error: coloured cursors with alpha channel aren't supported, using default cursor ***\n"));
	}
/*Set icon*/
    iconfile = g_build_filename(DATADIR, "gamine.png", NULL);
    if (!g_file_test (iconfile, G_FILE_TEST_EXISTS))
        g_printf(_("*** Error: %s does not exist ***\n"), iconfile);
    else {
        icon_pixbuf = gdk_pixbuf_new_from_file(iconfile, NULL);
        gtk_window_set_icon (gtkwindow, icon_pixbuf);
        g_object_unref (icon_pixbuf);
    }
    g_free(iconfile);
    
    cb.xold = -1;
    cb.yold = -1;
    cb.context = NULL;
    lastkeyval = 0;


    gtk_main ();
    
    if (keyboard != NULL) {
		gdk_device_ungrab(keyboard, GDK_CURRENT_TIME);
	}
	
	if (mouse != NULL) {
		gdk_device_ungrab(mouse, GDK_CURRENT_TIME);
	}
    
    return 0;
}

