/* textwidget.cc
 *
 * Copyright (C) 2001-2002 The gtkmm Development Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <cstring>
#include "textwidget.h"
#include "pangomm/fontdescription.h"

using std::strstr;
using std::strncmp;
using std::strlen;


TextWidget::TextWidget(bool is_source)
{
  set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  set_shadow_type (Gtk::SHADOW_IN);

  m_refTextBuffer = Gtk::TextBuffer::create();
  m_TextView.set_buffer(m_refTextBuffer);
  m_TextView.set_editable(false);
  m_TextView.set_cursor_visible(false);
  add(m_TextView);

  if (is_source)
  {
    m_TextView.set_wrap_mode (Gtk::WRAP_NONE);

    auto refTag = m_refTextBuffer->create_tag("source");
    refTag->property_font() = "Courier 12";

    refTag = m_refTextBuffer->create_tag("comment");
    refTag->property_foreground() = "red";

    refTag = m_refTextBuffer->create_tag("type");
    refTag->property_foreground() = "ForestGreen";

    refTag = m_refTextBuffer->create_tag("string");

    refTag->property_foreground() = "RosyBrown";
    refTag->property_weight() = Pango::WEIGHT_BOLD;


    refTag = m_refTextBuffer->create_tag("control");
    refTag->property_foreground() = "purple";

    refTag = m_refTextBuffer->create_tag("preprocessor");
    refTag->property_style() = Pango::STYLE_OBLIQUE;
    refTag->property_foreground() = "burlywood4";

    refTag = m_refTextBuffer->create_tag("function");
    refTag->property_weight() = Pango::WEIGHT_BOLD;
    refTag->property_foreground() = "DarkGoldenrod4";
  }
  else
  {
    // Make it a bit nicer for text.
    m_TextView.set_wrap_mode (Gtk::WRAP_WORD);
    m_TextView.set_pixels_above_lines(2);
    m_TextView.set_pixels_below_lines(2);

    auto refTag = m_refTextBuffer->create_tag("title");
    refTag->property_font() = "Sans 18";
  }
}


TextWidget::~TextWidget()
{
}

Glib::RefPtr<Gtk::TextBuffer> TextWidget::get_buffer()
{
   return m_refTextBuffer;
}

void TextWidget::wipe()
{
  Gtk::TextBuffer::iterator start, end;
  m_refTextBuffer->get_bounds(start, end);
  m_refTextBuffer->erase(start, end);
}


/* Stupid syntax highlighting.
 *
 * No regex was used in the making of this highlighting.
 * It should only work for simple cases.  This is good, as
 * that's all we should have in the demos.
 */
/* This code should not be used elsewhere, except perhaps as an example of how
 * to iterate through a text buffer.
 */
enum enumStates
{
  STATE_NORMAL,
  STATE_IN_COMMENT
};

static const char* tokens[] =
{
  "/*",
  "\"",
  nullptr
};

static const char* types[] =
{
  "static",
  "const ",
  "void",
  "gint",
  "int ",
  "char ",
  "gchar ",
  "gfloat",
  "float",
  "gint8",
  "gint16",
  "gint32",
  "guint",
  "guint8",
  "guint16",
  "guint32",
  "guchar",
  "glong",
  "gboolean" ,
  "gshort",
  "gushort",
  "gulong",
  "gdouble",
  "gldouble",
  "gpointer",
  "NULL",
  "GList",
  "GSList",
  "FALSE",
  "TRUE",
  "FILE ",
  "GtkObject ",
  "GtkColorSelection ",
  "GtkWidget ",
  "GtkButton ",
  "GdkColor ",
  "GdkRectangle ",
  "GdkEventExpose ",
  "GdkGC ",
  "GdkPixbufLoader ",
  "GdkPixbuf ",
  "GError",
  "size_t",
  nullptr
};

static const char* control[] =
{
  " if ",
  " while ",
  " else",
  " do ",
  " for ",
  "?",
  ":",
  "return ",
  "goto ",
  nullptr
};

typedef const char* constpch;

void
parse_chars (constpch text,
       constpch* end_ptr,
       enumStates* state,
       constpch* tag,
       bool   start)
{
  int i = 0;
  const char* next_token = nullptr;

  /* Handle comments first */
  if(*state == STATE_IN_COMMENT)
  {
    *end_ptr = strstr (text, "*/");
    if (*end_ptr)
    {
      *end_ptr += 2;
      *state = STATE_NORMAL;
      *tag = "comment";
    }

    return;
  }

  *tag = nullptr;
  *end_ptr = nullptr;

  /* check for comment */
  if (!strncmp (text, "/*", 2))
  {
    *end_ptr = strstr (text, "*/");

    if (*end_ptr)
      *end_ptr += 2;
    else
      *state = STATE_IN_COMMENT;
    *tag = "comment";

    return;
  }

  /* check for preprocessor defines */
  if (*text == '#' && start)
  {
    *end_ptr = nullptr;
    *tag = "preprocessor";
    return;
  }

  /* functions */
  if (start && * text != '\t' && *text != ' ' && *text != '{' && *text != '}')
  {
    if (strstr (text, "("))
    {
      *end_ptr = strstr (text, "(");
      *tag = "function";
      return;
    }
  }

  /* check for types */
  for (i = 0; types[i] != nullptr; i++)
    if (!strncmp (text, types[i], strlen (types[i])))
    {
      *end_ptr = text + strlen (types[i]);
      *tag = "type";
      return;
    }

  /* check for control */
  for (i = 0; control[i] != nullptr; i++)
    if (!strncmp (text, control[i], strlen (control[i])))
    {
      *end_ptr = text + strlen (control[i]);
      *tag = "control";
      return;
    }

  /* check for string */
  if (text[0] == '"')
  {
    int maybe_escape = false;

    *end_ptr = text + 1;
    *tag = "string";

    while (**end_ptr != '\000')
    {
      if (**end_ptr == '\"' && !maybe_escape)
      {
        *end_ptr += 1;
        return;
      }

      if (**end_ptr == '\\')
        maybe_escape = true;
      else
        maybe_escape = false;
      *end_ptr += 1;
    }
    return;
  }

  /* not at the start of a tag.  Find the next one. */
  for (i = 0; tokens[i] != nullptr; i++)
  {
    next_token = strstr (text, tokens[i]);
    if (next_token)
    {
      if (*end_ptr)
        *end_ptr = (*end_ptr<next_token)?*end_ptr:next_token;
      else
        *end_ptr = next_token;
    }
  }

  for (i = 0; types[i] != nullptr; i++)
  {
    next_token = strstr (text, types[i]);
    if (next_token)
    {
      if (*end_ptr)
        *end_ptr = (*end_ptr<next_token)?*end_ptr:next_token;
      else
        *end_ptr = next_token;
    }
  }

  for (i = 0; control[i] != nullptr; i++)
  {
    next_token = strstr (text, control[i]);
    if (next_token)
    {
      if (*end_ptr)
        *end_ptr = (*end_ptr<next_token)?*end_ptr:next_token;
      else
        *end_ptr = next_token;
    }
  }

}


/* While not as cool as c-mode, this will do as a quick attempt at highlighting */
void TextWidget::fontify()
{
  enumStates state = STATE_NORMAL;

  Gtk::TextBuffer::iterator iterStart;
  Gtk::TextBuffer::iterator iterEnd;
  m_refTextBuffer->get_bounds(iterStart, iterEnd);
  m_refTextBuffer->apply_tag_by_name("source", iterStart, iterEnd);

  iterStart = m_refTextBuffer->get_iter_at_offset(0);

  auto iterNext = iterStart;
  while(iterNext.forward_line())
  {
    bool start = true;
    const Glib::ustring& str = iterStart.get_text(iterNext);
    const gchar* start_ptr = str.c_str();

    const gchar* end_ptr = nullptr;
    const gchar* tag = nullptr;

    do
    {
      parse_chars (start_ptr, &end_ptr, &state, &tag, start);

      start = false;
      Gtk::TextBuffer::iterator iterTmp;
      if(end_ptr)
      {
        iterTmp = iterStart;
        iterTmp.forward_chars(end_ptr - start_ptr);
      }
      else
      {
        iterTmp = iterNext;
      }

      if(tag)
        m_refTextBuffer->apply_tag_by_name(tag, iterStart, iterTmp);

      iterStart = iterTmp;
      start_ptr = end_ptr;
    }
    while(end_ptr);

    iterStart = iterNext;
  }
}
