/*  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.h>
#include <glib-object.h>
#include "graphics.h"
#include "metashape.h"

static double
rndin_level (GRand *state, double low, double high, int level)
{ /* level starts at 0, but we'll shift the scale
     so that it starts at 1, here */
  return low + g_rand_double (state) * (high - low) / ((double) level + 1);
}

static double
outer_angle (double r, double r_plus, double angle_inside)
{
      double y_1 = r_plus * cos (angle_inside) - r;
      double x = r_plus * sin (angle_inside);
      double theta_1 = atan (x / y_1);

      if (theta_1 < 0) {
        theta_1 += M_PI;
      }

      return theta_1;
}

void
visualid_metashape_exec (VisualID_Metashape *shape, cairo_t *cr)
{
  double x, y;
  int i;

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

  cairo_save (cr);
  for (i = 0; i <= shape->n; i++) {
    cairo_line_to (cr, 0, shape->points[i]);

    if (i == shape->n) {
      break;
    }

    if (shape->outline_child) {
      cairo_save (cr);
      cairo_translate (cr, 0, shape->points[i]);
      cairo_scale (cr, shape->oc_scale, shape->oc_scale);
      visualid_draw_path (shape->outline_child, cr);
      cairo_restore (cr);
    }

    if (shape->inside_child) {
      double angle_inside = shape->max_angle / shape->n;
      double r = shape->points[i];
      double r_plus = shape->points[i + 1];
      double r_minus;

      if (i == 0) {
        if (shape->max_angle < 2*M_PI) {
          r_minus = shape->points[i]*2 - r_plus;
        } else {
          r_minus = shape->points[shape->n - 1];
        }
      } else { /* 0 < i < n */
        r_minus = shape->points[i - 1];
      }

      cairo_save (cr);
      cairo_translate (cr, 0, shape->points[i]);
      cairo_rotate (cr, (outer_angle (r, r_plus, angle_inside) -
                         outer_angle (r, r_minus, angle_inside)) / 2);

      cairo_translate (cr, 0, -shape->ic_scale*4);

      cairo_scale (cr, shape->ic_scale, shape->ic_scale);
      visualid_draw_path (shape->inside_child, cr);
      cairo_restore (cr);
    }

    cairo_rotate (cr, shape->max_angle / shape->n);
  }
  cairo_restore (cr);

  if (shape->max_angle == 2*M_PI) {
    cairo_line_to (cr, 0, shape->points[0]);
    cairo_close_path (cr);
  }

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

G_DEFINE_ABSTRACT_TYPE (VisualID_Metashape, visualid_metashape, VISUALID_TYPE_GLYPH)

static void
metashape_constructed (GObject *self)
{
  VisualID_Metashape *shape = VISUALID_METASHAPE (self);
  VisualID_Glyph *glyph = VISUALID_GLYPH (self);
  GObjectClass *parent_class = G_OBJECT_CLASS (visualid_metashape_parent_class);

  int level;

  int i, j;

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

  level = VISUALID_GLYPH (self)->recursion_level;

  shape->n = pow (2, rndin_level (glyph->generator->state, 3, 6, level));

 /* Whether to use fourier-series generation: */
  shape->fourier = virand_prob (glyph->generator->state, 0.5);

  /* Parameters for fourier-series generation: */
  shape->ncoef = 2 * g_rand_double_range (glyph->generator->state, 2, 5) + 1;
  shape->coefficients = g_new (double, shape->ncoef);

  /* spectral exponent for FFT filtering: */
/*   shape->exponent = g_rand_double_range (glyph->generator->state, 1.6, 2.2); */

  shape->points = g_new (double, shape->n+1);

  /* XXX To be true to the essay, we should check whether shape->fourier,
     rather than just always using fourier-series generation: */

  for (i = 0; i <= shape->n; i++) {
    shape->points[i] = .5; /* run around on a .5-unit circle;
                          starting with 1 will probably result in
                          excessively large shapes. */
  }

  for (j = 0; j < shape->ncoef; j++) {
    shape->coefficients[j] =
      g_rand_double_range (glyph->generator->state, -1.0, 1.0) / (j + 1);
    /*^ 1/f envelope */

    for (i = 0; i <= shape->n; i++) {
      double angle = shape->max_angle * i / shape->n;

      shape->points[i] += shape->coefficients[j] * sin((j + 1) * angle) / 2;
    }
  }
  /* Alternately, we don't really /need/ to use a fourier
     series--whatever series we generate will be cyclic by virtue of
     the ends being connected. This produces fine shapes, for example:

         shape->points[i] = 1 - rnd11 () / ((double) i + 1);

     ... though it probably ends up ~100% too big
     (but sometimes this looks good).
  */


  if ((shape->max_angle < 2*M_PI &&
       virand_prob_reduced (glyph->generator->state, .8, level))
  ||  virand_prob_reduced (glyph->generator->state, .6, level)) {
    visualid_glyph_spawn (glyph, "vertex-child");
    shape->oc_scale = g_rand_double_range (glyph->generator->state, .1, .6);
  } else {
    shape->outline_child = NULL;
  }
}

static void
metashape_dispose (GObject *self)
{
  VisualID_Metashape *shape = VISUALID_METASHAPE (self);

  if (shape->outline_child) {
    visualid_glyph_unparent (shape->outline_child);
    shape->outline_child = NULL;
  }

  if (shape->inside_child) {
    visualid_glyph_unparent (shape->inside_child);
    shape->inside_child = NULL;
  }

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

enum {
  PROP_VERTEX_COUNT = 1,

  PROP_OUTLINE_CHILD,
  PROP_OUTLINE_CHILD_SCALE
};

static void
metashape_get_property (GObject *object,
                    guint property_id,
                    GValue *value,
                    GParamSpec *pspec)
{
  VisualID_Metashape *self = VISUALID_METASHAPE (object);

  switch (property_id)
    {
    case PROP_VERTEX_COUNT:
      g_value_set_int (value, self->n);
      break;

    case PROP_OUTLINE_CHILD:
      g_value_set_object (value, self->outline_child);
      break;
    case PROP_OUTLINE_CHILD_SCALE:
      g_value_set_double (value, self->oc_scale);
      break;

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

void
synth_wave (VisualID_Metashape *self)
{
  if (self->n) {
    int i, j;
    for (i = 0; i <= self->n; i++) {
      double angle = self->max_angle * i / self->n;
      self->points[i] = .5;

      for (j = 0; j < self->ncoef; j++) {
        self->points[i] += self->coefficients[j] * sin ((j+1) * angle) / 2;
      }
    }
  }
}

static void
metashape_set_property (GObject *object,
                        guint property_id,
                        const GValue *value,
                        GParamSpec *pspec)
{
  VisualID_Metashape *self = VISUALID_METASHAPE (object);

  switch (property_id)
    {
    case PROP_VERTEX_COUNT:
      self->n = g_value_get_int (value);
      self->points = g_renew (double, self->points, self->n+1);
      synth_wave (self);
      break;

    case PROP_OUTLINE_CHILD:
      visualid_glyph_take_child (VISUALID_GLYPH (self),
                                 g_value_get_object (value),
                                 &self->outline_child);
      break;
    case PROP_OUTLINE_CHILD_SCALE:
      self->oc_scale = g_value_get_double (value);
      break;

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

int
visualid_metashape_complexity (VisualID_Metashape *shape, int recurse)
{
  int complexity = shape->n;

  if (recurse--) {
    if (shape->outline_child) {
      complexity += (visualid_glyph_complexity (shape->outline_child, recurse)
                     * shape->n);
    }
  }

  return complexity;
}

static void
visualid_metashape_init (VisualID_Metashape *self)
{
  self->max_angle = 2*M_PI;
}

static void
visualid_metashape_class_init (VisualID_MetashapeClass *class)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  VISUALID_GLYPH_CLASS (class)->produce =
    (producer_fn *) visualid_metashape_exec;
  VISUALID_GLYPH_CLASS (class)->complexity_fn =
    (complexity_fn *) visualid_metashape_complexity;

  gobject_class->get_property = metashape_get_property;
  gobject_class->set_property = metashape_set_property;
  gobject_class->constructed = metashape_constructed;
  gobject_class->dispose = metashape_dispose;

  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 outline",
                       1<<3, 1<<6, 1<<4,
                       G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_OUTLINE_CHILD,
     g_param_spec_object ("vertex-child",
                          "Outline child",
                          "Glyph to place at vertices along the outline",
                          VISUALID_TYPE_GLYPH,
                          G_PARAM_READWRITE));
  g_object_class_install_property
    (gobject_class, PROP_OUTLINE_CHILD_SCALE,
     g_param_spec_double ("vertex-scale",
                          "Vertex scale",
                          "Relative scale of the glyphs placed at vertices",
                          .1, .6, .3,
                          G_PARAM_READWRITE));
}
