#include <gtk/gtk.h>
#include "sql_editor_fe.h"

//------------------------------------------------------------------------------
SqlEditorFE::SqlEditorFE()
:
_be(NULL),
_errors_count(0)
{
  _editor = scintilla_new();
  _sci    = SCINTILLA(_editor);
  _widget = Glib::wrap(_editor);

  send_editor(SCI_SETCARETSTICKY, true);
  send_editor(SCI_SETSCROLLWIDTHTRACKING, true);
  send_editor(SCI_SETSCROLLWIDTH, 800); 
  send_editor(SCI_SETMARGINTYPEN, 0, SC_MARGIN_NUMBER);
  send_editor(SCI_SETMARGINWIDTHN, 0, 35);

  // markers margin
  send_editor(SCI_SETMARGINWIDTHN, 1, 16);

  // folder margin
  send_editor(SCI_SETMARGINWIDTHN, 2, 16);
  send_editor(SCI_SETMARGINMASKN, 2, SC_MASK_FOLDERS);
  send_editor(SCI_SETMARGINSENSITIVEN, 2, 1);
  send_editor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_BOXMINUS);
  send_editor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_BOXPLUS);
  send_editor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE);
  send_editor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNER);
  send_editor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_BOXPLUSCONNECTED);
  send_editor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_BOXMINUSCONNECTED);
  send_editor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNER);
  for (int n= 25; n < 32; ++n) // markers 25..31 are reserved for folding
  {
    send_editor(SCI_MARKERSETFORE, n, 0xffffff);
    send_editor(SCI_MARKERSETBACK, n, 0x000000);
  }

  // init markers & indicators for highlighting of sql errors
  {
    send_editor(SCI_INDICSETFORE, 0, 0x0000ff);
    send_editor(SCI_INDICSETUNDER, 0, 1);
    send_editor(SCI_INDICSETSTYLE, 0, INDIC_SQUIGGLE);

    send_editor(SCI_INDICSETFORE, 1, 0x0000ff);
    send_editor(SCI_INDICSETUNDER, 1, 1);
    send_editor(SCI_INDICSETSTYLE, 1, INDIC_ROUNDBOX);

    send_editor(SCI_MARKERSETBACK, 0, 0x0000ff);
  }

  init_lexer();

  gtk_signal_connect(GTK_OBJECT(_editor), SCINTILLA_NOTIFY, GTK_SIGNAL_FUNC(&SqlEditorFE::notify_signal), this);

  _widget->show();
}

//------------------------------------------------------------------------------
void SqlEditorFE::set_focus()
{
  send_editor(SCI_GRABFOCUS);
  send_editor(SCI_SETFOCUS, true);
  _widget->activate();
  _widget->grab_focus();
}

//------------------------------------------------------------------------------
void SqlEditorFE::notify_signal(GtkWidget *w, gint wParam, gpointer lParam, SqlEditorFE *editor)
{
  Scintilla::SCNotification *event = reinterpret_cast<Scintilla::SCNotification *>(lParam);
  editor->notify(event);
}

void SqlEditorFE::notify(Scintilla::SCNotification *event)
{
  switch (event->nmhdr.code)
  {
    case SCN_MODIFIED:
      {
      const int mod_type = event->modificationType;
      if (mod_type & SC_MOD_INSERTTEXT || mod_type & SC_MOD_DELETETEXT)
      {
        _background_action_timer_conn.disconnect();
        _background_action_timer_conn= Glib::signal_timeout().connect(
          sigc::mem_fun(this, &SqlEditorFE::on_background_action_timer),
          2000); //! get value from wb options
        _text_changed_signal.emit();
      }
      if (mod_type & SC_MOD_CHANGEFOLD)
        fold_changed(event->line, event->foldLevelNow, event->foldLevelPrev);
      }
      break;

    case SCN_MARGINCLICK:
      {
      if (event->margin == 2)
        margin_click(event->position, event->modifiers);
      }
      break;
  }
}

//------------------------------------------------------------------------------
Gtk::Widget& SqlEditorFE::widget()
{
  return *_widget;
}

//------------------------------------------------------------------------------
void SqlEditorFE::set_text(const std::string& text)
{
  if (text != get_text())
  {
    const int pos = send_editor(SCI_GETCURRENTPOS);
    send_editor(SCI_SETTEXT, 0, (sptr_t)text.c_str());
    send_editor(SCI_GOTOPOS, pos);
    send_editor(SCI_SCROLLCARET);
  }
	  
  check_sql();
}

//------------------------------------------------------------------------------
std::string SqlEditorFE::get_text()
{
  char *buffer(0);
  const int length = send_editor(SCI_GETLENGTH);
  if ( length > 0 )
  {
    buffer = new char[length+1];
    try
    {
      send_editor(SCI_GETTEXT, length+1, (sptr_t)buffer);
      send_editor(SCI_SETSAVEPOINT);
    }
    catch (...)
    {
      delete[] buffer;
      buffer = 0;
    }
  }

  return buffer ? buffer : "";
}

//------------------------------------------------------------------------------
std::string SqlEditorFE::get_selected_text()
{
  int start= send_editor(SCI_GETSELECTIONSTART);
  int end= send_editor(SCI_GETSELECTIONEND);
  std::string text= get_text();
  return text.substr(start, end-start);
}

//------------------------------------------------------------------------------
void SqlEditorFE::set_savepoint()
{
    send_editor(SCI_SETSAVEPOINT);
}

//------------------------------------------------------------------------------
bool SqlEditorFE::get_modify()
{
    return (bool)send_editor(SCI_GETMODIFY);
}

//------------------------------------------------------------------------------
void SqlEditorFE::scroll_to(const int line, const std::string& msg)
{
  send_editor(SCI_GOTOLINE, line);
}

//------------------------------------------------------------------------------
void SqlEditorFE::be(Sql_editor* be)
{
  _be= be;
  if (_be)
  {
    _be->sql_parser_err_cb(sigc::mem_fun(this, &SqlEditorFE::process_sql_error));//!
  }
  return;
}

bool SqlEditorFE::on_background_action_timer()
{
  _background_action_cb();
  return false;
}

void SqlEditorFE::check_sql()
{
  reset_sql_errors();
  if (_be)
  {
    _be->sql(get_text());
    _be->check_sql();
  }
}

int SqlEditorFE::reset_sql_errors()
{
  _errors_count = 0;
  int length= send_editor(SCI_GETLENGTH);

  send_editor(SCI_SETINDICATORCURRENT, 0);
  send_editor(SCI_INDICATORCLEARRANGE, 0, length);

  send_editor(SCI_SETINDICATORCURRENT, 1);
  send_editor(SCI_INDICATORCLEARRANGE, 0, length);

  send_editor(SCI_MARKERDELETEALL, -1);

  return 0;
}

int SqlEditorFE::process_sql_error(const int line, const int err_tok_line_pos, const int err_tok_len, const std::string &err_msg)
{
  int line_start_pos= send_editor(SCI_POSITIONFROMLINE, line-1);

  send_editor(SCI_SETINDICATORCURRENT, 0);
  send_editor(SCI_INDICATORFILLRANGE, line_start_pos+err_tok_line_pos, err_tok_len);

  send_editor(SCI_SETINDICATORCURRENT, 1);
  send_editor(SCI_INDICATORFILLRANGE, line_start_pos+err_tok_line_pos, err_tok_len);

  send_editor(SCI_MARKERADD, line-1, 0);

  ++_errors_count;

  return 0;
}

//------------------------------------------------------------------------------
sptr_t SqlEditorFE::send_editor(unsigned int msg, uptr_t uparam, sptr_t sparam)
{
  return scintilla_send_message(_sci, msg, uparam, sparam);
}

static const char* keywords =
"absolute action add admin after aggregate "
"alias all allocate alter and any are array as asc "
"assertion at authorization "
"before begin binary bit blob boolean both breadth by "
"call cascade cascaded case cast catalog char character "
"check class clob close collate collation column commit "
"completion connect connection constraint constraints "
"constructor continue corresponding create cross cube current "
"current_date current_path current_role current_time current_timestamp "
"current_user cursor cycle "
"data date day deallocate dec decimal declare default "
"deferrable deferred delete depth deref desc describe descriptor "
"destroy destructor deterministic dictionary diagnostics disconnect "
"distinct domain double drop dynamic "
"each edit else end end-exec equals escape every except "
"exception exec execute external "
"false fetch first float for foreign found from free full "
"function "
"general get global go goto grant group grouping "
"having host hour "
"identity if ignore immediate in indicator initialize initially "
"inner inout input insert int integer intersect interval "
"into is isolation iterate "
"join "
"key "
"language large last lateral leading left less level like "
"limit local localtime localtimestamp locator "
"map match minute modifies modify module month "
"names national natural nchar nclob new next no none "
"not null numeric "
"object of off old on only open operation option "
"or order ordinality out outer output "
"pad parameter parameters partial path postfix precision prefix "
"preorder prepare preserve primary "
"prior privileges procedure public "
"read reads real recursive ref references referencing relative "
"restrict result return returns revoke right "
"role rollback rollup routine row rows "
"savepoint schema scroll scope search second section select "
"sequence session session_user set sets size smallint some| space "
"specific specifictype sql sqlexception sqlstate sqlwarning start "
"state statement static structure system_user "
"table temporary terminate than then time timestamp "
"timezone_hour timezone_minute to trailing transaction translation "
"treat trigger true "
"under union unique unknown "
"unnest update usage user using "
"value values varchar variable varying view "
"when whenever where with without work write "
"year "
"zone";

//------------------------------------------------------------------------------
void SqlEditorFE::init_lexer()
{
  //!send_editor(SCI_SETLEXER, SCLEX_SQL);
  //!send_editor(SCI_SETLEXER, SCLEX_MYSQL);
  send_editor(SCI_SETLEXERLANGUAGE, NULL, (sptr_t)"mysql");
  send_editor(SCI_SETSTYLEBITS, send_editor(SCI_GETSTYLEBITSNEEDED));

  send_editor(SCI_SETKEYWORDS, 0, (sptr_t)keywords);
  //!send_editor(SCI_SETKEYWORDS, 1, (sptr_t)"begin end delimiter proc comment");

  send_editor(SCI_SETPROPERTY, (uptr_t)"fold", (long int)"1");
  send_editor(SCI_SETPROPERTY, (uptr_t)"fold.compact", (long int)"0");
  send_editor(SCI_SETPROPERTY, (uptr_t)"fold.comment", (long int)"1");
  send_editor(SCI_SETPROPERTY, (uptr_t)"fold.preprocessor", (long int)"1");
  //!send_editor(SCI_SETPROPERTY, (uptr_t)"fold.sql.only.begin", (long int)"1");

  send_editor(SCI_STYLESETFORE, SCE_SQL_DEFAULT, 0x808080);
  send_editor(SCI_STYLESETFORE, SCE_SQL_COMMENT, 0x007F00);
  send_editor(SCI_STYLESETFORE, SCE_SQL_COMMENTLINE, 0x007F00);
  send_editor(SCI_STYLESETFORE, SCE_SQL_COMMENTDOC, 0x7F7F7F);
  send_editor(SCI_STYLESETFORE, SCE_SQL_NUMBER, 0x007F7F);
  send_editor(SCI_STYLESETFORE, SCE_SQL_WORD, 0x7F0000);
  send_editor(SCI_STYLESETBOLD, SCE_SQL_WORD, 1);
  send_editor(SCI_STYLESETFORE, SCE_SQL_STRING, 0x7F007F);
  send_editor(SCI_STYLESETFORE, SCE_SQL_CHARACTER, 0x7F007F);
  send_editor(SCI_STYLESETFORE, SCE_SQL_SQLPLUS, 0x808080);
  send_editor(SCI_STYLESETFORE, SCE_SQL_SQLPLUS_PROMPT, 0x007F00);//,$(font.monospace),back:0xE0FFE0,eolfilled
  send_editor(SCI_STYLESETBOLD, SCE_SQL_OPERATOR, 1);
  //send_editor(SCI_STYLESETFORE, SCE_SQL_IDENTIFIER, );
  //send_editor(SCI_STYLESETFORE, SCE_SQL_SQLPLUS_COMMENT, :0x000000,$(font.monospace),back:0xE0C0E0,eolfilled
  send_editor(SCI_STYLESETFORE, SCE_SQL_SQLPLUS_COMMENT, 0x007F00);
  send_editor(SCI_STYLESETFORE, SCE_SQL_COMMENTLINEDOC, 0x007F00);
  send_editor(SCI_STYLESETFORE, SCE_SQL_WORD2, 0xB00040);
  //send_editor(SCI_STYLESETFORE, SCE_SQL_17=fore:0x3060A0,$(font.code.comment.doc)
  //send_editor(SCI_STYLESETFORE, SCE_SQL_18=fore:0x804020,$(font.code.comment.doc)
  //send_editor(SCI_STYLESETFORE, SCE_SQL_19=fore:0x4B0082
  //send_editor(SCI_STYLESETFORE, SCE_SQL_20=fore:0xB00040
  //send_editor(SCI_STYLESETFORE, SCE_SQL_21=fore:0x8B0000
  //send_editor(SCI_STYLESETFORE, SCE_SQL_22=fore:0x800080
}

//------------------------------------------------------------------------------
bool SqlEditorFE::margin_click(int position, int modifiers)
{
  int lineClick = send_editor(SCI_LINEFROMPOSITION, position);
  //    send_editor(SCI_GETFOLDLEVEL, lineClick) & SC_FOLDLEVELHEADERFLAG);
  if (modifiers & SCMOD_SHIFT)
    fold_close_all();
  else if (modifiers & SCMOD_CTRL)
    fold_open_all();
  else if (send_editor(SCI_GETFOLDLEVEL, lineClick) & SC_FOLDLEVELHEADERFLAG)
  {
    if (modifiers & SCMOD_SHIFT)
    {
      // Ensure all children visible
      send_editor(SCI_SETFOLDEXPANDED, lineClick, 1);
      expand(lineClick, true, true, 100);
    }
    else if (modifiers & SCMOD_CTRL)
    {
      if (send_editor(SCI_GETFOLDEXPANDED, lineClick))
      {
        // Contract this line and all children
        send_editor(SCI_SETFOLDEXPANDED, lineClick, 0);
        expand(lineClick, false, true, 0);
      }
      else
      {
        // Expand this line and all children
        send_editor(SCI_SETFOLDEXPANDED, lineClick, 1);
        expand(lineClick, true, true, 100);
      }
    }
    else
    {
      // Toggle this line
      send_editor(SCI_TOGGLEFOLD, lineClick);
    }
  }
  return true;
}

void SqlEditorFE::fold_changed(int line, int levelNow, int levelPrev)
{
  if (levelNow & SC_FOLDLEVELHEADERFLAG)
    send_editor(SCI_SETFOLDEXPANDED, line, 1);
  else if (levelPrev & SC_FOLDLEVELHEADERFLAG)
  {
    if (!send_editor(SCI_GETFOLDEXPANDED, line))
    {
      // Removing the fold from one that has been contracted so should expand
      // otherwise lines are left invisible with no way to make them visible
      expand(line, true, false, 0, levelPrev);
    }
  }
}

void SqlEditorFE::expand(int &line, bool doExpand, bool force, int visLevels, int level)
{
  int lineMaxSubord = send_editor(SCI_GETLASTCHILD, line, level);
  line++;
  while (line <= lineMaxSubord)
  {
    if (force)
    {
      if (visLevels > 0)
        send_editor(SCI_SHOWLINES, line, line);
      else
        send_editor(SCI_HIDELINES, line, line);
    }
    else if (doExpand)
        send_editor(SCI_SHOWLINES, line, line);

    int levelLine = level;
    if (levelLine ==-1)
      levelLine = send_editor(SCI_GETFOLDLEVEL, line);
    if (levelLine & SC_FOLDLEVELHEADERFLAG)
    {
      if (force)
      {
        if (visLevels > 1)
          send_editor(SCI_SETFOLDEXPANDED, line, 1);
        else
          send_editor(SCI_SETFOLDEXPANDED, line, 0);
        expand(line, doExpand, force, visLevels - 1);
      }
      else
      {
        if (doExpand && send_editor(SCI_GETFOLDEXPANDED, line))
          expand(line, true, force, visLevels - 1);
        else
          expand(line, false, force, visLevels - 1);
      }
    }
    else
      line++;
  }
}

void SqlEditorFE::fold_code(bool expanding)
{
  int maxLine = send_editor (SCI_GETTEXTLENGTH);
  send_editor(SCI_COLOURISE, 0, -1);
  for (int line = 0; line < maxLine; line++)
  {
    int level = send_editor(SCI_GETFOLDLEVEL, line);
    if ((level & SC_FOLDLEVELHEADERFLAG) && (SC_FOLDLEVELBASE == (level & SC_FOLDLEVELNUMBERMASK)))
    {
      if (expanding)
      {
        send_editor(SCI_SETFOLDEXPANDED, line, 1);
        expand(line, true);
        line--;
      }
      else
      {
        int lineMaxSubord = send_editor(SCI_GETLASTCHILD, line, -1);
        send_editor(SCI_SETFOLDEXPANDED, line, 0);
        if (lineMaxSubord > line)
          send_editor(SCI_HIDELINES, line + 1, lineMaxSubord);
      }
    }
  }
}

void SqlEditorFE::fold_open_all()
{
  fold_code (true);
}

void SqlEditorFE::fold_close_all()
{
  fold_code (false);
}

//------------------------------------------------------------------------------
