/*  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 "line.h"

#include <glib.h>

struct _VisualID_LineClass {
  VisualID_GlyphClass parent_class;
};

G_DEFINE_TYPE(VisualID_Line, visualid_line, VISUALID_TYPE_GLYPH)

static void
exec_line (VisualID_Line *line, cairo_t *cr)
{
  double x, y;
  int i;

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

  cairo_move_to (cr, 0, line->length/2);
  if (line->head) {
    cairo_save (cr);
    cairo_translate (cr, 0, line->length/2);
    cairo_scale (cr, line->head_scale, line->head_scale);
    visualid_draw_path (line->head, cr);
    cairo_restore (cr);
  }

  cairo_line_to (cr, 0, -line->length/2);
  if (line->tail) {
    cairo_save (cr);
    cairo_translate (cr, 0, -line->length/2);
    cairo_scale (cr, line->tail_scale, line->tail_scale);
    visualid_draw_path (line->tail, cr);
    cairo_restore (cr);
  }

  if (line->rib) {
    for (i = 0;
         i < line->rib_count;
         i++) {
      /* # of 0-based indices along the line;
         or the number of *spaces* between ribs: */
      int nspaces = (line->rib_count - 1);

      /* Shift the scale so that, rather than covering [0%, 100%],
         it covers [-50%, 50%] with zero at the centre: */
      double base_offset = -.5;

      double progress = 0;
      if (nspaces != 0) {
        progress = (double) i / nspaces;
      }

      cairo_save (cr);
      cairo_translate (cr, 0, line->length * (base_offset + progress));
      cairo_scale (cr, line->rib_scale, line->rib_scale);
      if (line->rib_taper) {
        double taper = 1 + (.5 * i/line->rib_count);
        if (!line->rib_increase) {
          taper = 1/taper;
        }
        cairo_scale (cr, taper, taper);
      }
      if (line->rib_doublesided) {
        cairo_translate (cr, 1, 0);
        cairo_save (cr);
        cairo_rotate (cr, line->rib_angle - M_PI_2);
        visualid_draw_path (line->rib, cr);
        cairo_restore (cr);
        cairo_scale (cr, -1, 1);
        cairo_translate (cr, 2, 0);
      }
      cairo_rotate (cr, line->rib_angle - M_PI_2);
      visualid_draw_path (line->rib, cr);
      cairo_restore (cr);
    }
  }

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

static void
line_constructed (GObject *self)
{
  VisualID_Line *line = VISUALID_LINE (self);
  VisualID_Glyph *glyph = VISUALID_GLYPH (self);
  GObjectClass *parent_class = G_OBJECT_CLASS (visualid_line_parent_class);

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

  line->length = g_rand_double_range (glyph->generator->state, 0.5, 1);
  line->rib_count = virand_range_biased (glyph->generator->state, 3, 10, 1.5);

  if ((line->spawn_rib = virand_prob (glyph->generator->state, 0.5))) {
    line->rib_scale = virand_range_biased (glyph->generator->state,
                                           0.05, 0.7, 1.5);
    line->rib_doublesided = virand_prob (glyph->generator->state, 0.8);
    line->rib_angle = g_rand_double_range (glyph->generator->state,
                                           -M_PI_4, M_PI_4);
    line->rib_taper = virand_prob (glyph->generator->state, 0.3);
    line->rib_increase = virand_prob (glyph->generator->state, 0.3);
  }

  if ((line->spawn_tail = virand_prob (glyph->generator->state, 0.75))) {
    line->tail_scale = g_rand_double_range (glyph->generator->state, 0.15, 0.6);
  }

  if ((line->spawn_head = !(line->tail || line->rib) ||
       virand_prob (glyph->generator->state, 0.75))) {
    line->head_scale = g_rand_double_range (glyph->generator->state, 0.15, 0.6);
  }
}

static gboolean
line_extend (VisualID_Glyph *glyph, int atlevel)
{
  VisualID_Line *line = VISUALID_LINE (glyph);

  gboolean extended = FALSE;

  if (atlevel) {
    if (line->rib) {
      extended |= visualid_glyph_extend (line->rib, atlevel-1);
    }

    if (line->tail) {
      extended |= visualid_glyph_extend (line->tail, atlevel-1);
    }

    if (line->head) {
      extended |= visualid_glyph_extend (line->head, atlevel-1);
    }

    return extended;
  }

  if (line->spawn_rib) {
    if (visualid_glyph_spawn (glyph, "rib-child")) {
      extended = TRUE;
    }
  }

  if (line->spawn_tail) {
    if (visualid_glyph_spawn (glyph, "tail-child")) {
      extended = TRUE;
    }
  }

  if (line->spawn_head)
  {
    if (visualid_glyph_spawn (glyph, "head-child")) {
      extended = TRUE;
    }
  }

  return extended;
}

static void
line_dispose (GObject *self)
{
  VisualID_Line *line = VISUALID_LINE (self);

  if (line->head) {
    visualid_glyph_unparent (line->head);
    line->head = NULL;
  }

  if (line->tail) {
    visualid_glyph_unparent (line->tail);
    line->tail = NULL;
  }

  if (line->rib) {
    visualid_glyph_unparent (line->rib);
    line->rib = NULL;
  }

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

enum {
  PROP_LENGTH = 1,

  PROP_HEAD_CHILD,
  PROP_HEAD_SCALE,

  PROP_TAIL_CHILD,
  PROP_TAIL_SCALE,

  PROP_RIB_CHILD,
  PROP_RIB_COUNT,
  PROP_RIB_SCALE,
  PROP_RIB_ANGLE,
  PROP_SYMMETRY,
  PROP_TAPER,
  PROP_INCREASE
};

static void
line_get_property (GObject *object,
                   guint property_id,
                   GValue *value,
                   GParamSpec *pspec)
{
  VisualID_Line *self = VISUALID_LINE (object);

  switch (property_id)
    {
    case PROP_LENGTH:
      g_value_set_double (value, self->length);
      break;
    case PROP_HEAD_CHILD:
      g_value_set_object (value, self->head);
      break;
    case PROP_HEAD_SCALE:
      g_value_set_double (value, self->head_scale);
      break;

    case PROP_TAIL_CHILD:
      g_value_set_object (value, self->tail);
      break;
    case PROP_TAIL_SCALE:
      g_value_set_double (value, self->tail_scale);
      break;

    case PROP_RIB_COUNT:
      g_value_set_int (value, self->rib_count);
      break;
    case PROP_RIB_SCALE:
      g_value_set_double (value, self->rib_scale);
      break;
    case PROP_RIB_ANGLE:
      g_value_set_double (value, self->rib_angle);
      break;
    case PROP_RIB_CHILD:
      g_value_set_object (value, self->rib);
      break;
    case PROP_SYMMETRY:
      g_value_set_boolean (value, self->rib_doublesided);
      break;
    case PROP_TAPER:
      g_value_set_boolean (value, self->rib_taper);
      break;

    case PROP_INCREASE:
      g_value_set_boolean (value, self->rib_increase);

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

static void
line_set_property (GObject *object,
                   guint property_id,
                   const GValue *value,
                   GParamSpec *pspec)
{
  VisualID_Line *self = VISUALID_LINE (object);
  switch (property_id)
  {
    case PROP_LENGTH:
      self->length = g_value_get_double (value);
      break;

    case PROP_HEAD_CHILD:
      visualid_glyph_take_child (VISUALID_GLYPH (self),
                                 g_value_get_object (value),
                                 &self->head);
      break;
    case PROP_HEAD_SCALE:
      self->head_scale = g_value_get_double (value);
      break;

    case PROP_TAIL_CHILD:
      visualid_glyph_take_child (VISUALID_GLYPH (self),
                                 g_value_get_object (value),
                                 &self->tail);
      break;
    case PROP_TAIL_SCALE:
      self->tail_scale = g_value_get_double (value);
      break;

    case PROP_RIB_CHILD:
      visualid_glyph_take_child (VISUALID_GLYPH (self),
                                 g_value_get_object (value),
                                 &self->rib);
      break;
    case PROP_RIB_COUNT:
      self->rib_count = g_value_get_int (value);
      break;
    case PROP_RIB_SCALE:
      self->rib_scale = g_value_get_double (value);
      break;
    case PROP_RIB_ANGLE:
      self->rib_angle = g_value_get_double (value);
      break;
    case PROP_SYMMETRY:
      self->rib_doublesided = g_value_get_boolean (value);
      break;
    case PROP_TAPER:
      self->rib_taper = g_value_get_boolean (value);
      break;
    case PROP_INCREASE:
      self->rib_increase = g_value_get_boolean (value);
      break;

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

static int
visualid_line_complexity (VisualID_Line *line, int recurse)
{
  int complexity = 1;

  if (recurse--) {
    if (line->head) {
      complexity += visualid_glyph_complexity (line->head, recurse);
    }

    if (line->tail) {
      complexity += visualid_glyph_complexity (line->tail, recurse);
    }

    if (line->rib) {
      int rib_complexity = visualid_glyph_complexity (line->rib, recurse);

      if (line->rib_doublesided) {
        rib_complexity *= 2;
      }

      complexity += (rib_complexity * line->rib_count);
    }
  }

  return complexity;
}

static void
visualid_line_class_init (VisualID_LineClass *class)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  VISUALID_GLYPH_CLASS (class)->extend = line_extend;
  VISUALID_GLYPH_CLASS (class)->render = (visualid_render_fn *) exec_line;
  VISUALID_GLYPH_CLASS (class)->complexity_fn =
    (visualid_complexity_fn *) visualid_line_complexity;

  gobject_class->get_property = line_get_property;
  gobject_class->set_property = line_set_property;
  gobject_class->constructed = line_constructed;
  gobject_class->dispose = line_dispose;

  g_object_class_install_property
    (gobject_class, PROP_LENGTH,
     g_param_spec_double ("line-length",
                          "Line length",
                          "Relative length of the line",
                          .5, 1, .75,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_HEAD_CHILD,
     g_param_spec_object ("head-child",
                          "Head child",
                          "Glyph to place at the top (or outward-pointing) end of the line",
                          VISUALID_TYPE_GLYPH,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_HEAD_SCALE,
     g_param_spec_double ("head-scale",
                          "Head scale",
                          "Relative scale of the head-glyph",
                          .15, .6, .4,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_TAIL_CHILD,
     g_param_spec_object ("tail-child",
                          "Tail child",
                          "Glyph to place at the bottom (or inward-pointing) end of the line",
                          VISUALID_TYPE_GLYPH,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_TAIL_SCALE,
     g_param_spec_double ("tail-scale",
                          "Tail scale",
                          "Relative scale of tail-glyph",
                          .15, .6, .4,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_RIB_CHILD,
     g_param_spec_object ("rib-child",
                          "Rib child",
                          "Glyph to place at regular intervals along the line",
                          VISUALID_TYPE_GLYPH,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_RIB_COUNT,
     g_param_spec_int ("rib-count",
                       "Rib count",
                       "The number of `rib' glyphs to place along the line",
                       3, 10, 5,
                       G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_RIB_SCALE,
     g_param_spec_double ("rib-scale",
                          "Rib scale",
                          "(Nominal) relative scale of rib-glyphs",
                          .05, .7, .1,
                          G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_RIB_ANGLE,
     g_param_spec_double ("rib-angle",
                          "Rib angle",
                          "Angle at which rib-glyphs are rotated relative to the line",
                          -M_PI_4, M_PI_4, 0,
                          G_PARAM_READWRITE));
}

static void
visualid_line_init (VisualID_Line *self)
{
}

