/*
 * File: dw_ext_iterator.c
 *
 * Copyright (C) 2002 Sebastian Geerken <S.Geerken@ping.de>
 *
 * 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.
 */

/*
 * DwExtIterator is an extension of DwIterator, which stores
 * DwIterator's in a stack, for cases where recursions are not
 * possible.
 *
 * DwWordIterator is a simple wrapper for DwExtIterator, which splits
 * text segments into words, and is used for text search (in
 * findtext.c)
 *
 * todo: In some cases (e.g. for "<i>foo</i>bar"), DwIterator and
 *       DwExtIterator return two words, where there is actually only
 *       one.  In this case, iterator->content.space is set to FALSE.
 *       DwWordIterator currently ignores this flag, but instead
 *       returns two words, instead of concaternating them to one.
 */

#include "dw_ext_iterator.h"
#include "list.h"
#include "misc.h"

/*#define DEBUG_LEVEL 1*/
#include "debug.h"

/*
 * The following two functions are used by a_Dw_extiterator_new, when the
 * passed DwIterator points to a widget. Since a DwExtIterator never returns
 * a widget, the DwIterator has to be corrected, by searching for the next
 * content downwards (within the widget pointed to), forwards, and backwards
 * (in the traversed tree).
 */

/*
 * Search downwards. If from_end is TRUE, start search at the end,
 * otherwise at the beginning.
 * The pararameter indent is only for debugging purposes.
 */
static DwIterator *Dw_ext_iterator_search_downward (DwIterator *it,
                                                    gboolean from_end,
                                                    int indent)
{
   DwIterator *it2, *it3;

   DEBUG_MSG (1, "%*smoving down (%swards) from %s\n",
              indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it));

   g_return_val_if_fail (it->content.type == DW_CONTENT_WIDGET, NULL);
   it2 = a_Dw_widget_iterator (it->content.data.widget, it->mask, from_end);

   if (it2 == NULL) {
      /* Moving downwards failed. */
      DEBUG_MSG (1, "%*smoving down failed\n", indent, "");
      return NULL;
   }

   while (from_end ? a_Dw_iterator_prev (it2) : a_Dw_iterator_next (it2)) {
      DEBUG_MSG (1, "%*sexamining %s\n",
                 indent, "", a_Dw_iterator_text (it2));

      if (it2->content.type == DW_CONTENT_WIDGET) {
         /* Another widget. Search in it downwards. */
         it3 = Dw_ext_iterator_search_downward (it2, from_end, indent + 3);
         if (it3 != NULL) {
            a_Dw_iterator_free (it2);
            return it3;
         }
         /* Else continue in this widget. */
      } else {
         /* Success! */
         DEBUG_MSG (1, "%*smoving down succeeded: %s\n",
                    indent, "", a_Dw_iterator_text (it2));
         return it2;
      }
   }

   /* Nothing found. */
   a_Dw_iterator_free (it2);
   DEBUG_MSG (1, "%*smoving down failed (nothing found)\n", indent, "");
   return NULL;
}

/*
 * Search sidewards. from_end specifies the direction, FALSE means forwards,
 * TRUE means backwards.
 * The pararameter indent is only for debugging purposes.
 */
static DwIterator *Dw_ext_iterator_search_sideward (DwIterator *it,
                                                    gboolean from_end,
                                                    int indent)
{
   DwIterator *it2, *it3;

   DEBUG_MSG (1, "%*smoving %swards from %s\n",
              indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it));

   g_return_val_if_fail (it->content.type == DW_CONTENT_WIDGET, NULL);
   it2 = a_Dw_iterator_clone (it);

   while (from_end ? a_Dw_iterator_prev (it2) : a_Dw_iterator_next (it2)) {
      if (it2->content.type == DW_CONTENT_WIDGET) {
         /* Search downwards in this widget. */
         it3 = Dw_ext_iterator_search_downward (it2, from_end, indent + 3);
         if (it3 != NULL) {
            a_Dw_iterator_free (it2);
            DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n",
                       indent, "", from_end ? "back" : "for",
                       a_Dw_iterator_text (it3));
            return it3;
         }
         /* Else continue in this widget. */
      } else {
         /* Success! */
         DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n",
                    indent, "", from_end ? "back" : "for",
                    a_Dw_iterator_text (it2));
         return it2;
      }
   }

   /* Nothing found, go upwards in the tree (if possible). */
   a_Dw_iterator_free (it2);
   if (it->widget->parent) {
      it2 = a_Dw_widget_iterator (it->widget->parent, it->mask, FALSE);
      while (TRUE) {
         if (!a_Dw_iterator_next(it2)) {
            g_warning ("BUG in DwExtIterator!");
            a_Dw_iterator_free (it2);
            return NULL;
         }
         if (it2->content.type == DW_CONTENT_WIDGET &&
             it2->content.data.widget == it->widget) {
            it3 = Dw_ext_iterator_search_sideward (it2, from_end, indent + 3);
            a_Dw_iterator_free (it2);
            DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n",
                       indent, "", from_end ? "back" : "for",
                       a_Dw_iterator_text (it3));
            return it3;
         }
      }
   }

   /* Nothing found at all. */
   DEBUG_MSG (1, "%*smoving %swards failed (nothing found)\n",
              indent, "", from_end ? "back" : "for");
   return NULL;
}


/*
 * Create a new DwExtIterator from an existing DwIterator. The content of the
 * return value will be the content of "it". If within the widget tree, there
 * is no non-widget content, NULL is returned.
 *
 * NOTES:
 * (i)  If you want to continue using "it", pass a_Dw_iterator_clone (it).
 * (ii) The mask of "it" must include DW_CONTENT_WIDGET, but
 *      a_Dw_ext_iterator_next will never return widgets.
 *
 * TODO: Change in the near future: NULL is by all other functions within this
 * module interpreted as an "empty" iterator, which makes code outside more
 * robust.
 */
DwExtIterator* a_Dw_ext_iterator_new (DwIterator *it)
{
   DwExtIterator *eit;
   DwIterator *it2;
   DwWidget *w;
   int sp;

   DEBUG_MSG (1, "a_Dw_ext_iterator_new: %s\n", a_Dw_iterator_text (it));

   /* If "it" points to a widget, find a near non-widget content,
    * since an DwExtIterator should never return widgets. */
   if (it->content.type == DW_CONTENT_WIDGET) {
      /* The second argument of Dw_ext_iterator_search_downward is
       * actually a matter of taste :-) */
      if ((it2 = Dw_ext_iterator_search_downward (it, FALSE, 3)) ||
          (it2 = Dw_ext_iterator_search_sideward (it, FALSE, 3)) ||
          (it2 = Dw_ext_iterator_search_sideward (it, TRUE, 3))) {
         a_Dw_iterator_free (it);
         it = it2;
      } else {
         /* This may happen, when a page does not contain any
          * non-widget content. */
         DEBUG_MSG (1, "a_Dw_ext_iterator_new got totally helpless!\n");
         a_Dw_iterator_free (it);
         return NULL;
      }
   }

   DEBUG_MSG (1, "  => %s\n", a_Dw_iterator_text (it));

   eit = g_new (DwExtIterator, 1);
   eit->stack_top = 0;
   /* If this widget has parents, we must construct appropiate iterators.
    * todo: There may be a faster way instead of iterating through the
    * parent widgets. */
   for (w = it->widget; w->parent != NULL; w = w->parent)
      eit->stack_top++;
   eit->stack_max = 4;
   while (eit->stack_top >= eit->stack_max)
      eit->stack_max <<= 1;
   eit->stack = g_new (DwIterator*, eit->stack_max);

   /* Construct the iterators. */
   for (w = it->widget, sp = eit->stack_top - 1;
       w->parent != NULL;
       w = w->parent, sp--) {
      eit->stack[sp] = a_Dw_widget_iterator (w->parent, it->mask, FALSE);
      while (TRUE) {
         if (!a_Dw_iterator_next(eit->stack[sp])) {
            g_warning ("BUG in DwExtIterator!");
            return NULL;
         }
         if (eit->stack[sp]->content.type == DW_CONTENT_WIDGET &&
             eit->stack[sp]->content.data.widget == w)
            break;
      }
   }

   eit->stack[eit->stack_top] = it;
   eit->content = it->content;
   return eit;
}


/*
 * This function is similar to a_Dw_ext_iterator_new, but is in many
 * cases faster, since it tries to copy parts of the stack of "base".
 * Used for selection, where the old and new position (which are
 * represented by DwExtIterator) are often quite next to each other.
 * "base" may be NULL.
 */
DwExtIterator* a_Dw_ext_iterator_new_variant (DwExtIterator *base,
                                              DwIterator *it)
{
   /* todo: Not yet implemented, and actually not yet needed very much. */
   return a_Dw_ext_iterator_new (it);
}


/*
 * Move iterator forward and store content in it. Returns TRUE on
 * success.
 */
gboolean a_Dw_ext_iterator_next (DwExtIterator *eit)
{
   DwIterator *it = eit->stack[eit->stack_top];

   if (a_Dw_iterator_next(it)) {
      if (it->content.type == DW_CONTENT_WIDGET) {
         /* Widget: new iterator on stack, to search in this widget. */
         eit->stack_top++;
         a_List_add (eit->stack, eit->stack_top, eit->stack_max);
         eit->stack[eit->stack_top] =
            a_Dw_widget_iterator (it->content.data.widget, it->mask, FALSE);
         return a_Dw_ext_iterator_next (eit);
      } else {
         /* Simply return the content of the iterartor. */
         eit->content = it->content;
         return TRUE;
      }
   } else {
      /* No more data in the top-most widget. */
      if (eit->stack_top > 0) {
         /* Pop iterator from stack, and move to next item in the old one. */
         a_Dw_iterator_free (it);
         eit->stack_top--;
         return a_Dw_ext_iterator_next (eit);
      } else {
         /* Stack is empty. */
         eit->content.type = DW_CONTENT_END;
         return FALSE;
      }
   }
}

/*
 * Move iterator backward and store content in it. Returns TRUE on
 * success.
 */
gboolean a_Dw_ext_iterator_prev (DwExtIterator *eit)
{
   DwIterator *it = eit->stack[eit->stack_top];

   if (a_Dw_iterator_prev(it)) {
      if (it->content.type == DW_CONTENT_WIDGET) {
         /* Widget: new iterator on stack, to search in this widget. */
         eit->stack_top++;
         a_List_add (eit->stack, eit->stack_top, eit->stack_max);
         eit->stack[eit->stack_top] =
            a_Dw_widget_iterator (it->content.data.widget, it->mask, TRUE);
         return a_Dw_ext_iterator_prev (eit);
      } else {
         /* Simply return the content of the iterartor. */
         eit->content = it->content;
         return TRUE;
      }
   } else {
      /* No more data in the top-most widget. */
      if (eit->stack_top > 0) {
         /* Pop iterator from stack, and move to previous item in the
            old one. */
         a_Dw_iterator_free (it);
         eit->stack_top--;
         return a_Dw_ext_iterator_prev (eit);
      } else {
         /* Stack is empty. */
         eit->content.type = DW_CONTENT_START;
         return FALSE;
      }
   }
}

/*
 * Create an exact copy of the iterator, which then can be used
 * independantly of the original one.
 */
DwExtIterator* a_Dw_ext_iterator_clone (DwExtIterator *eit)
{
   int i;
   DwExtIterator *eit2 = g_new (DwExtIterator, 1);

   *eit2 = *eit;
   eit2->stack = g_new (DwIterator*, eit2->stack_max);
   for (i = 0; i <= eit2->stack_top; i++)
      eit2->stack[i] = a_Dw_iterator_clone (eit->stack[i]);

   return eit2;
}

/*
 * Return a value < 0, if first iterator is smaller, > 0, if it is
 * greater, or 0 for equal iterators. It is assumed, that both terators
 * belong to the same widget tree (i.e. they have the same top-level
 * widget).
 */
gint a_Dw_ext_iterator_compare (DwExtIterator *eit1,
                                DwExtIterator *eit2)
{
   int nea = 0;

   while (eit1->stack[nea]->widget == eit2->stack[nea]->widget) {
      if (nea == eit1->stack_top || nea == eit2->stack_top)
         break;
      nea++;
   }
   if (eit1->stack[nea]->widget != eit2->stack[nea]->widget)
      nea--;
   return a_Dw_iterator_compare (eit1->stack[nea], eit2->stack[nea]);
}

/*
 * Free memory of iterator.
 */
void a_Dw_ext_iterator_free (DwExtIterator *eit)
{
   int i;
   for (i = 0; i <= eit->stack_top; i++)
      a_Dw_iterator_free (eit->stack[i]);
   g_free (eit->stack);
   g_free (eit);
}

/*
 * Create a new word iterator. <widget> is the top of the widget
 * tree over which is iterated.
 */
DwWordIterator* a_Dw_word_iterator_new (DwWidget *widget)
{
   DwWordIterator *it;
   DwIterator *it0;

   it = g_new (DwWordIterator, 1);
   it->word = NULL;
   it0 = a_Dw_widget_iterator (widget, DW_CONTENT_TEXT | DW_CONTENT_WIDGET,
                               FALSE);
   it->iterator = a_Dw_ext_iterator_new (it0);
   it->word_splitpos = NULL;
   it->content_hl_start = -1;
   it->content_hl_end = -1;
   return it;
}

/*
 * Move iterator forward and store word in it. Returns TRUE on
 * success.
 */
gboolean a_Dw_word_iterator_next (DwWordIterator *it)
{
   if (it->word) {
      g_free (it->word);
      it->word = NULL;
   }

   while (it->word_splitpos == NULL ||
          it->word_splitpos[2 * (it->word_pos + 1)] == -1) {
      if (it->word_splitpos) {
         g_free (it->word_splitpos);
         it->word_splitpos = NULL;
         it->content_hl_start = -1;
         it->content_hl_end = -1;
      }
      if (!a_Dw_ext_iterator_next (it->iterator))
         return FALSE;
      it->word_splitpos =
         a_Misc_strsplitpos (it->iterator->content.data.text, " \t\n");
      it->word_pos = -1;
   }

   it->word_pos++;
   it->word = a_Misc_strpdup (it->iterator->content.data.text,
                              it->word_splitpos[2 * it->word_pos],
                              it->word_splitpos[2 * it->word_pos + 1]);
   return TRUE;
}


/*
 * Move iterator backward and store word in it. Returns TRUE on
 * success.
 */
gboolean a_Dw_word_iterator_prev (DwWordIterator *it)
{
   if (it->word) {
      g_free (it->word);
      it->word = NULL;
   }

   while (it->word_splitpos == NULL || it->word_pos == 0) {
      if (it->word_splitpos) {
         g_free (it->word_splitpos);
         it->word_splitpos = NULL;
         it->content_hl_start = -1;
         it->content_hl_end = -1;
      }
      if (!a_Dw_ext_iterator_prev (it->iterator))
         return FALSE;
      it->word_splitpos =
         a_Misc_strsplitpos(it->iterator->content.data.text, " \t\n");
      it->word_pos = 0;
      while (it->word_splitpos[2 * it->word_pos] != -1)
         it->word_pos++;
   }

   it->word_pos--;
   it->word = a_Misc_strpdup (it->iterator->content.data.text,
                              it->word_splitpos[2 * it->word_pos],
                              it->word_splitpos[2 * it->word_pos + 1]);
   return TRUE;
}

/*
 * Highlight a part of the current word. Unhighlight the current word
 * by passing -1 as start.
 */
void a_Dw_word_iterator_highlight (DwWordIterator *it,
                                   gint start,
                                   gint end,
                                   DwHighlightLayer layer)
{
   gint new_start, new_end;

   if (start == -1) {
      /* Unhighlight the whole content word.
       * todo: This works incorrect, by unhighlighting the whole
       * current content of the DwExtIterator. Anyway, a correct
       * behavior is not needed. */
      it->content_hl_start = -1;
      it->content_hl_end = -1;
      a_Dw_ext_iterator_unhighlight (it->iterator, layer);
   } else {
      new_start = it->word_splitpos[2 * it->word_pos] + start;
      new_end = it->word_splitpos[2 * it->word_pos] + end;

      if (it->content_hl_start == -1) {
         /* nothing selected yet */
         it->content_hl_start = new_start;
         it->content_hl_end = new_end;
      } else {
         it->content_hl_start = MIN (it->content_hl_start, new_start);
         it->content_hl_end = MAX (it->content_hl_end, new_end);
      }

      a_Dw_ext_iterator_highlight (it->iterator, it->content_hl_start,
                                   it->content_hl_end, layer);
   }
}


DwWordIterator* a_Dw_word_iterator_clone (DwWordIterator *it)
{
   DwWordIterator *it2;

   it2 = g_new (DwWordIterator, 1);
   *it2 = *it;
   it2->iterator = a_Dw_ext_iterator_clone (it->iterator);
   it2->word = it->word ? g_strdup (it->word) : NULL;
   it2->word_splitpos =
      it->word_splitpos ? a_Misc_strsplitposdup (it->word_splitpos) : NULL;
   return it2;
}

void a_Dw_word_iterator_free (DwWordIterator *it)
{
   a_Dw_ext_iterator_free (it->iterator);
   if (it->word)
      g_free (it->word);
   if (it->word_splitpos)
      g_free (it->word_splitpos);
   g_free (it);
}
