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

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

#include "fragment.h"

Template *
template_new (void)
{
  Template *tpl;

  tpl = g_new0 (Template, 1);
  tpl->refs = 1;
  tpl->fragments = g_ptr_array_new ();

  return tpl;
}

Template *
template_ref (Template *tpl)
{
  g_return_val_if_fail (tpl != NULL, NULL);
  g_return_val_if_fail (tpl->refs > 0, NULL);
  
  ++tpl->refs;

  return tpl;
}

void
template_unref (Template *tpl)
{
  g_return_if_fail (tpl != NULL);
  g_return_if_fail (tpl->refs > 0);

  --tpl->refs;
  if (tpl->refs == 0) {
    int i;
    for (i = 0; i < tpl->fragments->len; ++i) {
      Fragment *f = g_ptr_array_index (tpl->fragments, i);
      fragment_unref (f);
    }
    g_ptr_array_free (tpl->fragments, TRUE);
    g_free (tpl);
  }
}

Fragment *
template_get (Template *tpl, int i)
{
  g_return_val_if_fail (tpl != NULL, NULL);
  g_return_val_if_fail (i >= 0, NULL);
  g_return_val_if_fail (i < tpl->fragments->len, NULL);

  return g_ptr_array_index (tpl->fragments, i);
}

void
template_append (Template *tpl, Fragment *f)
{
  g_return_if_fail (tpl != NULL);
  g_return_if_fail (f != NULL);

  g_ptr_array_add (tpl->fragments, fragment_ref (f));
}

gboolean
template_is_blank (Template *tpl)
{
  int i;

  g_return_val_if_fail (tpl != NULL, TRUE);

  for (i = 0; i < tpl->fragments->len; ++i) {
    Fragment *f = g_ptr_array_index (tpl->fragments, i);
    g_assert (f != NULL);

    if (fragment_is_bound (f) 
	&& ! ( fragment_is_start (f) || fragment_is_stop (f)))
      return FALSE;
  }

  return TRUE;
}

double
template_get_metric_error (Template *tpl)
{
  Fragment *a, *b, *c;
  int i, N;
  double tally = 0;

  g_return_val_if_fail (tpl != NULL, TRUE);

  N = tpl->fragments->len;

  /* Sum up the per-fragment errors. */
  for (i = 0; i < N; ++i) {
    Fragment *f = g_ptr_array_index (tpl->fragments, i);
    g_assert (f != NULL);

    tally += f->metric_error;
  }

  for (i = 0; i < N; ++i) {
    a = i>0 ? g_ptr_array_index (tpl->fragments, i-1) : NULL;
    b = g_ptr_array_index (tpl->fragments, i);
    c = i < N-1 ? g_ptr_array_index (tpl->fragments, i+1) : NULL;

    /* Check for widows and orphans. */

    if ((a == NULL || fragment_is_break (a))
	&& fragment_is_text (b)
	&& fragment_is_stop (c)
	&& b->syllables < 3)
      tally += 3.1 - b->syllables;

    if (fragment_is_start (a)
	&& fragment_is_text (b)
	&& (c == NULL || fragment_is_break (c))
	&& b->syllables < 3)
      tally += 3.1 - b->syllables;

    /* In general, we want to end a sentence on a stress. */

    if (a != NULL
	&& fragment_is_text (a)
	&& fragment_is_stop (b)) {
      Meter *tokm = token_meter (a->token);
      if (tokm
	  && *tokm
	  && tokm[strlen(tokm)-1] == METER_UNSTRESSED)
	tally += 0.2;
    }
  }

  return tally;
}

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

Fragment *
template_next_fragment (Template *tpl)
{
  int i;
  double p, next_priority = -1;
  Fragment *next = NULL;

  g_return_val_if_fail (tpl != NULL, NULL);

  for (i = 0; i < tpl->fragments->len; ++i) {
    Fragment *f = g_ptr_array_index (tpl->fragments, i);
    g_assert (f != NULL);

    p = fragment_priority (f);
    if (p > 0 && p > next_priority) {
      next = f;
      next_priority = p;
    }
  }

  return next;
}

Template *
template_solve (Template *tpl,
		Markov *markov,
		double max_metric_error)
{
  Fragment *f;
  GSList *history = NULL, *iter;
  Template *state;

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

  history = g_slist_prepend (history, template_ref (tpl));
  state = tpl;

  while (1) {
  
    f = template_next_fragment (state);

    /* If there is no next fragment, we must be finished. */
    if (f == NULL)
      break;

    //template_spew (state);

    /* A copy of the evolved template is returned. */
    state = fragment_evolve (f, markov, state);

    if (state == NULL 
	|| (max_metric_error >= 0
	    && template_get_metric_error (state) > max_metric_error)) {
      int N  = g_slist_length (history);
      /* backtrack */
      if (N < 2) {
	state = NULL;
	break;
      } else {
	int back = 1 + (random() % (N-1));
	while (back > 0) {
	  template_unref (history->data);
	  history = g_slist_delete_link (history, history);
	  g_assert (history != NULL);
	  --back;
	}
	state = history->data;
      }
    } else {
      /* Save a copy of this state */
      history = g_slist_prepend (history, state);
    }
  }

  /* Clean up our history (the first entry contains state) */
  for (iter = history->next; iter != NULL; iter = iter->next)
    template_unref (iter->data);
  g_slist_free (history);

  return state;      
}

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

void
template_spew (Template *tpl)
{
  int i;

  g_return_if_fail (tpl != NULL);

  g_print("\n----------------------------------------------------\n\n");

  for (i = 0; i < template_length (tpl); ++i) {
    Fragment *f = template_get (tpl, i);
    g_print ("%2d: ", i);
    if (fragment_is_start (f)) {
      g_print ("start");
    } else if (fragment_is_stop (f)) {
      g_print ("stop");
    } else if (fragment_is_break (f)) {
      /* no-op */
    }  else if (fragment_is_bound (f)) {
      g_print ("'%s'", f->token->word);
    } else {
      g_print ("(language syl=%d meter=%s)", f->syllables, f->target_meter);
    }
    g_print ("\n");
  }
}

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

/* Python Type Magic */

typedef struct _PyTemplate PyTemplate;
struct _PyTemplate {
  PyObject_HEAD;
  Template *template;
};

static PyObject *
py_template_length (PyObject *self, PyObject *args)
{
  Template *tpl = template_from_py (self);
  if (tpl == NULL)
    return NULL;

  return Py_BuildValue ("i", template_length (tpl));
}

static PyObject *
py_template_get (PyObject *self, PyObject *args)
{
  Template *tpl = template_from_py (self);
  Fragment *f;
  int i;

  if (! PyArg_ParseTuple (args, "i", &i))
    return NULL;

  f = template_get (tpl, i);

  return fragment_to_py (f);
}

static PyObject *
py_template_append (PyObject *self, PyObject *args)
{
  Template *tpl = template_from_py (self);
  PyObject *py_f;
  Fragment *f;

  if (! PyArg_ParseTuple (args, "O", &py_f))
    return NULL;

  f = fragment_from_py (py_f);

  template_append (tpl, f);

  Py_INCREF (Py_None);
  return Py_None;
}

static PyObject *
py_template_is_blank (PyObject *self, PyObject *args)
{
  Template *tpl = template_from_py (self);
  if (tpl == NULL)
    return NULL;

  return Py_BuildValue ("i", template_is_blank (tpl));
}

static PyObject *
py_template_get_metric_error (PyObject *self, PyObject *args)
{
  Template *tpl = template_from_py (self);
  if (tpl == NULL)
    return NULL;

  return Py_BuildValue ("f", template_get_metric_error (tpl));
}

static PyObject *
py_template_next_fragment (PyObject *self, PyObject *args)
{
  Template *tpl = template_from_py (self);
  if (tpl == NULL)
    return NULL;

  return fragment_to_py (template_next_fragment (tpl));
}

static PyObject *
py_template_solve (PyObject *self, PyObject *args)
{
  Template *tpl = template_from_py (self), *soln;
  Markov *markov;
  PyObject *py_soln, *py_markov;
  double max_metric_error = -1;

  if (! PyArg_ParseTuple (args, "O|d", &py_markov, &max_metric_error))
    return NULL;

  markov = markov_from_py (py_markov);
  soln = template_solve (tpl, markov, max_metric_error);
  py_soln = template_to_py (soln);
  template_unref (soln);

  return py_soln;
}


static PyMethodDef py_template_methods[] = {
  { "length", py_template_length, METH_VARARGS,
    "template length" },
  { "get", py_template_get, METH_VARARGS,
    "get a fragment from the template" },
  { "append", py_template_append, METH_VARARGS,
    "append a fragment to the template" },
  { "is_blank", py_template_is_blank, METH_VARARGS,
    "test if a template is blank" },
  { "get_metric_error", py_template_get_metric_error, METH_VARARGS,
    "get the template's total metric error" },
  { "next_fragment", py_template_next_fragment, METH_VARARGS,
    "get the next fragment that needs to be processed" },
  { "solve", py_template_solve, METH_VARARGS,
    "solve a template" },
  { NULL, NULL, 0, NULL}
};

static PyObject *
py_template_getattr(PyObject *obj, char *name)
{
    return Py_FindMethod(py_template_methods, obj, name);
}

static void
py_template_dealloc(PyObject *self)
{
  Template *f = template_from_py (self);
  template_unref (f);
  PyObject_Del(self);
}

static PyTypeObject py_template_type_info = {
  PyObject_HEAD_INIT(NULL)
  0,
  "Template",
  sizeof(PyTemplate),
  0,
  py_template_dealloc, /*tp_dealloc*/
  NULL,                /*tp_print*/
  py_template_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 *
template_to_py (Template *template)
{
  PyTemplate *py_template;

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

  py_template = PyObject_New(PyTemplate, &py_template_type_info);
  py_template->template = template_ref (template);
  return (PyObject *) py_template;
}

Template *
template_from_py (PyObject *obj)
{
  return ((PyTemplate *) obj)->template;
}

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

PyObject *
py_template_new (PyObject *self, PyObject *args)
{
  Template *tpl = template_new ();
  PyObject *ret = template_to_py (tpl);
  template_unref (tpl);
  return ret;
}


