/* 
 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * 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; version 2 of the
 * License.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "stdafx.h"
#include "wf_code_editor.h"

using namespace System::IO;

using namespace ScintillaNet;

using namespace MySQL;
using namespace MySQL::Forms;
using namespace MySQL::Utilities;
using namespace MySQL::Controls;

DEFAULT_LOG_DOMAIN(DOMAIN_MFORMS_WRAPPER)

// Marker ID assignments. Markers with higher number overlay lower ones.
#define CE_STATEMENT_MARKER 0
#define CE_ERROR_MARKER 1
#define CE_BREAKPOINT_MARKER 2
#define CE_BREAKPOINT_HIT_MARKER 3
#define CE_CURRENT_LINE_MARKER 3

// Constants from Scintilla.h which aren't defined in Scintilla.NET.
#define SC_IV_NONE 0
#define SC_IV_REAL 1
#define SC_IV_LOOKFORWARD 2
#define SC_IV_LOOKBOTH 3

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

/**
 * Converts .NET modifier codes to mforms codes.
 */
mforms::ModifierKey getModifiers(Keys keyData)
{
  mforms::ModifierKey modifiers = mforms::ModifierNoModifier;
  if ((keyData & Keys::Control) == Keys::Control)
    modifiers = modifiers | mforms::ModifierControl;
  if ((keyData & Keys::Alt) == Keys::Alt)
    modifiers = modifiers | mforms::ModifierAlt;
  if ((keyData & Keys::Shift) == Keys::Shift)
    modifiers = modifiers | mforms::ModifierShift;
  if ((keyData & Keys::LWin) == Keys::LWin)
    modifiers = modifiers | mforms::ModifierCommand;

  return modifiers;
}

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

/**
 * Helper to setup an individual marker in the code editor.
 */
void setup_marker(Scintilla^ scintilla, int marker_id, String^ icon_path, Color background)
{
  Marker^ marker = scintilla->Markers[marker_id];
  if (icon_path != "" && File::Exists(icon_path))
  {
    StreamReader^ reader = File::OpenText(icon_path);
    String^ icon = reader->ReadToEnd();
    marker->SetImage(icon);
  }
  else
  {
    marker->Symbol = MarkerSymbol::Background;
    marker->BackColor = background;
    marker->ForeColor = Color::White;
    if (background.A < 255)
      marker->Alpha = background.A;
  }
}

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

/**
 * Either hides the gutter entirely or shows it with certain width settings.
 */
void setup_gutter(Scintilla^ scintilla, bool show)
{
  if (show)
  {
    int width = scintilla->NativeInterface->TextWidth(Constants::STYLE_LINENUMBER, "_99999");
    scintilla->Margins->Margin0->Width = width; // line numbers
    scintilla->Margins->Margin0->IsClickable = true;
    scintilla->Styles[Constants::STYLE_LINENUMBER]->ForeColor = Color::FromArgb(64, 64, 64);
    scintilla->Styles[Constants::STYLE_LINENUMBER]->BackColor = Color::FromArgb(220, 220, 220);
    scintilla->Margins->Margin1->Width = 16; // markers
    scintilla->Margins->Margin1->IsClickable = true;
    scintilla->Margins->Margin2->Width = 16; // indicators
    scintilla->Margins->Margin2->IsClickable = true;
    scintilla->Margins->FoldMarginColor = Color::FromArgb(230, 230, 230);
  }
  else
  {
    scintilla->Margins->Margin0->Width = 0;
    scintilla->Margins->Margin1->Width = 0;
    scintilla->Margins->Margin2->Width = 0;
  }
}

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

/**
 * Helper to set up the code editor with usually used settings.
 */
void setup_editor(Scintilla^ scintilla, bool use_tabs, int indentation, String^ language)
{
  // Setting the language currently loads most of the settings from an xml file.
  // This will later change when we switch to our own Scintilla editor impl.
  scintilla->ConfigurationManager->Language = language;
  scintilla->AcceptsReturn = true;
  scintilla->AcceptsTab = true;
  scintilla->UseFont = true;

  scintilla->Caret->HighlightCurrentLine = true;
  scintilla->Caret->CurrentLineBackgroundColor = Color::FromArgb(248, 200, 0);
  scintilla->Caret->CurrentLineBackgroundAlpha = 20;

  scintilla->Indentation->UseTabs = use_tabs;
  scintilla->Indentation->TabWidth = indentation;
  scintilla->Indentation->TabIndents = true;
  scintilla->Indentation->IndentWidth = indentation;
  scintilla->Indentation->BackspaceUnindents = true;
  scintilla->Indentation->SmartIndentType = SmartIndent::Simple;
  // scintilla->Indentation->ShowGuides = true; Wrong implementation in Scintilla.NET, we set them manually.
  // For now disabled. They seem to work properly only with tabs.
  //int guide_type = (language == "python") ? SC_IV_LOOKFORWARD : SC_IV_LOOKBOTH;
  //scintilla->NativeInterface->SendMessageDirect(Constants::SCI_SETINDENTATIONGUIDES, guide_type, 0);

  setup_gutter(scintilla, true);

  // Fixes improper ViewportSize from start.
  // Once row is stretched enough to make thumb appear this thumb becomes irreversible.
  scintilla->Scrolling->HorizontalWidth = 1;

  scintilla->EndOfLine->Mode = EndOfLineMode::LF;

  // Marker definitions.
  setup_marker(scintilla, CE_STATEMENT_MARKER, "images/ui/editor_statement.xpm", Color::Blue);
  setup_marker(scintilla, CE_ERROR_MARKER, "images/ui/editor_error.xpm", Color::FromArgb(50, 222, 17, 46));
  setup_marker(scintilla, CE_BREAKPOINT_MARKER, "images/ui/editor_breakpoint.xpm", Color::FromArgb(169, 68, 62));
  setup_marker(scintilla, CE_BREAKPOINT_HIT_MARKER, "images/ui/editor_breakpoint_hit.xpm", Color::FromArgb(169, 68, 62));
  setup_marker(scintilla, CE_CURRENT_LINE_MARKER, "images/ui/editor_current_pos.xpm", Color::FromArgb(169, 68, 62));
}

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

CodeEditorImpl::CodeEditorImpl(mforms::CodeEditor *self)
  : ViewImpl(self)
{
}

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

bool CodeEditorImpl::create(mforms::CodeEditor *self)
{
  CodeEditorImpl^ editor= gcnew CodeEditorImpl(self);

  if (editor != nullptr)
  {
    Scintilla^ scintilla= ViewImpl::create<Scintilla>(self, editor);

    scintilla->MarginClick += gcnew EventHandler<MarginClickEventArgs^>(editor, &CodeEditorImpl::margin_clicked);
    scintilla->TextInserted += gcnew EventHandler<TextModifiedEventArgs^>(editor, &CodeEditorImpl::text_changed);
    scintilla->TextDeleted += gcnew EventHandler<TextModifiedEventArgs^>(editor, &CodeEditorImpl::text_changed);
    return true;
  }
  return false;
}

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

void CodeEditorImpl::text_changed(Object^ sender, TextModifiedEventArgs^ args)
{
  Scintilla^ scintilla= (Scintilla^) sender;
  if (scintilla->Tag != nullptr)
  {
    mforms::CodeEditor* editor= ViewImpl::get_backend_control<mforms::CodeEditor>(scintilla);
    if (editor != NULL)
      editor->text_changed(scintilla->NativeInterface->LineFromPosition(args->Position), args->LinesAddedCount);
  }
}

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

void CodeEditorImpl::margin_clicked(Object^ sender, MarginClickEventArgs^ args)
{
  Scintilla^ scintilla= (Scintilla^) sender;
  if (scintilla->Tag != nullptr)
  {
    mforms::CodeEditor* editor= ViewImpl::get_backend_control<mforms::CodeEditor>(scintilla);
    if (editor != NULL)
    {
      mforms::ModifierKey modifiers = getModifiers(args->Modifiers);

      editor->gutter_clicked(args->Margin->Number, args->Line->Number, modifiers);
    }
  }
}

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

void CodeEditorImpl::set_text(mforms::CodeEditor *self, const std::string &text)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);

  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    scintilla->Text = CppStringToNative(text);

    String^ tt = scintilla->Text;
    int i = 0;
  }
}

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

const std::string CodeEditorImpl::get_text(mforms::CodeEditor* self, bool selection_only)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);

  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    if (selection_only)
      return NativeToCppString(scintilla->Selection->Text);
    else
      return NativeToCppString(scintilla->Text);
  }
  return "";
}

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

void CodeEditorImpl::get_selection(mforms::CodeEditor* self, int &start, int &length)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);

  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    start = scintilla->Selection->Start;
    length = scintilla->Selection->Length;
  }
}

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

void CodeEditorImpl::set_selection(mforms::CodeEditor* self, int start, int length)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);

  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    scintilla->Selection->Start = start;
    scintilla->Selection->End = start + length;
  }
}

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

bool CodeEditorImpl::get_range_of_line(mforms::CodeEditor* self, int line, int &start, int &length)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);

  if (editor != nullptr)
  {
    Scintilla^ scintilla = editor->get_control<Scintilla>();
    start = scintilla->Lines[line]->StartPosition;
    length = scintilla->Lines[line]->EndPosition - start;

    return true;
  }
  return false;
}

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

void CodeEditorImpl::set_language(mforms::CodeEditor* self, mforms::SyntaxHighlighterLanguage language)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);

  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    switch (language)
    {
      case mforms::LanguageCpp:
        setup_editor(scintilla, false, 2, "cpp"); // Currently string based. Will later use enum.
        break;

      case mforms::LanguageLua:
        setup_editor(scintilla, false, 2, "lua");
        break;

      case mforms::LanguagePython:
        setup_editor(scintilla, false, 2, "python");
        break;

      case mforms::LanguageMySQL:
        setup_editor(scintilla, false, 2, "mysql");
        break;

      default:
        setup_editor(scintilla, false, 2, "Null");
    }
  }
}

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

void CodeEditorImpl::set_read_only(mforms::CodeEditor* self, bool flag)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);

  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    scintilla->IsReadOnly = flag;

    Color backColor;
    if (flag)
      backColor = Color::FromKnownColor(KnownColor::ButtonFace);
    else
      backColor = Color::FromKnownColor(KnownColor::Window);

    for (int i = 0; i <= 32; i++)
      scintilla->Styles[i]->BackColor = backColor;

  }
}

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

void CodeEditorImpl::show_markup(mforms::CodeEditor* self, mforms::LineMarkup markup, int line)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);
  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();

    // The marker mask contains one bit for each set marker (0..31).
    unsigned int marker_mask = scintilla->Lines[line]->GetMarkerMask();
    unsigned int new_marker_mask = 0;
    if ((markup & mforms::LineMarkupStatement) != 0)
    {
      unsigned int mask = 1 << CE_STATEMENT_MARKER;
      if ((marker_mask & mask) != mask)
        new_marker_mask |= mask;
    }
    if ((markup & mforms::LineMarkupError) != 0)
    {
      unsigned int mask = 1 << CE_ERROR_MARKER;
      if ((marker_mask & mask) != mask)
        new_marker_mask |= mask;
    }
    if ((markup & mforms::LineMarkupBreakpoint) != 0)
    {
      unsigned int mask = 1 << CE_BREAKPOINT_MARKER;
      if ((marker_mask & mask) != mask)
        new_marker_mask |= mask;
    }
    if ((markup & mforms::LineMarkupBreakpointHit) != 0)
    {
      unsigned int mask = 1 << CE_BREAKPOINT_HIT_MARKER;
      if ((marker_mask & mask) != mask)
        new_marker_mask |= mask;
    }
    if ((markup & mforms::LineMarkupCurrent) != 0)
    {
      unsigned int mask = 1 << CE_CURRENT_LINE_MARKER;
      if ((marker_mask & mask) != mask)
        new_marker_mask |= mask;
    }

    scintilla->Lines[line]->AddMarkerSet(new_marker_mask);
  }
}

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

void CodeEditorImpl::remove_markup(mforms::CodeEditor* self, mforms::LineMarkup markup, int line)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);
  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();

    if (markup == mforms::LineMarkupAll)
      scintilla->Lines[line]->DeleteAllMarkers();
    else
    {
      if ((markup & mforms::LineMarkupStatement) != 0)
        scintilla->Lines[line]->DeleteMarker(CE_STATEMENT_MARKER);
      if ((markup & mforms::LineMarkupError) != 0)
        scintilla->Lines[line]->DeleteMarker(CE_ERROR_MARKER);
      if ((markup & mforms::LineMarkupBreakpoint) != 0)
        scintilla->Lines[line]->DeleteMarker(CE_BREAKPOINT_MARKER);
      if ((markup & mforms::LineMarkupBreakpointHit) != 0)
        scintilla->Lines[line]->DeleteMarker(CE_BREAKPOINT_HIT_MARKER);
      if ((markup & mforms::LineMarkupCurrent) != 0)
        scintilla->Lines[line]->DeleteMarker(CE_CURRENT_LINE_MARKER);
    }
  }
}

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

int CodeEditorImpl::line_count(mforms::CodeEditor* self)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^) ObjectImpl::FromUnmanaged(self);
  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    return scintilla->Lines->Count;
  }
  return 0;
}

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

void CodeEditorImpl::set_font(mforms::CodeEditor* self, const std::string& fontDescription)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^)ObjectImpl::FromUnmanaged(self);
  if (editor != nullptr)
  {
    std::string font;
    int size;
    bool bold;
    bool italic;
    base::parse_font_description(fontDescription, font, size, bold, italic);

    FontStyle style = FontStyle::Regular;
    if (bold)
      style = (FontStyle) (style | FontStyle::Bold);
    if (italic)
      style = (FontStyle) (style | FontStyle::Italic);

    try
    {
      editor->get_control<Scintilla>()->Font = ControlUtilities::GetFont(CppStringToNativeRaw(font),
        (float) size, style);
    }
    catch (System::ArgumentException^ e)
    {
      // Argument exception pops up when the system cannot find the Regular font style (corrupt font).
      log_error("CodeEditorImpl::set_monospaced failed. %s\n", e->Message);
    }
  }
}

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

void CodeEditorImpl::show_gutter(mforms::CodeEditor* self, bool flag)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^)ObjectImpl::FromUnmanaged(self);
  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    setup_gutter(scintilla, flag);
  }
}

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

void CodeEditorImpl::set_features(mforms::CodeEditor* self, mforms::CodeEditorFeature features, bool flag)
{
  CodeEditorImpl^ editor= (CodeEditorImpl^)ObjectImpl::FromUnmanaged(self);
  if (editor != nullptr)
  {
    Scintilla^ scintilla= editor->get_control<Scintilla>();
    
    if ((features & mforms::FeatureWrapText) != 0)
    {
      if (flag)
        scintilla->LineWrap->Mode = WrapMode::Word;
      else
        scintilla->LineWrap->Mode = WrapMode::None;
    }
  }
}

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