/* 
 * Copyright (c) 2010, 2013, 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 "mforms/mforms.h"
#include "wf_view.h"
#include "wf_code_editor.h"
#include "wf_menu.h"
#include "wf_find_panel.h"

#include "Scintilla.h"
#include "base/log.h"

DEFAULT_LOG_DOMAIN(DOMAIN_MFORMS_WRAPPER)

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

using namespace mforms;

//----------------- ScintillaControl ---------------------------------------------------------------

ScintillaControl::ScintillaControl()
  : Control()
{
  direct_pointer = 0;
  message_function = NULL;
}

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

/**
 * Calls directly into the scintilla backend, which avoids going through the message loop first.
 *   Advantage: fast
 *   Disadvantage: no thread synchronization, but cross-thread calls would not be a good idea anyway
 *                 (considering the other platforms).
 */
sptr_t ScintillaControl::direct_call(unsigned int message, uptr_t wParam, sptr_t lParam)
{
  if (Disposing || IsDisposed)
    return -1;

  HWND handle = (HWND)Handle.ToInt32();
  if (handle == 0)
    return -1;

  if (message_function == NULL)
    message_function = (SciFnDirect)SendMessage(handle, SCI_GETDIRECTFUNCTION, 0, 0);
  
  if (direct_pointer == 0)
    direct_pointer = (sptr_t)SendMessage(handle, SCI_GETDIRECTPOINTER, 0, 0);

  // This call will throw an exception if this thread and the one in which the window has been created
  // are not identical.
  return message_function(direct_pointer, message, wParam, lParam);
}

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

bool ScintillaControl::CanUndo::get()
{
  return direct_call(SCI_CANUNDO, 0, 0) != 0;
}

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

bool ScintillaControl::CanRedo::get()
{
  return direct_call(SCI_CANREDO, 0, 0) != 0;
}

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

bool ScintillaControl::CanCopy::get()
{
  int length = direct_call(SCI_GETSELECTIONEND, 0, 0) - direct_call(SCI_GETSELECTIONSTART, 0, 0);
  return length > 0;
}

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

bool ScintillaControl::CanCut::get()
{
  return CanCopy && CanDelete;
}

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

bool ScintillaControl::CanPaste::get()
{
  try
  {
    // Doesn't usually crash but if the clipboard chain is forcibly broken we might get this
    // error. No sense to show it to the user.
    return Clipboard::ContainsText() && direct_call(SCI_GETREADONLY, 0, 0) == 0;
  }
  catch (...)
  {
    log_error("Clipboard::ContainsText returned with exception\n");
    return false;
  }
}

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

bool ScintillaControl::CanDelete::get()
{
  return CanCopy && direct_call(SCI_GETREADONLY, 0, 0) == 0;
}

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

void ScintillaControl::Undo()
{
  direct_call(SCI_UNDO, 0, 0);
}

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

void ScintillaControl::Redo()
{
  direct_call(SCI_REDO, 0, 0);
}

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

void ScintillaControl::Copy()
{
  direct_call(SCI_COPY, 0, 0);
}

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

void ScintillaControl::Cut()
{
  direct_call(SCI_CUT, 0, 0);
}

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

void ScintillaControl::Paste()
{
  direct_call(SCI_PASTE, 0, 0);
}

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

void ScintillaControl::Delete()
{
  direct_call(SCI_REPLACESEL, 0, (sptr_t)"");
}

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

void ScintillaControl::SelectAll()
{
  direct_call(SCI_SELECTALL, 0, 0);
}

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

Windows::Forms::CreateParams^ ScintillaControl::CreateParams::get()
{
  Windows::Forms::CreateParams^ params = Control::CreateParams::get();
  params->ClassName = "Scintilla";

  return params;
};

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

void ScintillaControl::WndProc(Windows::Forms::Message% m)
{
  switch (m.Msg)
  {
  case WM_PAINT:
    // Weird, this message must be forwarded to the default window proc not the inherited WndProc,
    // otherwise nothing is drawn because no WM_PAINT message reaches Scintilla.
    Control::DefWndProc(m);
    break;

  case WM_DROPFILES:
//    handleFileDrop(m.WParam);
    break;

  case WM_SETCURSOR:
  case WM_GETTEXT:
  case WM_GETTEXTLENGTH:
    // Only used for getting the window text, which we don't have. The default implementation
    // returns the full text of the editor which produces problems with large content.
    m.Result = IntPtr::Zero;
    break;

  case WM_NOTIFY + 0x2000: // WM_NOTIFY reflected by .NET from the parent window.
  {
    // Parent notification. Details are passed as SCNotification structure.
    Scintilla::SCNotification* scn = reinterpret_cast<Scintilla::SCNotification*>(m.LParam.ToPointer());
    mforms::CodeEditor* editor = ViewImpl::get_backend_control<mforms::CodeEditor>(this);
    if (editor != NULL)
      editor->on_notify(scn);
    break;
  }

  case WM_COMMAND + 0x2000: // Ditto for WM_COMMAND.
    {
      mforms::CodeEditor* editor = ViewImpl::get_backend_control<mforms::CodeEditor>(this);
      if (editor != NULL)
        editor->on_command(m.WParam.ToInt32() >> 16);
      break;
    }

  default:
    Control::WndProc(m);
  }
}

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

void ScintillaControl::ShowFindPanel(bool doReplace)
{
  mforms::CodeEditor* editor = ViewImpl::get_backend_control<mforms::CodeEditor>(this);
  if (editor != NULL)
    editor->show_find_panel(doReplace);
}

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

void ScintillaControl::OnMouseDown(MouseEventArgs^ e)
{
  if (e->Button == Windows::Forms::MouseButtons::Right)
  {
    mforms::CodeEditor* editor = ViewImpl::get_backend_control<mforms::CodeEditor>(this);

    // update the associated context menu
    if (editor != NULL && editor->get_context_menu() != NULL)
    {
      Windows::Forms::ContextMenuStrip^ menu = MenuImpl::GetNativeControl(editor->get_context_menu());
      if (menu != ContextMenuStrip)
      {
        ContextMenuStrip = menu;
        (*editor->get_context_menu()->signal_will_show())();
      }
    }
    else
      ContextMenuStrip = nullptr;
  }
}

//----------------- CodeEditorImpl -----------------------------------------------------------------

CodeEditorImpl::CodeEditorImpl(mforms::CodeEditor* editor)
  : ViewImpl(editor)
{
  show_find_panel_delegate = gcnew ShowFindPanelDelegate(this, &CodeEditorImpl::show_find_panel);
  IntPtr ip = Marshal::GetFunctionPointerForDelegate(show_find_panel_delegate);
  ShowFindPanelDelegateType callback = static_cast<ShowFindPanelDelegateType>(ip.ToPointer());
  editor->set_show_find_panel_callback(boost::bind(callback, _1, _2));
}

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

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

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

    return true;
  }
  return false;
}

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

sptr_t CodeEditorImpl::send_editor(CodeEditor* editor, unsigned int message, uptr_t wParam, sptr_t lParam)
{
  CodeEditorImpl^ native_editor = (CodeEditorImpl^) ObjectImpl::FromUnmanaged(editor);
  ScintillaControl^ scintilla = native_editor->get_control<ScintillaControl>();
  return scintilla->direct_call(message, wParam, lParam);
}

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

void CodeEditorImpl::show_find_panel(mforms::CodeEditor* editor, bool show)
{
  mforms::FindPanel* find_panel = editor->get_find_panel();
  if (find_panel != NULL)
  {
    FindPanelImpl^ native_find_panel = (FindPanelImpl^)ObjectImpl::FromUnmanaged(find_panel);
    Control^ find_control = native_find_panel->get_control();
    if (show)
    {
      if (find_control->Parent == nullptr)
      {
        CodeEditorImpl^ native_editor = (CodeEditorImpl^)ObjectImpl::FromUnmanaged(editor);
        Control^ editor_control = native_editor->get_control();
        
        // Insert the find panel directly before the editor control (same index).
        int index = editor_control->Parent->Controls->GetChildIndex(editor_control);
        editor_control->Parent->Controls->Add(find_control);
        if (editor_control->Dock == DockStyle::Fill)
          editor_control->Parent->Controls->SetChildIndex(find_control, index + 1);
        else
          editor_control->Parent->Controls->SetChildIndex(find_control, index);
        find_control->Dock = DockStyle::Top;
        find_control->Show();
      }        
    }
    else
      find_control->Parent->Controls->Remove(find_control);
  }
}

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

