/* 
 * 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
 */

#ifndef __WB_H__
#define __WB_H__

#include "wb_config.h"
#include "workbench/wb_context.h"
#include "workbench/wb_context_ui.h"
#include "model/wb_model_diagram_form.h"
#include "model/wb_context_model.h"
#include "model/wb_history_tree.h"
#include "model/wb_diagram_options.h"
#include "model/wb_user_datatypes.h"
#include "model/wb_overview_physical.h"

#include "common/preferences_form.h"
#include "common/document_properties_form.h"

#include "GrtTemplates.h"
#include "DelegateWrapper.h"
#include "FindDialogBE.h"
#include "Overview.h"
#include "ModelDiagramFormWrapper.h"

#include "LogWrapper.h"

#if WBContext_VERSION > 5
# error outdated wrapper Wb.h
#endif

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;

using namespace Aga::Controls::Tree;

using namespace MySQL::Grt;
using namespace MySQL::GUI::Mdc;
using namespace MySQL::Forms;

namespace MySQL {
namespace Workbench {

public enum class Msg_type
{
  MT_warning=  grt::WarningMsg,
  MT_error=    grt::ErrorMsg,
  MT_info=     grt::InfoMsg,
  MT_progress= grt::ProgressMsg,
};

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

public ref class PageSettings
{
public:
  PageSettings()
    : paper_type(nullptr), margin_top(0), margin_bottom(0), margin_left(0), margin_right(0), 
      orientation(nullptr)
  {
  }

  PageSettings(const app_PageSettingsRef &settings)
    : paper_type(CppStringToNative(settings->paperType().is_valid() ? settings->paperType()->name() : "")),
    margin_top(settings->marginTop()), margin_bottom(settings->marginBottom()),
    margin_left(settings->marginLeft()), margin_right(settings->marginRight()),
    orientation(CppStringToNative(settings->orientation()))
  {
  }

  void update_object(grt::ListRef<app_PaperType> paperTypes, app_PageSettingsRef settings)
  {
    settings->paperType(grt::find_named_object_in_list(paperTypes, NativeToCppString(paper_type)));

    settings->marginTop(grt::DoubleRef(margin_top));
    settings->marginBottom(grt::DoubleRef(margin_bottom));
    settings->marginLeft(grt::DoubleRef(margin_left));
    settings->marginRight(grt::DoubleRef(margin_right));

    settings->orientation(NativeToCppString(orientation));
  }

  String^ paper_type;
  double margin_top;
  double margin_bottom;
  double margin_left;
  double margin_right;
  String^ orientation;
};


public ref class PaperSize
{
public:
  PaperSize(const wb::WBPaperSize &paperSize)
    : name(CppStringToNative(paperSize.name)), 
    caption(CppStringToNative(paperSize.caption)), 
    margins_set(paperSize.margins_set),
    margin_top(paperSize.margin_top), margin_bottom(paperSize.margin_bottom), 
    margin_left(paperSize.margin_left), margin_right(paperSize.margin_right), 
    width(paperSize.width), height(paperSize.height),
    description(CppStringToNative(paperSize.description))
  {
  }

  String^ name;
  String^ caption;
  double width;
  double height;
  bool margins_set;
  double margin_top;
  double margin_bottom;
  double margin_left;
  double margin_right;
  String^ description;
};


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

public ref class WbOptions
{
private:
  wb::WBOptions* inner; 

public:
  WbOptions(String^ baseDir, String^ userDir);

  wb::WBOptions* get_unmanaged_object() { return inner; };
  bool parse_args(array<String^>^ args, String^ app_path);

  property bool ShowSplash
  {
    bool get() { return !inner->no_logo; }
  }

  property bool Verbose
  {
    bool get() { return !inner->verbose; }
  }

  property String^ OpenAtStartup
  {
    String^ get();
  }

  property String^ OpenAtStartupType
  {
    String^ get();
  }
};


public ref class WbContext
{
  ::wb::WBContextUI *inner;

  ::MySQL::Grt::GrtManager^ manager;
  ::MySQL::Workbench::Overview^ physical_overview;
  ::MySQL::Grt::GrtValueTree^ catalog_tree;

  ::System::Collections::ArrayList open_editor_slot_wrappers;

  DelegateSlot0<void,void> ^undo_delegate;
  DelegateSlot0<bool,bool> ^can_undo_delegate;
  DelegateSlot0<void,void> ^redo_delegate;
  DelegateSlot0<bool,bool> ^can_redo_delegate;
  DelegateSlot0<void,void> ^copy_delegate;
  DelegateSlot0<bool,bool> ^can_copy_delegate;
  DelegateSlot0<void,void> ^cut_delegate;
  DelegateSlot0<bool,bool> ^can_cut_delegate;
  DelegateSlot0<void,void> ^paste_delegate;
  DelegateSlot0<bool,bool> ^can_paste_delegate;
  DelegateSlot0<void,void> ^select_all_delegate;
  DelegateSlot0<bool,bool> ^can_select_all_delegate;
  DelegateSlot0<void,void> ^delete_delegate;
  DelegateSlot0<bool,bool> ^can_delete_delegate;
  DelegateSlot0<void,void> ^search_delegate;
  DelegateSlot0<void,void> ^find_delegate;
  DelegateSlot0<bool,bool> ^can_find_delegate;
  DelegateSlot0<void,void> ^find_replace_delegate;
  DelegateSlot0<bool,bool> ^can_find_replace_delegate;

public:
  delegate void VoidStrUIFormDelegate(String^ str1, UIForm^ form);

private:
  // void (string, bec::UIFrom)
  [UnmanagedFunctionPointerAttribute(CallingConvention::Cdecl)]
  delegate void VoidStrUIFormWrapperDelegate(const std::string& str1, boost::shared_ptr<bec::UIForm> form);
  typedef void (*WbContext::VOID_STR_UIFORM_CB)(const std::string& str1, boost::shared_ptr<bec::UIForm> form);

  // TODO: implement differently, simpler!
  // Creating these views needs a lot of specialized knowledge, which requires to include many heavy-weight
  // classes (e.g. SQL editor). This should not be handled by a general callback but by the classes
  // that need to create a view (can't the wrapper create the frontend view class?).
  VoidStrUIFormDelegate^ create_main_form_view_delegate;
  VoidStrUIFormWrapperDelegate^ create_main_form_view_wrapper_delegate;
  void set_create_main_form_view(MySQL::Forms::AppImpl^ application, VoidStrUIFormDelegate^ dt);
  void create_main_form_view_wrapper(const std::string& view_name, boost::shared_ptr<bec::UIForm> form_be);

public:
  WbContext(bool verbose)
  {
    inner = new ::wb::WBContextUI(verbose);
    manager = nullptr;
    physical_overview = nullptr;
    catalog_tree = nullptr;

    Logger::LogDebug(DOMAIN_WB_CONTEXT_WRAPPER, 1, "Creating WbContext\n");
  }

  ~WbContext()
  {
    delete catalog_tree;
    delete physical_overview;
    delete inner;
    delete manager;

    Logger::LogDebug(DOMAIN_WB_CONTEXT_WRAPPER, 1, "Destroying WbContext\n");
  }

  ::wb::WBContextUI *get_unmanaged_object() 
  { return inner; }

  bool init(MySQL::Forms::AppImpl^ application, WbOptions^ options,
    VoidStrUIFormDelegate^ create_main_form_view)
  {
    set_create_main_form_view(application, create_main_form_view);
    return inner->init(
      application->get_callbacks(), 
      options->get_unmanaged_object());
  }

  bool opengl_rendering_enforced()
  {
    return inner->get_wb()->opengl_rendering_enforced();
  }

  bool software_rendering_enforced()
  {
    return inner->get_wb()->software_rendering_enforced();
  }

  bool is_busy()
  {
    return inner->get_wb()->get_grt_manager()->get_dispatcher()->get_busy();
  }

  bool request_quit()
  {
    return inner->request_quit();
  }

  void perform_quit()
  {
    inner->perform_quit();
  }

  bool is_quitting()
  {
    return inner->is_quitting();
  }

  void finalize()
  {
    inner->finalize();
  }

  GrtManager^ get_grt_manager()
  {
    if (manager == nullptr)
      manager= gcnew GrtManager(inner->get_wb()->get_grt_manager());

    return manager; 
  }

  MenuStrip^ menu_for_form(MySQL::Grt::UIForm^ form);
  void validate_menu_for_form(MySQL::Grt::UIForm^ form);
  ToolStrip^ toolbar_for_form(MySQL::Grt::UIForm^ form);

  void focus_search_box(MySQL::Grt::UIForm^ form);
  String^ get_search_string(MySQL::Grt::UIForm^ form);
  String^ get_title();
  bool has_unsaved_changes();
  void open_document(String^ file);
  bool save_changes();
  void flush_idle_tasks();
  double delay_for_next_timer();
  void flush_timers();

  // ----- Edit menu handling
  void validate_edit_menu()
  {
    inner->get_command_ui()->revalidate_edit_menu_items();
  }

  void edit_undo()
  {
    if (inner->get_active_main_form())
      inner->get_active_main_form()->undo();
  }

  bool edit_can_undo()
  {
    if (inner->get_active_main_form() && inner->get_active_main_form()->can_undo())
      return true;
    return false;
  }

  void edit_redo()
  {
    if (inner->get_active_main_form())
      inner->get_active_main_form()->redo();
  }

  bool edit_can_redo()
  {
    if (inner->get_active_main_form() && inner->get_active_main_form()->can_redo())
      return true;
    return false;
  }

  void edit_copy()
  {
    if (inner->get_active_form())
      inner->get_active_form()->copy();
  }

  bool edit_can_copy()
  {
    if (inner->get_active_form())
      return inner->get_active_form()->can_copy();
    return false;
  }

  void edit_cut()
  {
    if (inner->get_active_form())
      inner->get_active_form()->cut();
  }

  bool edit_can_cut()
  {
    if (inner->get_active_form() && inner->get_active_form()->can_cut())
      return true;
    return false;
  }

  void edit_paste()
  {
    if (inner->get_active_form())
      inner->get_active_form()->paste();
  }

  bool edit_can_paste()
  {
    if (inner->get_active_form() && inner->get_active_form()->can_paste())
      return true;
    return false;
  }

  void edit_select_all()
  {
    if (inner->get_active_form())
      inner->get_active_form()->select_all();
  }

  bool edit_can_select_all()
  {
    if (inner->get_active_form() && inner->get_active_form()->can_select_all())
      return true;
    return false;
  }

  void edit_delete()
  {
    if (inner->get_active_form())
      inner->get_active_form()->delete_selection();
  }

  bool edit_can_delete()
  {
    if (inner->get_active_form() && inner->get_active_form()->can_delete())
      return true;
    return false;
  }


  bool try_searching_diagram(String ^text)
  {
    bec::UIForm *form= inner->get_active_main_form();
    if (form && dynamic_cast<wb::ModelDiagramForm*>(form))
    {
      dynamic_cast<wb::ModelDiagramForm*>(form)->search_and_focus_object(NativeToCppString(text));
      return true;
    }
    return false;
  }

  
  typedef DelegateSlot0<void, void> CommandActionDelegate;
  typedef DelegateSlot0<bool, bool> CommandValidateDelegate;
  

  void set_edit_menu_delegates(CommandActionDelegate::ManagedDelegate ^undo_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_undo_delegate,
                               CommandActionDelegate::ManagedDelegate ^redo_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_redo_delegate,
                               CommandActionDelegate::ManagedDelegate ^copy_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_copy_delegate,
                               CommandActionDelegate::ManagedDelegate ^cut_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_cut_delegate,
                               CommandActionDelegate::ManagedDelegate ^paste_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_paste_delegate,
                               CommandActionDelegate::ManagedDelegate ^select_all_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_select_all_delegate,
                               CommandActionDelegate::ManagedDelegate ^delete_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_delete_delegate,
                               CommandActionDelegate::ManagedDelegate ^search_delegate,
                               CommandActionDelegate::ManagedDelegate ^find_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_find_delegate,
                               CommandActionDelegate::ManagedDelegate ^find_replace_delegate,
                               CommandValidateDelegate::ManagedDelegate ^can_find_replace_delegate)
  {
    this->undo_delegate= gcnew CommandActionDelegate(undo_delegate);
    this->can_undo_delegate= gcnew CommandValidateDelegate(can_undo_delegate);
    this->redo_delegate= gcnew CommandActionDelegate(redo_delegate);
    this->can_redo_delegate= gcnew CommandValidateDelegate(can_redo_delegate);
    this->copy_delegate= gcnew CommandActionDelegate(copy_delegate);
    this->can_copy_delegate= gcnew CommandValidateDelegate(can_copy_delegate);
    this->cut_delegate= gcnew CommandActionDelegate(cut_delegate);
    this->can_cut_delegate= gcnew CommandValidateDelegate(can_cut_delegate);
    this->paste_delegate= gcnew CommandActionDelegate(paste_delegate);
    this->can_paste_delegate= gcnew CommandValidateDelegate(can_paste_delegate);
    this->select_all_delegate= gcnew CommandActionDelegate(select_all_delegate);
    this->can_select_all_delegate= gcnew CommandValidateDelegate(can_select_all_delegate);
    this->delete_delegate= gcnew CommandActionDelegate(delete_delegate);
    this->can_delete_delegate= gcnew CommandValidateDelegate(can_delete_delegate);
    this->search_delegate= gcnew CommandActionDelegate(search_delegate);
    this->find_delegate= gcnew CommandActionDelegate(find_delegate);
    this->can_find_delegate= gcnew CommandValidateDelegate(can_find_delegate);
    this->find_replace_delegate= gcnew CommandActionDelegate(find_replace_delegate);
    this->can_find_replace_delegate= gcnew CommandValidateDelegate(can_find_replace_delegate);

    inner->get_command_ui()->add_builtin_command("undo", 
        this->undo_delegate->get_slot(),
        this->can_undo_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("redo", 
        this->redo_delegate->get_slot(),
        this->can_redo_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("copy", 
        this->copy_delegate->get_slot(),
        this->can_copy_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("cut", 
        this->cut_delegate->get_slot(),
        this->can_cut_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("paste", 
        this->paste_delegate->get_slot(),
        this->can_paste_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("selectAll", 
        this->select_all_delegate->get_slot(),
        this->can_select_all_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("delete", 
        this->delete_delegate->get_slot(),
        this->can_delete_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("find", 
        this->find_delegate->get_slot(),
        this->can_find_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("search_replace", 
        this->find_replace_delegate->get_slot(),
        this->can_find_replace_delegate->get_slot());
    inner->get_command_ui()->add_builtin_command("searchbox", 
        this->search_delegate->get_slot());
  }
  
  // Plugins/command handling.
  void add_frontend_commands(List<String^>^ commands);
  void remove_frontend_commands(List<String^>^ commands);
  void activate_command(String^ name);
  virtual FindDialogBE^ get_find_dialog();

  // Overview.
  Overview^ get_physical_overview();
  ::MySQL::Grt::GrtValueTree^ get_catalog_tree();
  TreeViewAdv^ get_history_tree();
  TreeViewAdv^ get_usertypes_tree();
  ::MySQL::Grt::GrtValueInspector^ get_inspector_for_selection(UIForm^ form, [Out] List<String^>^ %items);
  bool are_lists_equal(GrtValue^ v1, GrtValue^ v2);
  String^ get_description_for_selection(UIForm^ form, [Out] GrtValue^ %activeObjList, [Out] List<String^>^ %items);
  String^ get_description_for_selection([Out] GrtValue^ %activeObjList, [Out] List<String^>^ %items);
  void set_description_for_selection(GrtValue^ activeObjList, String^ val);
  ::MySQL::Workbench::ModelDiagramFormWrapper^ get_diagram_form_for_diagram(String^ id);
  void set_active_form(UIForm^ uiform);
  void set_active_form_from_appview(MySQL::Forms::AppViewDockContent^ form);
  String ^get_active_context();
  void show_output();
  void close_gui_plugin(IntPtr handle);  
  void execute_plugin(String^ name);
  void report_bug(String^ errorInfo);

  // State support.
  String^ read_state(String^ name, String^ domain, String^ default_value);
  int read_state(String^ name, String^ domain, const int default_value);
  double read_state(String^ name, String^ domain, const double default_value);
  bool read_state(String^ name, String^ domain, const bool default_value);

  void save_state(String^ name, String^ domain, String^ value);
  void save_state(String^ name, String^ domain, const int value);
  void save_state(String^ name, String^ domain, const double value);
  void save_state(String^ name, String^ domain, const bool value);

  // Preferences support.
  String^ read_option_value(String^ model, String^ key, String^ default_value);

  // Paper.
  List<PaperSize^>^ get_paper_sizes()
  {
    return MySQL::Grt::CppListToObjectList<::wb::WBPaperSize,PaperSize>(inner->get_paper_sizes(false));
  }


  PageSettings^ get_page_settings()
  {
    app_PageSettingsRef settings(inner->get_page_settings());
    if (settings.is_valid())
      return gcnew PageSettings(settings);
    return nullptr;
  }


  void set_page_settings(PageSettings^ settings)
  {
    settings->update_object(inner->get_wb()->get_root()->options()->paperTypes(), inner->get_page_settings());
    inner->get_wb()->get_model_context()->update_page_settings();
  }

  /**
   * To be called by the front end once the main form is ready.
   */
  void finished_loading(WbOptions^ options)
  {
    inner->init_finish(options->get_unmanaged_object());
  }

  void close_document_finish()
  {
    inner->get_wb()->close_document_finish();

    // Explicitly delete the overview object to avoid garbage collection to kick in after
    // our internal (non-managed) objects are gone already.
    delete physical_overview;
    physical_overview = nullptr;
  }

  void new_model_finish()
  {
    inner->get_wb()->new_model_finish();
  }

  String^ get_filename()
  {
    return CppStringToNative(inner->get_wb()->get_filename());
  }  

};



public ref class DiagramOptionsBE
{
  wb::DiagramOptionsBE *inner;

  MySQL::Grt::DelegateSlot0<void, void> ^_delegate;

public:
  DiagramOptionsBE(WindowsGDICanvasView ^view, WbContext ^wbContext, MySQL::Grt::DelegateSlot0<void, void>::ManagedDelegate ^deleg)
  {
    _delegate= gcnew MySQL::Grt::DelegateSlot0<void, void>(deleg);

    inner= new wb::DiagramOptionsBE(view->get_unmanaged_object(), 
      wbContext->get_unmanaged_object()->get_wb()->get_model_context()->get_active_model_diagram(true), wbContext->get_unmanaged_object()->get_wb());

    inner->scoped_connect(inner->signal_changed(),_delegate->get_slot());
  }

  ~DiagramOptionsBE()
  {
    delete _delegate;
    delete inner;
  }

  String ^get_name()
  {
    return CppStringToNative(inner->get_name());
  }

  void set_name(String ^name)
  {
    inner->set_name(NativeToCppString(name));
  }

  int get_xpages() 
  {
    return inner->get_xpages();
  }

  int get_ypages()
  {
    return inner->get_ypages();
  }

  void set_xpages(int c)
  {
    inner->set_xpages(c);
  }

  void set_ypages(int c)
  {
    inner->set_ypages(c);
  }

  void commit()
  {
    inner->commit();
  }

  void update_size()
  {
    inner->update_size();
  }
};


} // namespace Workbench
} // namespace MySQL

#endif // __WB_H__
