/*  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 <math.h>
#include <cairo.h>
#include <glib-object.h>

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

#include "graphics.h"

#include "radial.h"
#include "spiral.h"
#include "line.h"
#include "figure.h"
#include "metashape.h"
#include "shape.h"
#include "path.h"
#include "symmetry.h"

#include <assert.h>

VisualID_Glyph *
visualid_glyph_new (VisualID_Generator *generator)
{
  GType glyph_type = (GType) virand_generate_discrete (generator->roots);

  return g_object_new (glyph_type,
                       "generator", generator,
                       NULL);
}

VisualID_Glyph *
visualid_glyph_spawn (VisualID_Glyph *parent, const char *attachment)
{
  int level;
  int complexity;
  GType glyph_type;
  VisualID_Glyph *glyph_root = parent;
  VisualID_Generator *generator;

  assert (parent);

  generator = parent->generator;

  while (glyph_root->parent_glyph) {
    glyph_root = glyph_root->parent_glyph;
  }

  complexity = visualid_glyph_complexity (glyph_root, -1);
  level = parent->recursion_level + 1;

  if (level > generator->recursion_max
   || complexity > generator->complexity_max) {
    return NULL;
  } else if (level < generator->recursion_max &&
             complexity < generator->complexity_max) {
    glyph_type =
      (GType) virand_generate_discrete (parent->generator->children);
  } else {
    glyph_type =
      (GType) virand_generate_discrete (parent->generator->terminals);
  }

  return g_object_new (glyph_type, "parent-glyph", parent,
                                   "parent-attachment", attachment,
                                   NULL);
}

void
visualid_glyph_unparent (VisualID_Glyph *glyph)
{
  if (!glyph->parent_glyph) {
    return;
  }

  glyph->parent_glyph = NULL;
  g_object_unref (glyph);
}

void
visualid_glyph_take_child (VisualID_Glyph *parent, VisualID_Glyph *orphan,
                           VisualID_Glyph **attachment)
{
  /* Take ownership of a (child) glyph--become its parent.

     Note that "orphan" means `a child who currently has no parent'....
  */

  /* This could be named `adopt_child()',
     except then I wouldn't be sure what to do with pre-owned children....
  */

  if (attachment && orphan == *attachment) {
    /* This parent already owns this child. */
    return;
  }

  if (orphan) {
    if (orphan->parent_glyph && orphan->parent_glyph != parent) {
      /* Child already has a parent? Steal the child!?
         No, it's not clear that we need to support kidnapping, yet.

         Note that the child may already know its new parent--
         this happens during child-construction, and is OK
         (it's how the automatic complexity-limiter works).
      */
      g_warning ("Trying to set %s as parent of a %s glyph that already has a parent\n",
                 G_OBJECT_TYPE_NAME (parent),
                 G_OBJECT_TYPE_NAME (orphan));
      return;
    } else {
      g_object_ref_sink (orphan);
      orphan->parent_glyph = parent;
    }
  }

  if (attachment) {
    if (*attachment) {
      visualid_glyph_unparent (*attachment);
    }

    *attachment = orphan;
  }
}

void
visualid_draw_path (VisualID_Glyph *glyph, cairo_t *cr)
{
  g_return_if_fail (VISUALID_IS_GLYPH (glyph));

  cairo_save (cr);
  VISUALID_GLYPH_GET_CLASS (glyph)->produce (glyph, cr);
  cairo_restore (cr);
}

void
visualid_draw (VisualID_Glyph *gen, cairo_t *cr,
               double width, double height,
               double linewidth)
{
  /* The `virtual canvas' has a unit radius,
     which means it's 2 units wide and 2 units tall: */
  const double target_size = 2;

  double path_left, path_right,
         path_top, path_bottom;
  double stroke_left, stroke_right,
         stroke_top, stroke_bottom;
  double margin_left, margin_right,
         margin_top, margin_bottom;
  double path_width, path_height;
  double midpoint_x, midpoint_y;
  double rescale;

  cairo_save (cr);

  cairo_scale (cr, width/target_size, -height/target_size);
  cairo_translate (cr, target_size/2, -target_size/2);

  visualid_draw_path (gen, cr);

  cairo_path_extents (cr, &path_left, &path_bottom,
                          &path_right, &path_top);
  cairo_set_line_width (cr, linewidth);
  cairo_stroke_extents (cr, &stroke_left, &stroke_bottom,
                            &stroke_right, &stroke_top);

  /* These need to be computed individually by comparing stroke-bounds
     and path-bounds, because the space needed for non-round
     line-joints is unpredictable: */

  margin_top = stroke_top - path_top;
  margin_left = path_left - stroke_left;
  margin_right = stroke_right - path_right;
  margin_bottom = path_bottom - stroke_bottom;

  path_width = path_right - path_left;
  path_height = path_top - path_bottom;

  /* Scale the glyph up or down so that it fits perfectly onto the canvas,
     (note that stroking the path with a brush that's (linewidth)
     units wide will add (linewidth/2) to each side,
     so the target-size of the _path_ per se should be that much smaller),
     assuming a circular brush: */
  rescale = (target_size - fmax ((margin_left + margin_right),
                                 (margin_bottom + margin_top)))
    / fmax (path_width, path_height);

  stroke_top = path_top + margin_top/rescale;
  stroke_left = path_left - margin_left/rescale;
  stroke_right = path_right + margin_right/rescale;
  stroke_bottom = path_bottom - margin_bottom/rescale;

  cairo_new_path (cr);

  cairo_scale (cr, rescale, rescale);

  midpoint_x = (stroke_left + stroke_right)/2;
  midpoint_y = (stroke_bottom + stroke_top)/2;
  cairo_translate (cr, -midpoint_x, -midpoint_y);

  visualid_draw_path (gen, cr);

  cairo_restore (cr);
}

int
visualid_glyph_complexity (VisualID_Glyph *glyph, int recurse)
{
  /* Recurse only if `recurse' is nonzero:
     if positive, recurse only that many levels;
     if negative, recurse all the way down.
  */

  if (!glyph) {
    return 0;
  }

  if (VISUALID_GLYPH_GET_CLASS (glyph)->complexity_fn) {
    return VISUALID_GLYPH_GET_CLASS (glyph)->complexity_fn (glyph, recurse);
  } else {
    g_warning ("No complexity fn in %s--treating it as a single stroke....",
               G_OBJECT_TYPE_NAME (glyph));
    return 1;
  }
}

/*
  Want to mutate a base generator based on:
  - how different the new string is from the base string

  What does the `different' mean?

  The `sameness' is characterised by the length of the string in common.

  string1_len / (string1_start + 1)

  or: 1 - score

  o: ...
 */
/* VisualID_Glyph * */
/* mutate_generator (VisualID_Glyph *base, int level, double amount) */
/* { */
/*   mutator_fn *mutate = get_mutator (base, level, amount); */
/*   VisualID_Glyph *new_gen = mutate (base, level, amount); */
/*   return new_gen; */
/* } */


G_DEFINE_ABSTRACT_TYPE (VisualID_Glyph, visualid_glyph,
                        G_TYPE_INITIALLY_UNOWNED)

enum {
  PROP_RECURSION_LEVEL = 1,
  PROP_PARENT_GLYPH,
  PROP_PARENT_ATTACHMENT,
  PROP_GENERATOR
};

static void
set_property (GObject *object,
              guint property_id,
              const GValue *value,
              GParamSpec *pspec)
{
  VisualID_Glyph *self = VISUALID_GLYPH (object);

  switch (property_id)
    {
    case PROP_RECURSION_LEVEL:
      self->recursion_level = g_value_get_int (value);
      break;

    case PROP_PARENT_GLYPH:
      self->parent_glyph = g_value_get_object (value);
      /* Note the conspicuous absense of a g_object_ref_sink() call:
         no proper reference to the parent is held,
         avoiding reference-cycles.
      */
      break;

    case PROP_PARENT_ATTACHMENT:
      {
        const char *attachment = g_value_get_string (value);

        if (self->parent_glyph && attachment) {
          g_object_set (self->parent_glyph, attachment, self, NULL);
        }
      }
      break;

    case PROP_GENERATOR:
      if (self->generator) {
        g_object_unref (self->generator);
      }
      self->generator = g_value_get_object (value);
      break;

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

static void
get_property (GObject *object,
              guint property_id,
              GValue *value,
              GParamSpec *pspec)
{
  VisualID_Glyph *self = VISUALID_GLYPH (object);

  switch (property_id)
    {
    case PROP_RECURSION_LEVEL:
      g_value_set_int (value, self->recursion_level);
      break;

    case PROP_PARENT_GLYPH:
      g_value_set_object (value, self->parent_glyph);
      break;

    case PROP_GENERATOR:
      g_value_set_object (value, self->generator);
      break;

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

static void
constructed (GObject *self)
{
  VisualID_Glyph *glyph = VISUALID_GLYPH (self);
  GObjectClass *parent_class = G_OBJECT_CLASS (visualid_glyph_parent_class);

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

  if (glyph->generator == NULL) {
    if (glyph->parent_glyph) {
      glyph->generator = g_object_ref (glyph->parent_glyph->generator);
    } else {
      glyph->generator = g_object_new (VISUALID_TYPE_GENERATOR, NULL);
    }
  }

  if (glyph->recursion_level < 0) {
    if (glyph->parent_glyph) {
      glyph->recursion_level = glyph->parent_glyph->recursion_level + 1;
    } else {
      glyph->recursion_level = 0;
    }
  }
}

static void
dispose (GObject *self)
{
  VisualID_Glyph *glyph = VISUALID_GLYPH (self);

  if (glyph->generator) {
    g_object_unref (glyph->generator);
    glyph->generator = NULL;
  }

  /* Note the conspicuous absense of anything like:

         g_object_unref(VISUALID_GLYPH (self)->parent_glyph)

     No proper reference to the parent is held (avoiding reference-cycles),
     so there's no reference to drop.

     It might be tempting to set parent_glyph to NULL either here or
     in a finalize() method, but consider that parent_glyph should
     already be NULL by the end of a child's lifetime,
     because any live parent should hold a reference
     that keeps this child-glyph alive, and a dead parent
     will have already caused this child's (and all of its
     other children's) parent_glyph pointers to have been
     nulled-out before dropping the references and potentially
     allowing the children to (recursively) die themselves.
  */

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

static void
visualid_glyph_class_init (VisualID_GlyphClass *class)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  gobject_class->set_property = set_property;
  gobject_class->get_property = get_property;
  gobject_class->constructed = constructed;
  gobject_class->dispose = dispose;

  /* Pure virtual methods: */
  class->complexity_fn = NULL;
  class->produce = NULL;
  class->mutate = NULL;

  g_object_class_install_property
    (gobject_class, PROP_RECURSION_LEVEL,
     g_param_spec_int ("recursion-level",
                       "Recursion level",
                       "The level of recursion at which this glyph was generated",
                       -1, G_MAXINT,
                       -1, /*^Invalid value to indicate `unset' status;
                             will be reset during or after construction. */
                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property
    (gobject_class, PROP_PARENT_GLYPH,
     g_param_spec_object ("parent-glyph",
                          "Parent glyph",
                          "The glyph to which this glyph is subordinate",
                          VISUALID_TYPE_GLYPH,
                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property
    (gobject_class, PROP_PARENT_ATTACHMENT,
     g_param_spec_string ("parent-attachment",
                          "Parent attachment",
                          "The attribute of the parent-glyph that this fills",
                          "foo",
                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property
    (gobject_class, PROP_GENERATOR,
     g_param_spec_object ("generator",
                          "Generator",
                          "The VisualID_Generator used to produce this glyph",
                          VISUALID_TYPE_GENERATOR,
                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
}

static void
visualid_glyph_init (VisualID_Glyph *self)
{
}
