/*  Copyright (C) 2009 Joshua Judson Rosen <rozzin@geekspace.com>.

    This program is free software: you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 3 as published by the Free Software Foundation.

    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; see the file COPYING. If not, see
    <http://www.gnu.org/licenses/> or write to:

        The Free Software Foundation, inc.
        51 Franklin Street, Fifth Floor
        Boston, MA 02110-1301
        USA
*/

#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>
#include <glade/glade.h>
#include "graphics.h"
#include <time.h>

#include "paths.h"

#include <math.h>
#include <string.h>

#include <popt.h>

#include "virand/random.h"
#include "generator.h"


static const char *name = NULL;
static VisualID_Glyph *glyph;
static GtkWidget *editor, *canvas;
static GType param_group_type;

static double linewidth = .03;
static double outline_width = .015;


void
set_scale (GtkSpinButton *spinner, gpointer data)
{
  GdkRegion *drawable_region = gdk_drawable_get_visible_region (GDK_DRAWABLE (canvas->window));

  const char *param_name = gtk_widget_get_name (GTK_WIDGET (spinner));
  double param_value = gtk_spin_button_get_value (spinner);
  GParamSpec *param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (glyph), param_name);

  if (param_spec) {
    GType param_type = G_PARAM_SPEC_TYPE (param_spec);
    if (param_type == G_TYPE_PARAM_INT) {
      g_object_set (glyph, param_name, (int) param_value,
                    NULL);
    } else { /* otherwise, assume it's a double */
      g_object_set (glyph, param_name, param_value,
                  NULL);
    }
  }

  gdk_window_invalidate_region (canvas->window, drawable_region, FALSE);

  gdk_region_destroy (drawable_region);
}

gboolean
refresh_window (GtkWidget *canvas, GdkEventExpose *event, gpointer data)
{
  gint x, y, width, height;
  double glyph_width, glyph_height;

  cairo_t *cr = gdk_cairo_create (canvas->window);

  gdk_window_get_geometry (canvas->window, &x, &y, &width, &height,
                           NULL);
  glyph_width = glyph_height = fmin (width, height);

  cairo_translate (cr, (width-glyph_width)/2, (height-glyph_height)/2);

  cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
  cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);

  visualid_draw (glyph, cr, glyph_width, glyph_height,
                 linewidth + 2*outline_width);

  cairo_scale (cr, glyph_width/2, glyph_height/2);

  cairo_set_line_width (cr, linewidth + outline_width*2);
  cairo_set_source_rgb (cr, 1, 1, 1);
  cairo_stroke_preserve (cr);

  cairo_set_line_width (cr, linewidth);
  cairo_set_source_rgb (cr, 0, 0, 0);
  cairo_stroke (cr);

  cairo_destroy (cr);

  return TRUE;
}

GtkContainer *
add_parameter_frame (GtkContainer *container, const char *group_name)
{
  GtkWidget *frame = gtk_frame_new (group_name);
  GtkWidget *param_group = g_object_new (param_group_type, NULL);

  gtk_container_add (GTK_CONTAINER (frame), param_group);
  gtk_box_pack_start (GTK_BOX (container), GTK_WIDGET (frame),
                      FALSE, FALSE, 0);

  return GTK_CONTAINER (param_group);
}

GtkContainer *
add_parameter_tab (GtkContainer *container, const char *group_name)
{
  /* Note that this assumes that individual label/spinbutton combos
     are laid-out identically (e.g.: either above/below or side|side)
     regardless of overall layout. Maybe this can be changed by using
     the GtkOrientable interface? */

  GtkWidget *param_group = g_object_new (param_group_type, NULL);
  GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);

  gtk_button_box_set_layout (GTK_BUTTON_BOX (param_group), GTK_BUTTONBOX_START);
  gtk_button_box_set_spacing (GTK_BUTTON_BOX (param_group), 10);

  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window),
                                         param_group);

  gtk_notebook_append_page (GTK_NOTEBOOK (container), scrolled_window,
                            gtk_label_new (group_name));

  return GTK_CONTAINER (param_group);
}

void
setup_parameters (VisualID_Glyph *glyph, GtkContainer *container,
                  GtkContainer *(*group_cons) (GtkContainer *container,
                                               const char *group_name))
{
  guint nprops;
  GParamSpec **params = g_object_class_list_properties (G_OBJECT_GET_CLASS(G_OBJECT(glyph)), &nprops);

  int idx;
  GHashTable *map = g_hash_table_new_full (g_str_hash, g_str_equal,
                                           g_free, NULL);

  printf ("Setting-up parameters\n");
  for (idx = 0; idx < nprops; idx++) {
    GParamSpec *pspec = params[idx];
    const char *param_name = g_param_spec_get_name (pspec);
    GtkWidget *control = NULL;

    if (!(pspec->flags & G_PARAM_WRITABLE)
        || pspec->flags & G_PARAM_CONSTRUCT_ONLY)
    {
      printf ("UNWRITABLE PARAM: %s\n", param_name);
      continue;
    }

    if (G_IS_PARAM_SPEC_DOUBLE (pspec)) {
      GParamSpecDouble *double_spec = G_PARAM_SPEC_DOUBLE (pspec);
      double param_val;
      control = gtk_spin_button_new_with_range (double_spec->minimum,
                                                double_spec->maximum,
                                                .01);
      g_object_get (glyph, param_name, &param_val, NULL);
      gtk_spin_button_set_value (GTK_SPIN_BUTTON (control), param_val);
      g_signal_connect (control, "value-changed",
                        (GCallback) set_scale,
                        NULL);
    } else if (G_IS_PARAM_SPEC_INT (pspec)) {
      GParamSpecInt *int_spec = G_PARAM_SPEC_INT (pspec);
      int param_val;
      control = gtk_spin_button_new_with_range (int_spec->minimum,
                                                int_spec->maximum,
                                                1);
      g_object_get (glyph, param_name, &param_val, NULL);
      gtk_spin_button_set_value (GTK_SPIN_BUTTON (control),
                                 param_val);
      g_signal_connect (control, "value-changed",
                        (GCallback) set_scale,
                        NULL);
    } else {
      printf ("PARAM OF UNKNOWN TYPE: %s\n", param_name);
    }

    if (control) {
      int brk_idx = strchr (param_name, '-') - param_name;
      char *group_name = g_strndup (param_name, brk_idx);
      const char *param_subname = param_name + brk_idx + 1;

      GtkContainer *row = g_hash_table_lookup (map, group_name);
      GtkContainer *col;

      gtk_widget_set_name (GTK_WIDGET (control), param_name);

      if (row == NULL) {
        printf ("Creating new hash-table entry for \"%s\" parms\n", group_name);
        row = group_cons (container, group_name);
        g_hash_table_insert (map, group_name, row);
      }

      col = GTK_CONTAINER (gtk_vbox_new (FALSE, 0));
      gtk_container_add (col, gtk_label_new (param_subname));
      gtk_container_add (col, GTK_WIDGET (control));
      gtk_container_add (row, GTK_WIDGET (col));
      printf ("Adding %s to %s group\n", param_subname, group_name);
    } else {
      printf ("Not adding %s\n", param_name);
    }
  }

  g_hash_table_destroy (map);
}

void
set_line_width (GtkSpinButton *spinner, gpointer data)
{
  GdkRegion *drawable_region = gdk_drawable_get_visible_region (GDK_DRAWABLE (canvas->window));

  linewidth = gtk_spin_button_get_value_as_float (spinner);

  gdk_window_invalidate_region (canvas->window, drawable_region, FALSE);

  gdk_region_destroy (drawable_region);
}

void
set_outline_width (GtkSpinButton *spinner, gpointer data)
{
  GdkRegion *drawable_region = gdk_drawable_get_visible_region (GDK_DRAWABLE (canvas->window));

  outline_width = gtk_spin_button_get_value_as_float (spinner);

  gdk_window_invalidate_region (canvas->window, drawable_region, FALSE);

  gdk_region_destroy (drawable_region);
}

int
main (int argc, char **argv)
{
  VisualID_Generator *generator;

  GladeXML *xml;

  char *ui_gladefile_default = g_strdup_printf ("%s/editor/gui/editor.glade",
                                                VISUALID_DATADIR);
  char *ui_gladefile = ui_gladefile_default;

  GtkContainer *param_container;

  char *editor_title = NULL;

  struct poptOption cmd_options[] = {
    {"gui", 0, POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT,
     &ui_gladefile, 0,
     "use the GUI defined in this file", "FILEPATH"},

    POPT_AUTOHELP
    POPT_TABLEEND
  };

  poptContext optCon;
  int opt;

  gtk_init (&argc, &argv);
  glade_init ();

  optCon = poptGetContext (NULL, argc, (const char **) argv, cmd_options, 0);
  poptSetOtherOptionHelp (optCon, "[OPTION...] <input-name>");

  while ((opt = poptGetNextOpt (optCon)) >= 0) {
    switch (opt) {
    }
    /* No specific actions--just set variables and handle "--help" */
  }

  if (opt < -1) {
    fprintf (stderr, "Error parsing argument %s: %s.\n",
             poptBadOption (optCon, 0), poptStrerror (opt));
    exit (1);
  }

  xml = glade_xml_new (ui_gladefile, NULL, NULL);

  if (xml == NULL) {
    g_critical ("Failed to open UI definition: %s\n", ui_gladefile);
    exit (1);
  }

  editor = glade_xml_get_widget (xml, "editor");

  param_group_type = G_OBJECT_TYPE
    (GTK_CONTAINER (glade_xml_get_widget (xml, "stroke-parameters")));

  generator = g_object_new (VISUALID_TYPE_GENERATOR, NULL);

  name = poptGetArg (optCon);
  if (name) {
    editor_title = g_strdup_printf ("VisualID Explorer: %s", name);
    virand_set_seed_string (generator->state,
                            name, strlen (name));
  } else {
    editor_title = g_strdup_printf ("VisualID Explorer (random)");
    g_rand_set_seed (generator->state, time (NULL));
  }

  gtk_window_set_title (GTK_WINDOW (editor), editor_title);
  g_free (editor_title);

  glyph = visualid_glyph_new (generator);

  canvas = glade_xml_get_widget (xml, "canvas");
  param_container = GTK_CONTAINER (glade_xml_get_widget (xml, "parameters"));

  if (GTK_IS_NOTEBOOK (param_container)) {
    setup_parameters (glyph,
                      param_container,
                      add_parameter_tab);
  } else {
    setup_parameters (glyph,
                      param_container,
                      add_parameter_frame);
  }

  g_signal_connect (G_OBJECT (canvas), "expose-event",
                    G_CALLBACK (refresh_window), NULL);

  glade_xml_signal_autoconnect (xml);

  gtk_widget_show_all (editor);
  gtk_main ();

  return 0;
}

