/*
 *  textarea.c -- portable gui library text area, platform independent
 */

#include "gui.h"


static void reflow_from(struct gui_ta *doc, unsigned int c, unsigned int w,
            gui_window_id window_id, unsigned int x_offset, unsigned int y_offset, int newline);

#ifdef DEBUG
static int document_ok(const struct gui_ta *doc);
#endif


/*
 *  gui_ta -- return a new document with the specified width
 */

struct gui_ta *gui_ta(unsigned long width)
{
  struct gui_ta *doc = malloc(sizeof(struct gui_ta));

  #ifdef DEBUG
  printf("gui_ta: width = %li\n", width);
  fflush(stdout);
  #endif

  if (doc == NULL) return NULL;

  doc->width = width;
  doc->height = doc->contents = doc->styles = 0;
  doc->content = malloc(0);
  doc->style = malloc(0);

  if (doc->content == NULL || doc->style == NULL) return NULL;

  assert(document_ok(doc));

  #ifdef DEBUG
  printf("gui_ta: doc = %p\n", doc);
  fflush(stdout);
  #endif

  return doc;
}


/*
 *  gui_ta_add_space -- add a space of the specified height to the document
 *                      and return success
 */

long gui_ta_add_space(struct gui_ta *doc, unsigned long height)
{
  struct gui_ta_content *con = malloc(sizeof(struct gui_ta_content));
  struct gui_ta_content *(*new_content)[];

  #ifdef DEBUG
  printf("gui_ta_add_space: doc = %p, height = %li\n", doc, height);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  new_content = realloc(doc->content, (doc->contents + 1) * sizeof(struct gui_ta_content *));
  if (con == NULL || new_content == NULL) return 0;

  con->type = CONTENT_SPACE;
  con->y0 = doc->height;
  con->y1 = doc->height + height;

  (*new_content)[doc->contents] = con;
  doc->content = new_content;
  doc->height += height;
  doc->contents++;

  assert(document_ok(doc));

  return 1;
}


/*
 *  gui_ta_add_paragraph -- add a paragraph of text
 */

int gui_ta_add_paragraph(struct gui_ta *doc, unsigned long leading,
      unsigned long base, wchar_t *text, unsigned long style, unsigned long align)
{
  unsigned int words = 1;				/* number of words added */
  unsigned int w;					/* word number */
  wchar_t *text_pos = text;				/* current position in text */
  struct gui_ta_content *con = malloc(sizeof(struct gui_ta_content));
  struct gui_ta_content *(*new_content)[];
  struct gui_ta_word *word;			/* current word structure */
  unsigned int len;			/* length of current word */
  wchar_t *space;			/* position of next space */

  #ifdef DEBUG
  printf("gui_ta_add_paragraph: doc = %p, leading = %li, base = %li, style = %li\n", doc, leading, base, style);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  new_content = realloc(doc->content, (doc->contents + 1) * sizeof(struct gui_ta_content *));
  if (con == NULL || new_content == NULL) return 0;

  /* set up new paragraph */
  con->type = CONTENT_PARAGRAPH;
  con->y0 = doc->height;
  con->leading = leading;
  con->base = base;
  con->align = align;

  /* count words and allocate space */
  while (*text_pos) if (*text_pos++ == ' ') words++;

  con->words = words;
  con->word = calloc(words, sizeof(struct gui_ta_word *));
  if (con->word == NULL) return 0;

  /* add the words */
  text_pos = text;
  for (w = 0; w < words; w++)
  {
    space = wcschr(text_pos, ' ');
    len = (space ? space - text_pos : wcslen(text_pos));

    word = malloc(gui_SIZEOF_GUI_TA_WORD(len + 1));
    if (word == NULL) return 0;

    wcsncpy(word->text, text_pos, len);
    word->text[len] = 0;

    word->width = gui_ta_text_width(doc, word->text, style);
    word->style = style;

    (*con->word)[w] = word;

    text_pos += len + 1;
  }

  doc->height = gui_ta_reformat_paragraph(doc, con, doc->height, doc->width, 0, 0);

  /* add paragraph to document */
  (*new_content)[doc->contents] = con;
  doc->content = new_content;
  doc->contents++;

  assert(document_ok(doc));

  return 1;
}


/*
 *  gui_ta_get_style -- get a style number, creating a new style if required
 */

unsigned long gui_ta_get_style(struct gui_ta *doc, unsigned long xsize,
      unsigned long ysize, unsigned long fcolour, unsigned long bcolour, char *font)
{
  unsigned int s;			/* style number */
  struct gui_ta_style *style;			/* current style structure */
  struct gui_ta_style *(*new_style)[];		/* new style pointer list */
  wchar_t const space[] = { 0x0020, 0 };

  #ifdef DEBUG
  printf("gui_ta_get_style: doc = %p, font = '%s'\n", doc, font);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  /* search for existing style */
  for (s = 0; s < doc->styles; s++)
  {
    style = (*doc->style)[s];

    if (style->xsize == xsize &&
        style->ysize == ysize &&
        style->fcolour == fcolour &&
        style->bcolour == bcolour &&
        strcmp(style->font, font) == 0) return s;
  }

  /* create a new style */
  style = malloc(gui_SIZEOF_GUI_TA_STYLE(strlen(font) + 1));
  if (style == NULL) return 0;

  new_style = realloc(doc->style, (doc->styles + 1) * sizeof(struct gui_ta_style *));
  if (new_style == NULL) return 0;

  style->xsize = xsize;
  style->ysize = ysize;
  style->fcolour = fcolour;
  style->bcolour = bcolour;
  strcpy(style->font, font);
  gui_ta_fill_style(style);

  (*new_style)[doc->styles] = style;
  doc->style = new_style;
  doc->styles++;

  style->space = gui_ta_text_width(doc, space, doc->styles - 1);

  assert(document_ok(doc));

  return doc->styles - 1;
}


/*
 *  gui_ta_render -- render document to the screen
 */

void gui_ta_render(struct gui_ta *doc, unsigned long x, unsigned long y,
      unsigned int invalid[], struct gui_ta_selection *sel)
{
  unsigned int c;
  unsigned int w;
  unsigned int base;
  const signed int x0 = (invalid[0] < x ? 0 : invalid[0] - x);
  const unsigned int y0 = (invalid[1] < y ? 0 : invalid[1] - y);
  const signed int x1 = (invalid[2] < x ? 0 : invalid[2] - x);
  const unsigned int y1 = (invalid[3] < y ? 0 : invalid[3] - y);
  struct gui_ta_content *con;
  struct gui_ta_word *word;

  #ifdef DEBUG
  printf("gui_ta_render: doc = %p\n", doc);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  /* loop through document contents */
  for (c = 0; c < doc->contents; c++)
  {
    con = (*doc->content)[c];

    /* render content if it lies in invalid area */
    if (!(con->y0 > y1 || con->y1 < y0))
    {
      switch (con->type)
      {
        case CONTENT_SPACE:
          break;

        case CONTENT_PARAGRAPH:
          base = con->base;

          /* loop through paragraph words */
          for (w = 0; w < con->words; w++)
          {
            word = (*con->word)[w];;

            /* continue if word is above or to right of invalid area */
            if (word->y < y0 || word->box_x0 > x1) continue;

            /* exit if word is below invalid area */
            if (word->y - con->leading > y1) return;

            assert((w < con->words - 1) || ((w == con->words - 1) && word->end));

            /* render word if it lies in invalid area */
            if (word->box_x1 >= x0)
              gui_ta_render_text(doc, word->text, word->style, x + word->x, y + word->y - base,
                        x + word->box_x0, y + word->y - con->leading, x + word->box_x1, y + word->y,
                        sel &&
                        (c > sel->start.content || (c == sel->start.content && w >= sel->start.word)) &&
                        (c < sel->end.content || (c == sel->end.content && w <= sel->end.word)));
          }
          break;
      }
    }
  }
}


/*
 *  gui_ta_position_xy -- update position from coordinates
 */

void gui_ta_position_xy(struct gui_ta *doc, struct gui_ta_position *pos,
      signed long x, unsigned long y)
{
  unsigned int c = 0;		/* content number */
  unsigned int w = 0;		/* word number */
  unsigned int word_y;		/* y position of word */
  struct gui_ta_content *con;	/* current content structure */

  #ifdef DEBUG
  printf("gui_ta_position_xy: doc = %p\n", doc);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  if (doc->contents == 0) return;

  if (y > doc->height)
    c = doc->contents - 1;
  else
    while (y > (*doc->content)[c]->y1) c++;

  if (x < 0) x = 0;

  pos->content = c;
  con = (*doc->content)[c];

  switch (con->type)
  {
    case CONTENT_SPACE:
      pos->word = pos->offset = pos->x = pos->y = 0xffffffffu;
      break;

    case CONTENT_PARAGRAPH:
      word_y = con->y0 + con->leading;
      while (y > word_y) word_y += con->leading;
      if (word_y > con->y1) word_y = con->y1;
      while ((*con->word)[w]->y != word_y) w++;

      while (x > (signed long) ((*con->word)[w]->x + (*con->word)[w]->width))
      {
        if (w == con->words - 1 || (*con->word)[w + 1]->y != word_y)
        {
          pos->word = w;
          pos->offset = wcslen((*con->word)[w]->text);
          pos->x = (*con->word)[w]->x + (*con->word)[w]->width;
          pos->y = word_y;
          return;
        }
        w++;
      }

      pos->word = w;
      pos->y = word_y;
      x -= (*con->word)[w]->x;
      pos->offset = gui_ta_position_x(doc, (*con->word)[w]->text,
                                      (*con->word)[w]->style, &x);
      pos->x = (*con->word)[w]->x + x;
      break;
  }
}


/*
 *  gui_ta_reformat -- change the width of the document
 */

void gui_ta_reformat(struct gui_ta *doc, unsigned long width)
{
  unsigned int c;			/* content number */
  unsigned int y = 0;			/* current y coordinate */
  unsigned int h;			/* height of space */
  struct gui_ta_content *con;		/* current content structure */

  #ifdef DEBUG
  printf("gui_ta_reformat: doc = %p\n", doc);
  fflush(stdout);
  #endif

  for (c = 0; c < doc->contents; c++)
  {
    con = (*doc->content)[c];

    switch (con->type)
    {
      case CONTENT_SPACE:
        h = con->y1 - con->y0;
        con->y0 = y;
        con->y1 = y = y + h;
        break;

      case CONTENT_PARAGRAPH:
        y = gui_ta_reformat_paragraph(doc, con, y, width, 0, 0);
        break;
    }
  }

  doc->width = width;
  doc->height = y;

  assert(document_ok(doc));
}


/*
 *  gui_ta_reformat_paragraph
 */

unsigned int gui_ta_reformat_paragraph(struct gui_ta *doc,
      struct gui_ta_content *con, unsigned long y, unsigned long width,
      unsigned long *y0_ret, unsigned long *y1_ret)
{
  unsigned int x = 0;			/* current x coordinate */
  unsigned int x1 = 0;			/* current x coordinate excluding last space */
  unsigned int w;			/* word number */
  unsigned int w_start = 0;		/* word at start of line */
  unsigned int w_align;			/* word when aligning */
  unsigned int space;
  unsigned int y0, y1;
  unsigned int change = 0;
  struct gui_ta_word *word;		/* current word structure */
  struct gui_ta_word *next_word;

  if (y0_ret) y0 = *y0_ret;
  if (y1_ret) y1 = *y1_ret;

  con->y0 = y;
  y += con->leading;

  for (w = 0; w < con->words; w++)
  {
    word = (*con->word)[w];

    if ((x + word->width > width) && (x != 0))
    {
      space = width - x1;
      switch (con->align)
      {
        case ALIGN_LEFT:
          for (w_align = w_start; w_align < w; w_align++)
            (*con->word)[w_align]->x = (*con->word)[w_align]->lx;
          break;

        case ALIGN_CENTRE:
          space /= 2;
        case ALIGN_RIGHT:
          for (w_align = w_start; w_align < w; w_align++)
            (*con->word)[w_align]->x = (*con->word)[w_align]->lx + space;
          break;

        case ALIGN_FULL:
          (*con->word)[w_start]->x = (*con->word)[w_start]->lx;
          for (w_align = 1; w_align < w - w_start; w_align++)
            (*con->word)[w_start + w_align]->x = (*con->word)[w_start + w_align]->lx +
                                                 w_align * space / (w - w_start - 1);
          break;
       }
      (*con->word)[w_start]->start = 1;
      (*con->word)[w - 1]->end = 1;
      x = x1 = 0;
      y += con->leading;
      w_start = w;
    }

    if (change == 0 && (x != word->lx || y != word->y))
    {
      if (y < word->y) { y0 = y - con->leading; y1 = word->y; }
      else { y0 = word->y - con->leading; y1 = y; }
      change = 1;
    }
    else if (change == 1 && (x != word->lx || y != word->y)) y1 = y < word->y ? word->y : y;
    else if (change == 1 && x == word->lx && y == word->y)   change = 2;

    word->lx = x;
    word->y = y;
    word->start = word->end = 0;
    x1 = x + word->width;
    x = x1 + (*doc->style)[word->style]->space;
  }

  space = width - x1;
  switch (con->align)
  {
    case ALIGN_LEFT:
    case ALIGN_FULL:
      for (w_align = w_start; w_align < con->words; w_align++)
        (*con->word)[w_align]->x = (*con->word)[w_align]->lx;
      break;

    case ALIGN_CENTRE:
      space /= 2;
    case ALIGN_RIGHT:
      for (w_align = w_start; w_align < con->words; w_align++)
        (*con->word)[w_align]->x = (*con->word)[w_align]->lx + space;
      break;
  }
  (*con->word)[w_start]->start = 1;
  word->end = 1;

  next_word = (*con->word)[0];
  for (w = 0; w < con->words; w++)
  {
    word = next_word;
    next_word = (*con->word)[w + 1];
    word->box_x0 = word->start ? 0 : word->x;
    word->box_x1 = word->end ? width : next_word->x;
  }

  con->y1 = y;

  if (y0_ret) *y0_ret = y0 < *y0_ret ? y0 : *y0_ret;
  if (y1_ret) *y1_ret = y1 < *y1_ret ? *y1_ret : y1;

  return y;
}



/*
 *  gui_ta_insert_char -- insert a character at the specified position
 */

void gui_ta_insert_char(struct gui_ta *doc, struct gui_ta_position *pos,
      wchar_t text, gui_window_id window_id,
      unsigned long x_offset, unsigned long y_offset)
{
  struct gui_ta_content *con = (*doc->content)[pos->content];
  struct gui_ta_word *word = (*con->word)[pos->word];

  #ifdef DEBUG
  printf("gui_ta_insert_char: doc = %p, char = %04x\n", doc, text);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  word = realloc(word, gui_SIZEOF_GUI_TA_WORD(wcslen(word->text) + 2));
  if (word == NULL) return;

  memmove(word->text + pos->offset + 1, word->text + pos->offset,
          (wcslen(word->text + pos->offset) + 1) * sizeof(wchar_t));
  word->text[pos->offset] = text;
  word->width = gui_ta_text_width(doc, word->text, word->style);

  (*con->word)[pos->word] = word;

  reflow_from(doc, pos->content, pos->word, window_id, x_offset, y_offset, 0);
}


/*
 *  gui_ta_insert_split -- split a word into two words at the specified position
 */

void gui_ta_insert_split(struct gui_ta *doc, struct gui_ta_position *pos,
      gui_window_id window_id, unsigned long x_offset, unsigned long y_offset)
{
  struct gui_ta_content *con = (*doc->content)[pos->content];
  struct gui_ta_word *word = (*con->word)[pos->word];
  struct gui_ta_word *left = malloc(gui_SIZEOF_GUI_TA_WORD(pos->offset + 1));
  struct gui_ta_word *right = malloc(gui_SIZEOF_GUI_TA_WORD(wcslen(word->text) - pos->offset + 1));
  struct gui_ta_word *(*new_word)[] = malloc((con->words + 1) * sizeof(struct gui_ta_word *));

  #ifdef DEBUG
  printf("gui_ta_insert_split: doc = %p\n", doc);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  if (new_word == NULL || left == NULL || right == NULL) return;

  memcpy(*new_word, *con->word, pos->word * sizeof(struct gui_ta_word *));
  (*new_word)[pos->word] = left;
  (*new_word)[pos->word + 1] = right;
  memcpy(*new_word + pos->word + 2, *con->word + pos->word + 1,
         (con->words - pos->word - 1) * sizeof(struct gui_ta_word *));

  wcsncpy(left->text, word->text, pos->offset);
  left->text[pos->offset] = 0;
  left->style = word->style;
  left->width = gui_ta_text_width(doc, left->text, left->style);
  left->x = word->x;
  left->lx = word->lx;
  left->y = word->y;

  wcscpy(right->text, word->text + pos->offset);
  right->style = word->style;
  right->width = gui_ta_text_width(doc, right->text, right->style);
  right->x = right->y = 0;
  left->lx = 0;

  free(word);
  free(con->word);
  con->word = new_word;
  con->words++;

  if (pos->word && (*con->word)[pos->word - 1]->y != left->y)
  {
    left->x = left->y = 0;
    reflow_from(doc, pos->content, pos->word - 1, window_id, x_offset, y_offset, 0);
  }
  else
    reflow_from(doc, pos->content, pos->word, window_id, x_offset, y_offset, 0);
}


/*
 *  gui_ta_insert_newline -- split a paragraph into two paragraphs at the specified position
 */

void gui_ta_insert_newline(struct gui_ta *doc, struct gui_ta_position *pos,
      gui_window_id window_id, unsigned long x_offset, unsigned long y_offset)
{
  struct gui_ta_content *con = (*doc->content)[pos->content];
  struct gui_ta_content *new_con = malloc(sizeof(struct gui_ta_content));
  struct gui_ta_word *word = (*con->word)[pos->word];
  struct gui_ta_word *left = malloc(gui_SIZEOF_GUI_TA_WORD(pos->offset + 1));
  struct gui_ta_word *right = malloc(gui_SIZEOF_GUI_TA_WORD(wcslen(word->text) - pos->offset + 1));
  struct gui_ta_word *(*new_word)[] = malloc((con->words - pos->word) * sizeof(struct gui_ta_word *));
  struct gui_ta_content *(*content)[];

  #ifdef DEBUG
  printf("gui_ta_insert_newline: doc = %p\n", doc);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  content = realloc(doc->content, (doc->contents + 1) * sizeof(struct gui_ta_content *));

  if (new_con == NULL || new_word == NULL || left == NULL || right == NULL || content == NULL) return;

  doc->content = content;

  new_con->type = con->type;
  new_con->y0 = 0;
  new_con->y1 = con->y1;
  new_con->leading = con->leading;
  new_con->base = con->base;
  new_con->align = con->align;
  new_con->words = con->words - pos->word;
  new_con->word = new_word;

  (*new_word)[0] = right;
  memcpy(*new_word + 1, *con->word + pos->word + 1,
         (con->words - pos->word - 1) * sizeof(struct gui_ta_word *));

  wcsncpy(left->text, word->text, pos->offset);
  left->text[pos->offset] = 0;
  left->style = word->style;
  left->width = gui_ta_text_width(doc, left->text, left->style);
  left->x = word->x;
  left->y = word->y;

  wcscpy(right->text, word->text + pos->offset);
  right->style = word->style;
  right->width = gui_ta_text_width(doc, right->text, right->style);
  right->x = right->y = 0;

  free(word);

  (*con->word)[pos->word] = left;
  con->words = pos->word + 1;

  memmove(*doc->content + pos->content + 2, *doc->content + pos->content + 1,
          (doc->contents - pos->content - 1) * sizeof(struct gui_ta_content *));
  (*doc->content)[pos->content + 1] = new_con;
  doc->contents++;

  if (pos->word && (*con->word)[pos->word - 1]->y != left->y)
  {
    left->x = left->lx = left->y = 0;
    reflow_from(doc, pos->content, pos->word - 1, window_id, x_offset, y_offset, 1);
  }
  else
    reflow_from(doc, pos->content, pos->word, window_id, x_offset, y_offset, 1);
}


/*
 *  reflow_from -- helper function to reflow document after insertion / deletion
 */

void reflow_from(struct gui_ta *doc, unsigned int c0, unsigned int w0,
             gui_window_id window_id, unsigned int x_offset, unsigned int y_offset, int newline)
{
  unsigned int y;
  unsigned long y0, y1;
  unsigned int old_y1;
  unsigned int c, w;
  struct gui_ta_content *con = (*doc->content)[c0];

  y0 = (*con->word)[w0]->y - con->leading;
  y1 = (*con->word)[w0]->y;

  old_y1 = con->y1;
  y = gui_ta_reformat_paragraph(doc, con, con->y0, doc->width, &y0, &y1);
  if (y == old_y1 && !newline)
  {
    assert(document_ok(doc));
    gui_refresh_box(window_id, x_offset, y_offset + y0,
                    x_offset + doc->width, y_offset + y1);
    return;
  }

  if (c0 + 1 < doc->contents)
  {
    con = (*doc->content)[c0 + 1];

    if (newline)
    {
      y = gui_ta_reformat_paragraph(doc, con, y, doc->width, 0, &y1);

      if (c0 + 2 == doc->contents)
      {
        doc->height = y;
        assert(document_ok(doc));
        gui_refresh_box(window_id, x_offset, y_offset + y0,
                        x_offset + doc->width, y_offset + y1);

        #ifdef DEBUG
        printf("reflow_from: last content\n");
        fflush(stdout);
        #endif

        return;
      }

      con = (*doc->content)[c0 + 2];
    }

    if (y != con->y0)
    {
      signed int move = y - con->y0;

      gui_move_box(window_id, x_offset, y_offset + con->y0, x_offset + doc->width,
                   y_offset + doc->height, x_offset, y_offset + y);

      for (c = c0 + 1 + newline; c < doc->contents; c++)
      {
        con = (*doc->content)[c];
        con->y0 += move;
        con->y1 += move;

        switch (con->type)
        {
          case CONTENT_SPACE:
            break;

          case CONTENT_PARAGRAPH:
            for (w = 0; w < con->words; w++) (*con->word)[w]->y += move;
            break;
        }
      }
      doc->height += move;
    }
  }
  else
  {
    doc->height = y;
  }

  assert(document_ok(doc));
  gui_refresh_box(window_id, x_offset, y_offset + y0,
                  x_offset + doc->width, y_offset + y1);

  #ifdef DEBUG
  printf("reflow_from: full\n");
  fflush(stdout);
  #endif
}


/*
 *  gui_ta_position_cwo -- update position from content, word, offset
 */

void gui_ta_position_cwo(struct gui_ta *doc, struct gui_ta_position *pos)
{
  struct gui_ta_word *word = (*(*doc->content)[pos->content]->word)[pos->word];

  #ifdef DEBUG
  printf("gui_ta_position_cwo: doc = %p\n", doc);
  fflush(stdout);
  #endif

  assert(document_ok(doc));

  pos->x = word->x + gui_ta_position_o(doc, word->text, word->style, pos->offset);
  pos->y = word->y;
}


/*
 *  gui_ta_delete -- delete a block of text
 *
 *  There are two possible cases:
 *
 *  1. The block to be deleted lies within one word
 *     => remove those characters
 *  2. The block is between different words
 *     => remove intervening words, and merge words
 */

void gui_ta_delete(struct gui_ta *doc, struct gui_ta_position *start,
      struct gui_ta_position *end, gui_window_id window_id,
      unsigned long x_offset, unsigned long y_offset)
{
  unsigned int c = start->content;
  unsigned int w = start->word;
//  struct gui_ta_content *con = (*doc->content)[c];
  struct gui_ta_content *start_con = (*doc->content)[c];
  struct gui_ta_word *start_word = (*start_con->word)[w];
  struct gui_ta_content *end_con = (*doc->content)[end->content];
  struct gui_ta_word *end_word = (*end_con->word)[end->word];
  void *mem;

  assert(end->content > start->content ||
         (end->content == start->content &&
          (end->word > start->word || (end->word == start->word && end->offset > start->offset))));

  /* case 1 */
  if (start_word == end_word)
  {
    wcscpy(start_word->text + start->offset, end_word->text + end->offset);
    start_word->width = gui_ta_text_width(doc, start_word->text, start_word->style);
    reflow_from(doc, start->content, start->word, window_id, x_offset, y_offset, 0);
    return;
  }

  /* case 2 */
  mem = realloc(start_word, gui_SIZEOF_GUI_TA_WORD(start->offset + wcslen(end_word->text + end->offset) + 1));
  if (mem == NULL) return;
  start_word = (*start_con->word)[start->word] = mem;

  if (start_con != end_con)
  {
    mem = realloc(start_con->word, (start->word + end_con->words - end->word) *
                                   sizeof(struct gui_ta_word *));
    if (mem == NULL) return;
    start_con->word = mem;
  }

  wcscpy(start_word->text + start->offset, end_word->text + end->offset);
  start_word->width = gui_ta_text_width(doc, start_word->text, start_word->style);

  memcpy(*start_con->word + start->word + 1, *end_con->word + end->word + 1,
         (end_con->words - end->word - 1) * sizeof(struct gui_ta_word *));
  start_con->words = start->word + end_con->words - end->word;

  if (start_con == end_con)
  {
    reflow_from(doc, start->content, start->word, window_id, x_offset, y_offset, 0);
    return;
  }
  start_con->y1 = end_con->y1;

  free(end_con->word);
  free(end_con);

  memcpy(*doc->content + start->content + 1, *doc->content + end->content + 1,
         (doc->contents - end->content - 1) * sizeof(struct gui_ta_content *));
  doc->contents = start->content + doc->contents - end->content;

  mem = realloc(doc->content, doc->contents * sizeof(struct gui_ta_content *));
  if (mem) doc->content = mem;

  reflow_from(doc, start->content, start->word, window_id, x_offset, y_offset, 0);
}

/*
  while (c < end->content || w < end->word)
  {
    w++;
    if (w == con->words)
    {
      if (c > start->content && c < end->content)
      {
        free(con->word);
        free(con);
      }
      c++;
      w = 0;
      con = (*doc->content)[c];
    }
    free((*con->word)[w]);
  }
*/


/*
 *  gui_ta_remove -- free up resources associated with a document
 */

extern void gui_ta_remove(struct gui_ta *doc)
{
  unsigned int c, w, s;
  struct gui_ta_content *con;

  for (c = 0; c < doc->contents; c++)
  {
    con = (*doc->content)[c];

    switch (con->type)
    {
      case CONTENT_PARAGRAPH:
      {
        for (w = 0; w < con->words; w++)
          free((*con->word)[w]);

        break;
      }
      case CONTENT_SPACE:
        ;
    }

    free(con);
  }

  for (s = 0; s < doc->styles; s++)
  {
    gui_ta_empty_style((*doc->style)[s]);
    free((*doc->style)[s]);
  }
}


/*
 *  gui_ta_align
 */

void gui_ta_align(struct gui_ta *doc, unsigned int content, unsigned int align,
                  gui_window_id window_id, unsigned int x_offset, unsigned int y_offset)
{
  struct gui_ta_content *con = (*doc->content)[content];
  con->align = align;

  gui_ta_reformat_paragraph(doc, con, con->y0, doc->width, 0, 0);
  gui_refresh_box(window_id, x_offset, y_offset + con->y0,
                  x_offset + doc->width, y_offset + con->y1);
}


/*
 *  gui_ta_export -- export a document
 */

void gui_ta_export(struct gui_ta *doc, const char *path)
{
  unsigned int c, w, s;
  FILE *fp = fopen(path, "w");
  struct gui_ta_style *style;
  struct gui_ta_content *con;
  struct gui_ta_word *word;

  wfputc(0x74785457, fp);
  wfputc(doc->contents, fp);

  for (c = 0; c < doc->contents; c++)
  {
    con = (*doc->content)[c];

    switch (con->type)
    {
      case CONTENT_SPACE:
        wfputc(0x63617053, fp);
        break;

      case CONTENT_PARAGRAPH:
      {
        wfputc(0x61726150, fp);
        wfputc(con->leading, fp);
        wfputc(con->base, fp);

        for (w = 0; w < con->words; w++)
        {
          word = (*con->word)[w];

          if ((w == 0) || (word->style != s))
          {
/*             style = (*doc->style)[s]; */
/*             fprintf(fp, "    <font>%s</font>\n", style->font); */
/*             fprintf(fp, "    <xsize>%d</xsize>\n", style->xsize); */
/*             fprintf(fp, "    <ysize>%d</ysize>\n", style->ysize); */
/*             fprintf(fp, "    <fcolour>%x</fcolour>\n", style->fcolour); */
/*             fprintf(fp, "    <bcolour>%x</bcolour>\n", style->bcolour); */
            wfputc(word->style, fp);
          }
          s = word->style;

          wfputs(word->text, fp);
        }

        break;
      }
    }
  }

  fclose(fp);
}


#ifdef DEBUG

/*
 *  document_ok -- check document integrity
 */

int document_ok(const struct gui_ta *doc)
{
  unsigned int c;
  unsigned int w;
  const struct gui_ta_content *con;
  const struct gui_ta_word *word;

/*   printf("%p [width %i, height %i, contents %i, styles %i]\n", */
/*          doc, doc->width, doc->height, doc->contents, doc->styles); */

  for (c = 0; c < doc->contents; c++)
  {
    con = (*doc->content)[c];

/*     printf("%i %p\n", c, con); */

    if (c && con->y0 != (*doc->content)[c - 1]->y1)
    {
      printf("content y coordinates don't match %i %li, %i %li\n", c - 1, (*doc->content)[c - 1]->y1,
                                                         c, con->y0);
      fflush(stdout);
      return 0;
    }

    if (con->y1 > doc->height)
    {
      printf("con->y1 %li greater than doc->height %li\n", con->y1, doc->height);
      fflush(stdout);
      return 0;
    }

    switch (con->type)
    {
      case CONTENT_SPACE:
/*         printf("<space %i to %i>\n", */
/*                  con->y0, con->y1); */
        break;

      case CONTENT_PARAGRAPH:
/*         printf("<paragraph %i to %i, leading %i, words %i>\n", */
/*                   con->y0, con->y1, con->leading, con->words); */
        for (w = 0; w < con->words; w++)
        {
          word = (*con->word)[w];
/*           printf(" <word at %i %i, width %i, style %i, box %i %i>\n", */
/*                  word->x, word->y, word->width, word->style, word->box_x0, word->box_x1); */

          if (word->x > doc->width)
          {
            printf("word->x %li greater than doc->width %li\n", word->x, doc->width);
            fflush(stdout);
            return 0;
          }
          if ((word->x != 0) && (word->x + word->width > doc->width))
          {
            printf("word->x %li + word->width %li greater than doc->width %li\n", word->x,
                   word->width, doc->width);
            fflush(stdout);
            return 0;
          }
          if (word->y < con->y0 || word->y > con->y1)
          {
            printf("word->y %li not within content y %li %li\n", word->y, con->y0, con->y1);
            fflush(stdout);
            return 0;
          }
          if (word->style >= doc->styles)
          {
            printf("word->style %li greater than doc->styles %li\n", word->style, doc->styles);
            fflush(stdout);
            return 0;
          }
        }
        break;

      default:
        printf("unknown document content type %li %li\n", c, con->type);
        fflush(stdout);
        return 0;
    }
  }

  return 1;
}

#endif
