/* This is -*- C -*- */
/* vim: set sw=2: */
/* $Id$ */

/*
 * fragment.c
 *
 * Copyright (C) 2003 The Free Software Foundation, Inc.
 *
 */

/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "fragment.h"

#include "markov.h"
#include "template.h"

Fragment *
fragment_new (void)
{
  Fragment *f = g_new0 (Fragment, 1);
  f->refs = 1;
  return f;
}

Fragment *
fragment_ref (Fragment *f)
{
  g_return_val_if_fail (f != NULL, NULL);
  g_return_val_if_fail (f->refs > 0, NULL);
  ++f->refs;
  return f;
}

void
fragment_unref (Fragment *f)
{
  g_return_if_fail (f != NULL);
  g_return_if_fail (f->refs > 0);
  
  --f->refs;
  if (f->refs == 0) {
    g_free (f->target_meter);
    g_free (f->rhyme_key);
    g_free (f);
  }
}

Fragment *
fragment_copy (Fragment *f)
{
  Fragment *c;

  g_return_val_if_fail (f != NULL, NULL);

  c = g_new0 (Fragment, 1);

  c->refs         = 1;
  c->token        = f->token;
  c->metric_error = f->metric_error;
  c->syllables    = f->syllables;
  c->target_meter = g_strdup (f->target_meter);
  c->rhyme_key    = g_strdup (f->rhyme_key);
  c->rhymes_with  = f->rhymes_with;
  c->is_break     = f->is_break;

  return c;
}

double
fragment_priority (Fragment *f)
{
  g_return_val_if_fail (f != NULL, -1);

  if (fragment_is_bound (f) || fragment_is_break (f))
    return 0;

  if (f->rhyme_key != NULL)
    return 100;

  return 10;
}

void
fragment_bind (Fragment *f, Token *t)
{
  g_return_if_fail (f != NULL);

  if (f->token != NULL && t != NULL) {
    g_warning ("Attempt to re-bind fragment ignored ('%s' vs '%s')",
	       f->token->word, t->word);
    return;
  }

  f->token = t;

  if (t) {
    if (f->target_meter && token_meter (t))
      f->metric_error = metric_error_left (token_meter (t), f->target_meter);
    else
      f->metric_error = 0;
  }
}

void
fragment_split (Fragment *f,
		int n,
		Fragment **left_frag,
		Fragment **right_frag)
{
  g_return_if_fail (f != NULL);
  g_return_if_fail (! fragment_is_break (f));
  g_return_if_fail (! fragment_is_bound (f));
  g_return_if_fail (left_frag != NULL);
  g_return_if_fail (right_frag != NULL);
  g_return_if_fail (n >= 0);
  g_return_if_fail (n <= f->syllables);

  *left_frag = fragment_new ();
  *right_frag = fragment_new ();

  (*left_frag)->syllables = n;
  (*right_frag)->syllables = f->syllables - n;
  
  if (f->target_meter) {
    (*left_frag)->target_meter = g_strndup (f->target_meter, n);
    (*right_frag)->target_meter = g_strdup (f->target_meter + n);
  }

  /* Rhymes go to the right */
  (*right_frag)->rhyme_key = g_strdup (f->rhyme_key);
  (*right_frag)->rhymes_with = f->rhymes_with;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

struct EvolveChoiceInfo {
  Token **tokens;
  int N;
};

static void
evolve_choice_cb (Token *t, gpointer user_data)
{
  struct EvolveChoiceInfo *info = user_data;
  info->tokens[info->N] = t;
  ++info->N;
}

Template *
fragment_evolve (Fragment *frag, 
		 Markov *markov,
		 Template *tpl)
{
  Template *evo = NULL;
  MarkovFilter filter = MARKOV_FILTER_INIT;
  Token *choice = NULL;
  GList *xform = NULL, *iter;
  int i, pos, syl, dist_to_start, dist_to_stop;
  gboolean broadcast_rhyme = FALSE;

  g_return_val_if_fail (frag != NULL, NULL);
  g_return_val_if_fail (markov != NULL, NULL);
  g_return_val_if_fail (tpl != NULL, NULL);

  g_return_val_if_fail (! fragment_is_bound (frag), NULL);
  g_return_val_if_fail (! fragment_is_break (frag), NULL);

  /* Find the fragment inside of the template */
  pos = -1;
  for (i = 0; i < template_length (tpl) && pos < 0; ++i) {
    if (template_get (tpl, i) == frag)
      pos = i;
  }
  if (pos < 0) {
    g_warning ("fragment/template mismatch");
    return NULL;
  }

  /* If this is a zero-syllable item, there is nothing to do. */
  if (frag->syllables == 0)
    goto finished;


  /* Find next and prev tokens */

  filter.prev_is = NULL;
  for (i = pos-1; i >= 0; --i) {
    Fragment *f_prev = template_get (tpl, i);
    if (! fragment_is_break (f_prev)) {
      filter.prev_is = f_prev->token;
      break;
    }
  }

  filter.next_is = NULL;
  for (i = pos+1; i < template_length (tpl); ++i) {
    Fragment *f_next = template_get (tpl, i);
    if (! fragment_is_break (f_next)) {
      filter.next_is = f_next->token;
      break;
    }
  }

  /* Find the closest adjacent start/stop, and disable them as
     choices if we are too close. */

  dist_to_start = 0;
  for (i = pos-1; i >= 0; --i) {
    Fragment *f = template_get (tpl, i);
    if (! fragment_is_break (f)) {
      if (fragment_is_start (f))
	break;
      else if (fragment_is_bound (f))
	++dist_to_start;
      else {
	dist_to_start = 666;
	break;
      }
    }
  }

  dist_to_stop = 0;
  for (i = pos+1; i < template_length (tpl); ++i) {
    Fragment *f = template_get (tpl, i);
    if (! fragment_is_break (f)) {
      if (fragment_is_stop (f))
	break;
      else if (fragment_is_bound (f))
	++dist_to_stop;
      else {
	dist_to_stop = 666;
	break;
      }
    }
  }

  filter.no_start = dist_to_stop < 4;
  filter.no_stop  = dist_to_start < 4;

  filter.left_rooted = TRUE;

  if (filter.next_is && ! filter.prev_is)
    filter.left_rooted = FALSE;

  if (frag->rhymes_with) {
    filter.rhymes_with = frag->rhymes_with;
    filter.minimum_rhyme_type = RHYME_TRUE;
    filter.left_rooted = FALSE;
  } else if (frag->rhyme_key) {
    filter.left_rooted = FALSE;
  }

  filter.min_syllables = 0;
  filter.max_syllables = frag->syllables;

#if 0
  if (frag->target_meter) {
    filter.target_meter = frag->target_meter;
    //filter.minimize_metric_error = TRUE;
  }
#endif

  if (frag->target_meter) {
    Token *choices[20];
    struct EvolveChoiceInfo info;
    int i;
    double min_err = 1000;
    
    info.tokens = choices;
    info.N = 0;

    markov_choose_many (markov, &filter, 20, 
			evolve_choice_cb, &info,
			NULL, NULL);

    if (info.N == 0)
      goto cleanup;

    /* shuffle choices */
    for (i = 0; i < info.N-1; ++i) {
      int j = i + 1 + (random () % (info.N-i-1));
      Token *tmp;
      tmp = choices[j];
      choices[j] = choices[i];
      choices[i] = tmp;
    }

    if (token_is_start (choices[0]) || token_is_stop (choices[0])) {
      choice = choices[0];
    } else {

      /* Find choice that minimizes error. */
      for (i = 0; i < info.N; ++i) {
	double err;
	Meter *tokm = token_meter (choices[i]);
	if (tokm) {
	  if (filter.left_rooted) 
	    err = metric_error_left (tokm, frag->target_meter);
	  else
	    err = metric_error_right (tokm, frag->target_meter);
	  if (err < min_err) {
	    choice = choices[i];
	    min_err = err;
	  }
	}
      }
    }
  } else {
    choice = markov_choose (markov, &filter, FALSE, NULL, NULL);
  }
  
  if (choice == NULL) /* OK, that didn't work out... */
    goto cleanup;

  syl = token_syllables (choice);

  if (syl == frag->syllables) {
    Fragment *f = fragment_copy (frag);
    fragment_bind (f, choice);
    xform = g_list_append (xform, f);
  } else {
    Fragment *left = NULL, *right = NULL;

    fragment_split (frag,
		    filter.left_rooted ? syl : frag->syllables - syl,
		    &left, &right);
    g_assert (left != NULL);
    g_assert (right != NULL);

    fragment_bind (filter.left_rooted ? left : right, choice);

    xform = g_list_append (xform, left);
    xform = g_list_append (xform, right);
  }

  /* Inform fragments w/ the same rhyme key what token they need to
     rhyme with. */
  if (frag->rhyme_key && ! frag->rhymes_with)
    broadcast_rhyme = TRUE;
  
 finished:
  evo = template_new ();
  for (i = 0; i < template_length (tpl); ++i) {
    Fragment *f = template_get (tpl, i);

    if (i != pos) {
      /* Copy the rest of the template across unchanged. */
      Fragment *cpy = fragment_copy (f);
      template_append (evo, cpy);
      fragment_unref (cpy);
    } else {
      /* Replace the item by the item(s) in the xform list. */
      for (iter = xform; iter != NULL; iter = iter->next)
	template_append (evo, iter->data);
    }
  }

  if (choice != NULL && broadcast_rhyme) {
    for (i = 0; i < template_length (evo); ++i) {
      Fragment *f = template_get (evo, i);
      if (f->rhyme_key && !strcmp (frag->rhyme_key, f->rhyme_key))
	f->rhymes_with = choice;
    }
  }

 cleanup:
  for (iter = xform; iter != NULL; iter = iter->next)
    fragment_unref (iter->data);
  g_list_free (xform);
  
  return evo;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

/* Python Type Magic */

typedef struct _PyFragment PyFragment;
struct _PyFragment {
  PyObject_HEAD;
  Fragment *fragment;
};

static PyObject *
py_fragment_is_break (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_from_py (self);

  return Py_BuildValue ("i", fragment_is_break (f));
}

static PyObject *
py_fragment_is_start (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_from_py (self);

  return Py_BuildValue ("i", fragment_is_start (f));
}

static PyObject *
py_fragment_is_stop (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_from_py (self);

  return Py_BuildValue ("i", fragment_is_stop (f));
}

static PyObject *
py_fragment_get_token (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_from_py (self);

  return token_to_py (f->token);
}

static PyObject *
py_fragment_get_metric_error (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_from_py (self);

  return Py_BuildValue ("f", f->metric_error);
}

static PyMethodDef py_fragment_methods[] = {
  { "is_break", py_fragment_is_break, METH_VARARGS, "foo" },
  { "is_start", py_fragment_is_start, METH_VARARGS, "foo" },
  { "is_stop",  py_fragment_is_stop,  METH_VARARGS, "foo" },
  { "get_token", py_fragment_get_token, METH_VARARGS, "foo" },
  { "get_metric_error", py_fragment_get_metric_error, METH_VARARGS, "foo" },
  {NULL, NULL, 0, NULL}
};

static PyObject *
py_fragment_getattr(PyObject *obj, char *name)
{
    return Py_FindMethod(py_fragment_methods, obj, name);
}

static void
py_fragment_dealloc(PyObject *self)
{
  Fragment *f = fragment_from_py (self);
  fragment_unref (f);
  PyObject_Del(self);
}

static PyTypeObject py_fragment_type_info = {
  PyObject_HEAD_INIT(NULL)
  0,
  "Fragment",
  sizeof(PyFragment),
  0,
  py_fragment_dealloc, /*tp_dealloc*/
  NULL,                /*tp_print*/
  py_fragment_getattr, /*tp_getattr*/
  NULL,                /*tp_setattr*/
  NULL,                /*tp_compare*/
  NULL,                /*tp_repr*/
  NULL,                /*tp_as_number*/
  NULL,                /*tp_as_sequence*/
  NULL,                /*tp_as_mapping*/
  NULL,                /*tp_hash */
};

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

PyObject *
fragment_to_py (Fragment *fragment)
{
  if (fragment)
    fragment_ref (fragment);
  return fragment_to_py_steal (fragment);
}

PyObject *
fragment_to_py_steal (Fragment *fragment)
{
  PyFragment *py_fragment;

  if (fragment == NULL) {
    Py_INCREF (Py_None);
    return Py_None;
  }

  py_fragment = PyObject_New(PyFragment, &py_fragment_type_info);
  py_fragment->fragment = fragment;
  return (PyObject *) py_fragment;

}

Fragment *
fragment_from_py (PyObject *obj)
{
  return ((PyFragment *) obj)->fragment;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

PyObject *
py_fragment_new_start (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_new ();
  fragment_bind (f, token_get_start ());
  return fragment_to_py_steal (f);
}

PyObject *
py_fragment_new_stop (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_new ();
  fragment_bind (f, token_get_stop ());
  return fragment_to_py_steal (f);
}

PyObject *
py_fragment_new_break (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_new ();
  f->is_break = TRUE;
  return fragment_to_py_steal (f);
}

PyObject *
py_fragment_new_token (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_new ();

  return fragment_to_py_steal (f);
}

PyObject *
py_fragment_new_language (PyObject *self, PyObject *args)
{
  Fragment *f = fragment_new ();
  int syl;
  char *meter, *rhyme_key;

  if (! PyArg_ParseTuple (args, "iss", &syl, &meter, &rhyme_key))
    return NULL;

  f->syllables = syl;
  if (meter && *meter)
    f->target_meter = g_strdup (meter);
  if (rhyme_key && *rhyme_key)
    f->rhyme_key = g_strdup (rhyme_key);

  return fragment_to_py_steal (f);
}


