/* 
 * Copyright (c) 2008, 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
 */

/**
 * Implementation of the textbox (multi line edit) wrapper for the mforms library.
 */

#include "stdafx.h"

#include "mforms/mforms.h"
#include "wf_view.h"
#include "wf_textbox.h"

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

using namespace MySQL::Forms;

using namespace System::Windows::Forms;
using namespace System::Text;

DEFAULT_LOG_DOMAIN(DOMAIN_MFORMS_WRAPPER)

//----------------- TextBoxEx ----------------------------------------------------------------------

bool TextBoxEx::ProcessCmdKey(Message% msg, Keys keyData)
{
  // In order to be able to determine certain special keys we have to hook into the chain before any
  // other key handling is performed.
  if (msg.Msg == WM_KEYDOWN)
  {
    switch (msg.WParam.ToInt32())
    {
    case Keys::Return:
      {
        bool result;
        mforms::TextBox* backend = ObjectImpl::get_backend_control<mforms::TextBox>(this);
        if (((msg.LParam.ToInt32() >> 16) & KF_EXTENDED) == KF_EXTENDED)
          result = backend->key_event(mforms::KeyReturn, getModifiers(keyData), "");
        else
          result = backend->key_event(mforms::KeyEnter, getModifiers(keyData), "");

        if (result)
          return TextBox::ProcessCmdKey(msg, keyData);
        else
          return false;

        break;
      }

    default:
      return TextBox::ProcessCmdKey(msg, keyData);
    }
  }
  else
    return TextBox::ProcessCmdKey(msg, keyData);
}

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

mforms::ModifierKey TextBoxEx::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;
}

//----------------- TextBoxImpl --------------------------------------------------------------------

bool TextBoxImpl::create(mforms::TextBox *self, mforms::ScrollBars scroll_bars)
{
  TextBoxImpl ^text= gcnew TextBoxImpl(self);

  if (text != nullptr)
  {
    TextBox ^textbox= ViewImpl::create<TextBoxEx>(self, text);
    textbox->ForeColor = Color::Black;
    textbox->Multiline= true;
    textbox->AcceptsReturn= true;
    ScrollBars native_scrollbars = ScrollBars::None;
    if ((scroll_bars & mforms::HorizontalScrollBar) != 0)
    {
      if ((scroll_bars & mforms::VerticalScrollBar) != 0)
        native_scrollbars = ScrollBars::Both;
      else
        native_scrollbars = ScrollBars::Horizontal;
    }
    else
      if ((scroll_bars & mforms::VerticalScrollBar) != 0)
        native_scrollbars = ScrollBars::Vertical;
    textbox->ScrollBars= native_scrollbars;
    textbox->TextChanged += gcnew EventHandler(text, &TextBoxImpl::OnChange);
    textbox->KeyDown += gcnew KeyEventHandler(text, &TextBoxImpl::OnKeyDown);
    textbox->KeyPress += gcnew KeyPressEventHandler(text, &TextBoxImpl::OnKeyPress);
    textbox->Size= Size(100, textbox->PreferredSize.Height); // DefaultSize is not accessible here. 
    return true;
  }
  return false;
}

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

void TextBoxImpl::OnChange(Object^ sender, EventArgs^ args)
{
  TextBox^ textbox= (TextBox^) sender;

  if (textbox->Tag != nullptr)
  {
    mforms::TextBox* box= ViewImpl::get_backend_control<mforms::TextBox>(textbox);
    if (box != NULL)
      box->callback();
  }
}

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

void TextBoxImpl::OnKeyDown(Object^ sender, KeyEventArgs^ args)
{
  // Don't call the back end for the return key. We have already done that.
  if (args->KeyCode != Keys::Return)
  {
    TextBoxEx^ textbox= (TextBoxEx^) sender;
    if (textbox->Tag != nullptr)
    {
      mforms::TextBox* box= ViewImpl::get_backend_control<mforms::TextBox>(textbox);

      modifiers = textbox->getModifiers(args->KeyData);
      mforms::KeyCode code = mforms::KeyNone;
      switch (args->KeyCode & Keys::KeyCode)
      {
      case Keys::Home:
        code = mforms::KeyHome;
        break;

      case Keys::End:
        code = mforms::KeyEnd;
        break;

      case Keys::Prior:
        code = mforms::KeyPrevious;
        break;

      case Keys::Next:
        code = mforms::KeyNext;
        break;

      case Keys::ShiftKey:
      case Keys::ControlKey:
      case Keys::Menu: // Alt key
      case Keys::LWin: // Command on Mac.
        code = mforms::KeyModifierOnly;
        break;
      }

      if (code != mforms::KeyNone)
        if (!box->key_event(code, modifiers, ""))
          args->Handled = true; // If the backend consumed the key (by returning false) then we stop
                                // further processing of this event.
    }
  }
}

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

void TextBoxImpl::OnKeyPress(Object^ sender, KeyPressEventArgs^ args)
{
  // Don't call the back end for the return key. We have already done that.
  if (args->KeyChar != '\r')
  {
    TextBoxEx^ textbox= (TextBoxEx^) sender;
    if (textbox->Tag != nullptr)
    {
      mforms::TextBox* box= ViewImpl::get_backend_control<mforms::TextBox>(textbox);
      String^ string = gcnew String(args->KeyChar, 1);
      if (!box->key_event(mforms::KeyChar, modifiers, NativeToCppString(string)))
        args->Handled = true;
    }
  }
}

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

void TextBoxImpl::set_bordered(mforms::TextBox *self, bool bordered)
{
  TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
    textbox->get_control<TextBox>()->BorderStyle= bordered ? BorderStyle::FixedSingle : BorderStyle::None;
}

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

void TextBoxImpl::set_text(mforms::TextBox *self, const std::string &text)
{
  TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
  {
    // Convert LF only line breaks into Windows line breaks.
    StringBuilder^ builder= gcnew StringBuilder(CppStringToNative(text));
    builder->Replace("\n", Environment::NewLine);

    TextBox^ native_box= textbox->get_control<TextBox>();

    native_box->Text= builder->ToString();
    native_box->Select(0, 0);
  }
}

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

void TextBoxImpl::append_text(mforms::TextBox *self, const std::string &text, bool scroll_to_end)
{
  TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
  {
    TextBox^ native_box = textbox->get_control<TextBox>();
    if (!native_box->IsDisposed)
    {
      // Convert from UTF-8 to UTF-16 + line end conversion.
      StringBuilder^ builder= gcnew StringBuilder(CppStringToNative(text));
      builder->Replace("\n", Environment::NewLine);

      native_box->AppendText(builder->ToString());
      if (scroll_to_end && native_box->Text->Length > 0)
      {
        native_box->Select(native_box->Text->Length - 1, 0);
        native_box->ScrollToCaret();
      }
    }
  }
}

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

std::string TextBoxImpl::get_text(mforms::TextBox *self)
{
  TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
  {
    // Convert Windows line breaks to LF.
    StringBuilder^ builder= gcnew StringBuilder(textbox->get_control<Control>()->Text);
    builder->Replace(Environment::NewLine, "\n");
    return NativeToCppString(builder->ToString());
  }
  return "";
}

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

void TextBoxImpl::set_read_only(mforms::TextBox *self, bool flag)
{
   TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
    textbox->get_control<TextBox>()->ReadOnly= flag;
}

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

void TextBoxImpl::set_padding(mforms::TextBox *self, int pad)
{
  TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
    textbox->get_control<TextBox>()->Padding = Padding(pad); // Doesn't have any effect.
}

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

void TextBoxImpl::clear(mforms::TextBox *self)
{
  TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
    textbox->get_control<TextBox>()->Clear();
}

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

void TextBoxImpl::set_monospaced(mforms::TextBox *self, bool flag)
{
  TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
  {
    if (flag)
      try
      {
        textbox->get_control<TextBox>()->Font = gcnew System::Drawing::Font("Bitstream Vera Sans Mono", textbox->get_control<TextBox>()->Font->Size - 2);
      }
      catch (System::ArgumentException^ e)
      {
        // Argument exception pops up when the system cannot find the Regular font style (corrupt font).
        log_error("TextBoxImpl::set_monospaced failed. %s\n", e->Message);
      }
    else
      textbox->get_control<TextBox>()->ResetFont();
  }
}

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

void TextBoxImpl::get_selected_range(mforms::TextBox *self, int &start, int &end)
{
  TextBoxImpl^ textbox= (TextBoxImpl^)ObjectImpl::FromUnmanaged(self);

  if (textbox != nullptr)
  {
    start = textbox->get_control<TextBox>()->SelectionStart;
    end = start + textbox->get_control<TextBox>()->SelectionLength;
  }
}

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

TextBoxImpl::TextBoxImpl(mforms::TextBox *text)
  : ViewImpl(text)
{
}

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