/*  Copyright (C) 2008--2010 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 <glib/grand.h>
#include "virand/random.h"

#include <math.h>
#include <cairo.h>

#include <glib-object.h>
#include "graphics.h"
#include "spiral.h"

#include <glib.h>

struct _VisualID_SpiralClass {
  VisualID_GlyphClass parent_class;
};

G_DEFINE_TYPE(VisualID_Spiral, visualid_spiral, VISUALID_TYPE_GLYPH)

void
exec_spiral (VisualID_Spiral *spiral, cairo_t *cr)
{
  double x, y;
  int i;

  cairo_get_current_point (cr, &x, &y);
  cairo_new_sub_path (cr);

  if (spiral->centre_child) {
    cairo_save (cr);
    cairo_move_to (cr, 0, 0);
    cairo_scale (cr, spiral->centre_scale, spiral->centre_scale);
    visualid_draw_path (spiral->centre_child, cr);
    cairo_restore (cr);
    cairo_new_sub_path (cr);
  }

  for (i = 1;
       i <= spiral->len;
       i++) {
    double theta = 2 * M_PI * ((double) i) / ((double) spiral->ppr);
    double r = 0.15 * sqrt(theta);
    double vx = r * -sin(theta);
    double vy = r * cos(theta);

    cairo_line_to (cr, vx, vy);

    if (spiral->outer_child) {
      cairo_save (cr);
      cairo_translate (cr, vx, vy);
      cairo_scale (cr, spiral->outer_scale, spiral->outer_scale);
      cairo_rotate (cr, theta);
      visualid_draw_path (spiral->outer_child, cr);
      cairo_restore (cr);
    }
  }

  cairo_new_sub_path (cr);
  cairo_move_to (cr, x, y);
}

static void
spiral_constructed (GObject *self)
{
  VisualID_Spiral *spiral = VISUALID_SPIRAL (self);
  VisualID_Glyph *glyph = VISUALID_GLYPH (self);
  GObjectClass *parent_class = G_OBJECT_CLASS (visualid_spiral_parent_class);

  if (parent_class->constructed) {
    parent_class->constructed (self);
  }

  switch (glyph->recursion_level) {
  case 0:
    spiral->len = g_rand_double_range (glyph->generator->state, 25, 60); break;
  case 1:
    spiral->len = g_rand_double_range (glyph->generator->state, 10, 30); break;
  case 2:
    spiral->len = g_rand_double_range (glyph->generator->state, 5, 10); break;
  default: /* > 2 */
    spiral->len = 5; break;
  }

  spiral->aligned = virand_prob (glyph->generator->state, .5);

  if (spiral->aligned) {
    spiral->ppr = virand_range_biased (glyph->generator->state, 3, 35, 2);
  } else {
    spiral->ppr =
      virand_rational (glyph->generator->state, 3, 35,
                       round (g_rand_double_range (glyph->generator->state,
                                                   4, 7)));
  }

  if (virand_prob (glyph->generator->state, 0.5)) {
    visualid_glyph_spawn (glyph, "centre-child");
    spiral->centre_scale = g_rand_double_range (glyph->generator->state,
                                                0.15, 0.7);
  }
  else {
    spiral->centre_child = NULL;
  }

  if (virand_prob (glyph->generator->state, 0.5)) {
    visualid_glyph_spawn (glyph, "vertex-child");
    spiral->outer_scale = g_rand_double_range (glyph->generator->state,
                                               0.05, 0.6);
  }
  else {
    spiral->outer_child = NULL;
  }
}

void
spiral_dispose (GObject *self)
{
  VisualID_Spiral *spiral = VISUALID_SPIRAL (self);

  if (spiral->outer_child) {
    visualid_glyph_unparent (spiral->outer_child);
    spiral->outer_child = NULL;
  }

  if (spiral->centre_child) {
    visualid_glyph_unparent (spiral->centre_child);
    spiral->centre_child = NULL;
  }

  G_OBJECT_CLASS (visualid_spiral_parent_class)->dispose (self);
}

enum {
  PROP_0,

  PROP_SPIRAL_TWIST,

  PROP_VERTEX_COUNT,
  PROP_VERTEX_ALIGNMENT,

  PROP_VERTEX_CHILD,
  PROP_VERTEX_SCALE,

  PROP_CENTRE_CHILD,
  PROP_CENTRE_SCALE
};

static void
spiral_get_property (GObject *object,
                     guint property_id,
                     GValue *value,
                     GParamSpec *pspec)
{
  VisualID_Spiral *self = VISUALID_SPIRAL (object);

  switch (property_id)
    {
    case PROP_SPIRAL_TWIST: /* rotations; should this be radians? */
      g_value_set_double (value, (double) self->len / self->ppr);
      break;

      /* case PROP_VERTEX_DENSITY: == ppr... */
    case PROP_VERTEX_COUNT:
      g_value_set_int (value, self->len);
      break;
    case PROP_VERTEX_ALIGNMENT:
      g_value_set_boolean (value, self->aligned);
      break;

    case PROP_VERTEX_CHILD:
      g_value_set_object (value, self->outer_child);
      break;
    case PROP_VERTEX_SCALE:
      g_value_set_double (value, self->outer_scale);
      break;

    case PROP_CENTRE_CHILD:
      g_value_set_object (value, self->centre_child);
      break;
    case PROP_CENTRE_SCALE:
      g_value_set_double (value, self->centre_scale);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
spiral_set_property (GObject *object,
                     guint property_id,
                     const GValue *value,
                     GParamSpec *pspec)
{
  VisualID_Spiral *self = VISUALID_SPIRAL (object);

  switch (property_id)
    {
    case PROP_SPIRAL_TWIST: /* rotations; should this be radians? */
      self->ppr = self->len / g_value_get_double (value);
      break;

    case PROP_VERTEX_COUNT:
      {
        double twist = (double) self->len / self->ppr;
        self->len = g_value_get_int (value);
        self->ppr = (double) self->len / twist;
      }
      break;
    case PROP_VERTEX_ALIGNMENT:
      self->aligned = g_value_get_boolean (value);
      break;

    case PROP_VERTEX_CHILD:
      visualid_glyph_take_child (VISUALID_GLYPH (self),
                                 g_value_get_object (value),
                                 &self->outer_child);
      break;
    case PROP_VERTEX_SCALE:
      self->outer_scale = g_value_get_double (value);
      break;

    case PROP_CENTRE_CHILD:
      visualid_glyph_take_child (VISUALID_GLYPH (self),
                                 g_value_get_object (value),
                                 &self->centre_child);
      break;
    case PROP_CENTRE_SCALE:
      self->centre_scale = g_value_get_double (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static int
visualid_spiral_complexity (VisualID_Spiral *spiral, int recurse)
{
  int complexity = spiral->len;

  if (recurse--) {
    if (spiral->centre_child) {
      complexity += visualid_glyph_complexity (spiral->centre_child, recurse);
    }

    if (spiral->outer_child) {
      complexity += (visualid_glyph_complexity (spiral->outer_child, recurse)
                     * spiral->len);
    }
  }

  return complexity;
}

static void
visualid_spiral_class_init (VisualID_SpiralClass *class)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  VISUALID_GLYPH_CLASS (class)->produce = (producer_fn *) exec_spiral;
  VISUALID_GLYPH_CLASS (class)->complexity_fn =
    (complexity_fn *) visualid_spiral_complexity;

  gobject_class->get_property = spiral_get_property;
  gobject_class->set_property = spiral_set_property;
  gobject_class->constructed = spiral_constructed;
  gobject_class->dispose = spiral_dispose;

  g_object_class_install_property
    (gobject_class, PROP_SPIRAL_TWIST,
     g_param_spec_double ("spiral-twist",
                          "Spiral twist",
                          "The amount of twist in the spiral",
                          5.0/35.0, 60.0/(3.0/7.0), 1,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_VERTEX_COUNT,
     g_param_spec_int ("vertex-count",
                       "Number of vertices",
                       "The number of vertices in the spiral",
                       5, 60, 5,
                       G_PARAM_READWRITE));
  g_object_class_install_property
    (gobject_class, PROP_VERTEX_ALIGNMENT,
     g_param_spec_boolean ("vertex-alignment",
                           "Vertex alignment",
                           "Whether vertices are aligned on radial spokes",
                           FALSE,
                           G_PARAM_READWRITE));
  g_object_class_install_property
    (gobject_class, PROP_VERTEX_CHILD,
     g_param_spec_object ("vertex-child",
                          "Vertex child",
                          "Glyph placed at vertices",
                          VISUALID_TYPE_GLYPH,
                          G_PARAM_READWRITE));
  g_object_class_install_property
    (gobject_class, PROP_VERTEX_SCALE,
     g_param_spec_double ("vertex-scale",
                          "Vertex scale",
                          "Relative scale of the vertex-glyphs",
                          0.05, 0.6, .325,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_CENTRE_CHILD,
     g_param_spec_object ("centre-child",
                          "Centre child",
                          "Glyph placed in the centre",
                          VISUALID_TYPE_GLYPH,
                          G_PARAM_READWRITE));
  g_object_class_install_property
    (gobject_class, PROP_CENTRE_SCALE,
     g_param_spec_double ("centre-scale",
                          "Centre scale",
                          "Relative scale of the centerpiece",
                          0.15, 0.7, 0.42,
                          G_PARAM_READWRITE));
}

static void
visualid_spiral_init (VisualID_Spiral *self)
{
}
