#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#define PRE_PROCESS(text, string, t) do {        \
    char ch;                                     \
    subst_to_spaces (trim (text, &ch), &string); \
    *(char *)(text + strlen (text)) = ch;        \
    spaces_to_space (string);                    \
    t = string;                                  \
} while (0)

#define FINALIZE_STRING() do { \
    *dest = '\0';              \
    *string = buf;             \
    return;                    \
} while (0)

#define IS_WHITESPACE(ws) \
    (ws == ' ' || ws == '\f' || ws == '\n' || ws == '\r' || ws == '\t')

#define SAVE_STRING(str, size, t)        \
    Newx (str, size + 1, char);          \
                                         \
    strncpy (str, t, size);              \
    *(str + size) = '\0';                \
                                         \
    t += size;                           \
                                         \
    EXTEND (SP, 1);                      \
    PUSHs (sv_2mortal(newSVpv(str, 0))); \
                                         \
    Safefree (str);

static const char *
trim (const char *text, char *ch)
{
    char *p;

    p = (char *)text + strlen (text);
    while (p > text && IS_WHITESPACE (*(p - 1)))
      p--;
    *ch = *p;
    *p = '\0';

    p = (char *)text;
    while (IS_WHITESPACE (*p))
      p++;
    return p;
}

static void
subst_to_spaces (const char *text, char **string)
{
  if (strpbrk (text, "\f\n\r\t"))
    {
      const char *src = text;
      char *dest;
      char *buf, *ws;
      const char *eot = text + strlen (text);
      Newx (buf, strlen (text) + 1, char);
      dest = buf;
      while ((ws = strpbrk (src, "\f\n\r\t")))
        {
          char *p = ws;
          strncpy (dest, src, ws - src);
          dest += ws - src;
          src  += ws - src;
          switch (*ws)
            {
              case '\f': p++; break; /* Form Feed */
              case '\n': p++; break; /* LF */
              case '\r': p++; break; /* CR */
              case '\t': p++; break; /* Tab */
              default:     abort (); /* never reached */
            }
          if (*ws == '\r' && *p == '\n') /* CRLF */
            p++;
          src += p - ws;
          if (p < eot)
            *dest++ = ' ';
          else
            FINALIZE_STRING ();
        }
      if (src < eot)
        {
          strncpy (dest, src, eot - src);
          dest += eot - src;
          src  += eot - src;
        }
      FINALIZE_STRING ();
    }
  else
    *string = savepv (text);
}

static void
spaces_to_space (char *string)
{
    char *s, *p;
    s = p = string;

    while (*p)
      {
        while (*p == ' ' && *(p + 1) == ' ')
          p++;
        *s++ = *p++;
      }
    *s = '\0';
}

static unsigned long
calc_average (unsigned long length, unsigned int wrap_at)
{
    unsigned int i;
    i = length / wrap_at;
    if (length % wrap_at != 0)
      i++;
    return ceil ((double)length / (double)i);
}

MODULE = Text::Wrap::Smart::XS                PACKAGE = Text::Wrap::Smart::XS

void
xs_exact_wrap (text, wrap_at)
      const char *text;
      unsigned int wrap_at;
    PROTOTYPE: $$
    INIT:
      unsigned long average, length, offset;
      char *string = NULL;
      char *eot, *t;
    PPCODE:
      PRE_PROCESS (text, string, t);
      length = strlen (t);
      eot = t + length;

      if (length == 0)
        {
          Safefree (string);
          XSRETURN_EMPTY;
        }

      average = calc_average (length, wrap_at);

      for (offset = 0; offset < length && *t; offset += average)
        {
          char *str;
          const unsigned long size = average > (eot - t) ? (eot - t) : average;

          SAVE_STRING (str, size, t);
        }

      Safefree (string);

void
xs_fuzzy_wrap (text, wrap_at)
      const char *text;
      unsigned int wrap_at;
    PROTOTYPE: $$
    INIT:
      unsigned long average, length;
      char *string = NULL;
      char *t;
    PPCODE:
      PRE_PROCESS (text, string, t);
      length = strlen (t);

      if (length == 0)
        {
          Safefree (string);
          XSRETURN_EMPTY;
        }

      average = calc_average (length, wrap_at);

      while (*t)
        {
          unsigned int spaces = 0;
          long remaining = average;
          unsigned long size;
          char *str, *s;

          /* calculate pos and size of each chunk */
          for (s = t; *s;)
            {
              char *ptr = strchr (s, ' ');
              if (ptr)
                {
                  unsigned long next_space;
                  char *n, *p;
                  /* advance pos to space */
                  remaining -= ptr - s;
                  p = s = ptr;
                  /* skip space */
                  p++;
                  /* advance pos after space */
                  remaining -= p - s;
                  /* get distance to next space */
                  n = strchr (p, ' ');
                  next_space = n ? n - p : 0;
                  /* pos and size complete */
                  if (next_space > remaining && spaces >= 1)
                    break;
                  else if (remaining <= 0)
                    break;
                  spaces++;
                  s = p;
                }
              else
                s += strlen (s);
            }
          size = s - t;
          if (!size)
            break;

          SAVE_STRING (str, size, t);

          if (*t)
            t++;
        }

      Safefree (string);