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

/*
 * wordbag.c
 *
 * Copyright (C) 2003 The Free Software Foundation, Inc.
 *
 * Developed by Jon Trowbridge <trow@gnu.org>
 */

/*
 * 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 CONFIG_H
#include <config.h>
#endif
#include "wordbag.h"

#define N_SYL 25

WordBag *
word_bag_new (void)
{
  WordBag *bag = g_new0 (WordBag, 1);

  bag->refs = 1;
  bag->by_syl = g_new0 (GPtrArray *, N_SYL);

  return bag;
}

WordBag *
word_bag_ref (WordBag *bag)
{
  g_return_val_if_fail (bag != NULL, NULL);
  g_return_val_if_fail (bag->refs > 0, NULL);

  ++bag->refs;
  return bag;
}

void
word_bag_unref (WordBag *bag)
{
  if (bag != NULL) {
    g_return_if_fail (bag->refs > 0);
    --bag->refs;
    if (bag->refs == 0) {
      /* FIXME: clean up properly */
      g_free (bag);
    }
  }
}

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

void
word_bag_add (WordBag *bag, DictionaryWord *word)
{
  GPtrArray *array;

  g_return_if_fail (bag != NULL);
  g_return_if_fail (word != NULL);
  g_return_if_fail (0 <= word->syllables && word->syllables < N_SYL);

  ++bag->total_size;

  array = bag->by_syl[word->syllables];
  if (array == NULL) {
    array = bag->by_syl[word->syllables] = g_ptr_array_new ();
  }

  g_ptr_array_add (array, word);
}

int
word_bag_size (WordBag *bag)
{
  g_return_val_if_fail (bag != NULL, -1);
  
  return bag->total_size;
}

int
word_bag_size_by_syllables (WordBag *bag, int min_syl, int max_syl)
{
  int i, total;

  min_syl = MAX (min_syl, 0);
  max_syl = MIN (max_syl, N_SYL-1);

  g_return_val_if_fail (bag != NULL, -1);
  if (min_syl > max_syl)
    return 0;

  total = 0;
  for (i = min_syl; i <= max_syl; ++i)
    if (bag->by_syl[i])
      total += bag->by_syl[i]->len;

  return total;
}

static void
seed_random ()
{
  static gboolean seeded = FALSE
;  /* lame-ass randomness */
  if (! seeded) {
    srandom (time (NULL));
    seeded = TRUE;
  }
}

DictionaryWord *
word_bag_pick_by_syllables (WordBag *bag, int min_syl, int max_syl)
{
  int i, total, choice;

  g_return_val_if_fail (bag != NULL, NULL);

  min_syl = MAX (min_syl, 0);
  max_syl = MIN (max_syl, N_SYL-1);

  total = word_bag_size_by_syllables (bag, min_syl, max_syl);
  if (total <= 0)
    return NULL;

  seed_random ();
  choice = random() % total;

  for (i = min_syl; i <= max_syl; ++i) {
    if (bag->by_syl[i]) {
      int len = bag->by_syl[i]->len;
      if (choice < len) {
	return g_ptr_array_index (bag->by_syl[i], choice);
      } else {
	choice -= len;
      }
    }
  }

  g_assert_not_reached();
  return NULL;
}

DictionaryWord *
word_bag_pick_by_meter (WordBag *bag,
			int min_syl, int max_syl,
			const Meter *meter,
			gboolean left_match)
{
  GPtrArray *array, *choices;
  int i, j;
  DictionaryWord *pick;

  g_return_val_if_fail (bag != NULL, NULL);
  if (meter == NULL || ! *meter)
    return word_bag_pick_by_syllables (bag, min_syl, max_syl);
  
  min_syl = MAX (min_syl, 0);
  max_syl = MIN (max_syl, N_SYL-1);
  max_syl = MIN (max_syl, strlen (meter));
  
  /* Assemble a list of possible words w/ matching meter. */
  choices = g_ptr_array_new ();
  for (i = min_syl; i <= max_syl; ++i) {
    array = bag->by_syl[i];
    if (array) {
      for (j = 0; j < array->len; ++j) {
	DictionaryWord *word = g_ptr_array_index (array, j);
	if (word->syllables == 0
	    || (word->meter
		&& (left_match ? metric_match_left (meter, word->meter)
		               : metric_match_right (meter, word->meter)))) {
	  g_ptr_array_add (choices, word);
	}
      }
    }
  }

  if (choices->len) {
    pick = g_ptr_array_index (choices, random () % choices->len);
  } else {
    pick = NULL;
  }

  g_ptr_array_free (choices, TRUE);

  return pick;
}

DictionaryWord *
word_bag_pick_by_meter_left (WordBag *bag,
			     int min_syl, int max_syl,
			     const Meter *meter)
{
  return word_bag_pick_by_meter (bag, min_syl, max_syl, meter, TRUE);
}

DictionaryWord *
word_bag_pick_by_meter_right (WordBag *bag,
			      int min_syl, int max_syl,
			      const Meter *meter)
{
  return word_bag_pick_by_meter (bag, min_syl, max_syl, meter, FALSE);
}

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

typedef struct _PyWordBag PyWordBag;
struct _PyWordBag {
  PyObject_HEAD;
  WordBag *bag;
};

static PyObject *
py_word_bag_add (PyObject *self, PyObject *args)
{
  WordBag *bag = word_bag_from_py (self);
  PyObject *obj;
  DictionaryWord *word;

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

  word = dictionary_word_from_py (obj);
  word_bag_add (bag, word);

  Py_INCREF (Py_None);
  return Py_None;
}

static PyObject *
py_word_bag_size (PyObject *self, PyObject *args)
{
  WordBag *bag = word_bag_from_py (self);
  return Py_BuildValue ("i", word_bag_size (bag));
}

static PyObject *
py_word_bag_size_by_syllables (PyObject *self, PyObject *args)
{
  WordBag *bag = word_bag_from_py (self);
  int min_syl, max_syl;
  if (! PyArg_ParseTuple (args, "ii", &min_syl, &max_syl))
    return NULL;

  return Py_BuildValue ("i", 
			word_bag_size_by_syllables (bag, min_syl, max_syl));
}

static PyObject *
py_word_bag_pick_by_syllables (PyObject *self, PyObject *args)
{
  WordBag *bag = word_bag_from_py (self);
  DictionaryWord *word;
  int min_syl, max_syl;
  if (! PyArg_ParseTuple (args, "ii", &min_syl, &max_syl))
    return NULL;

  word = word_bag_pick_by_syllables (bag, min_syl, max_syl);
  return dictionary_word_to_py (word);
}

static PyObject *
py_word_bag_pick_by_meter_left (PyObject *self, PyObject *args)
{
  WordBag *bag = word_bag_from_py (self);
  DictionaryWord *word;
  int min_syl, max_syl;
  char *meter;
  if (! PyArg_ParseTuple (args, "iis", &min_syl, &max_syl, &meter))
    return NULL;

  word = word_bag_pick_by_meter_left (bag, min_syl, max_syl, meter);
  return dictionary_word_to_py (word);
}

static PyObject *
py_word_bag_pick_by_meter_right (PyObject *self, PyObject *args)
{
  WordBag *bag = word_bag_from_py (self);
  DictionaryWord *word;
  int min_syl, max_syl;
  char *meter;
  if (! PyArg_ParseTuple (args, "iis", &min_syl, &max_syl, &meter))
    return NULL;

  word = word_bag_pick_by_meter_right (bag, min_syl, max_syl, meter);
  return dictionary_word_to_py (word);
}

static PyMethodDef py_word_bag_methods[] = {
  { "add", py_word_bag_add, METH_VARARGS,
    "Add a word to this bag." },
  { "size", py_word_bag_size, METH_VARARGS,
    "Get the total size of the word bag." },
  { "size_by_syllables", py_word_bag_size_by_syllables, METH_VARARGS,
    "Get the total number of words in the bag that are within the given "
    "range of syllables." },
  { "pick_by_syllables", py_word_bag_pick_by_syllables, METH_VARARGS,
    "Randomly pick a word in that bag that is in the given syllable range." },
  { "pick_by_meter_left", py_word_bag_pick_by_meter_left, METH_VARARGS,
    "Randomly pick a word in that bag that left-matches the given meter." },
  { "pick_by_meter_right", py_word_bag_pick_by_meter_right, METH_VARARGS,
    "Randomly pick a word in that bag that right-matches the given meter." },

  { NULL, NULL, 0, NULL }
};

static PyObject *
py_word_bag_getattr (PyObject *self, char *name)
{
  return Py_FindMethod (py_word_bag_methods, self, name);
}

static void
py_word_bag_dealloc (PyObject *self)
{
  WordBag *bag = word_bag_from_py (self);
  word_bag_unref (bag);
  PyObject_Del (self);
}

static PyTypeObject py_word_bag_type_info = {
  PyObject_HEAD_INIT(NULL)
  0,
  "WordBag",
  sizeof(PyWordBag),
  0,
  py_word_bag_dealloc,  /*tp_dealloc*/
  NULL,                 /*tp_print*/
  py_word_bag_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 *
word_bag_to_py (WordBag *bag)
{
  PyWordBag *py_bag;

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

  py_bag = PyObject_New (PyWordBag,
			 &py_word_bag_type_info);
  py_bag->bag = word_bag_ref (bag);

  return (PyObject *) py_bag;
}

WordBag *
word_bag_from_py (PyObject *obj)
{
  return ((PyWordBag *) obj)->bag;
}

PyObject *
py_word_bag_new (PyObject *self, PyObject *args)
{
  WordBag *bag = word_bag_new ();
  PyObject *obj = word_bag_to_py (bag);
  word_bag_unref (bag);
  return obj;
}

