/* 
 * Copyright (c) 2011, 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 "wb_sql_editor_form.h"
#include "wb_sql_editor_form_ui.h"
#include "query_side_palette.h"
#include "wb_context_names.h"
#include "objimpl/db.query/db_query_Resultset.h"
#include "grtui/inserts_export_form.h"
#include "grtdb/editor_dbobject.h"

#include "sqlide/wb_sql_editor_tree_controller.h"
#include "sqlide/sql_script_run_wizard.h"

#include "workbench/wb_command_ui.h"
#include "workbench/wb_context_ui.h"
#include "base/boost_smart_ptr_helpers.h"

#include <boost/signals2/connection.hpp>

#include "mforms/menubar.h"
#include "mforms/toolbar.h"
#include "mforms/code_editor.h"

using namespace bec;
using namespace grt;
using namespace wb;
using namespace base;

using boost::signals2::scoped_connection;


void SqlEditorForm::activate_command(const std::string &command)
{
  _wbsql->get_cmdui()->activate_command(command);
}


mforms::ToolBar *SqlEditorForm::get_toolbar()
{
  if (!_toolbar)
  {
    _toolbar = _wbsql->get_cmdui()->create_toolbar("data/dbquery_toolbar.xml", boost::bind(&SqlEditorForm::activate_command, this, _1));
    
    update_menu_and_toolbar();
  }
  return _toolbar;
}

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

mforms::MenuBar *SqlEditorForm::get_menubar()
{
  if (!_menu)
  {
    _menu = _wbsql->get_cmdui()->create_menubar_for_context(WB_CONTEXT_QUERY);
    
    update_menu_and_toolbar();
    
    _menu->set_item_enabled("query.save_edits", false);
    _menu->set_item_enabled("query.discard_edits", false);
    _menu->set_item_enabled("query.export", false);
    
    _menu->set_item_checked("query.stopOnError", !continue_on_error());
  }
  return _menu;
}

void SqlEditorForm::update_menu_and_toolbar()
{
  if (!_grtm->in_main_thread())
  {
    exec_sql_task->execute_in_main_thread(boost::bind(&SqlEditorForm::update_menu_and_toolbar, this),
                                          false,
                                          false);
    return;
  }
  
  bool running = is_running_query();
  
  if (_menu)
  {
    _menu->set_item_enabled("query.cancel", running);
    _menu->set_item_enabled("query.execute", !running);
    _menu->set_item_enabled("query.reconnect", !running);
    _menu->set_item_enabled("wb.sqlide.executeToTextOutput", !running);
    _menu->set_item_enabled("query.execute_current_statement", !running);
    _menu->set_item_enabled("query.explain", !running);
    _menu->set_item_enabled("query.explain_current_statement", !running);
    
    _menu->set_item_enabled("query.commit", !running && !auto_commit());
    _menu->set_item_enabled("query.rollback", !running && !auto_commit());    
    mforms::MenuItem *item = _menu->find_item("query.autocommit");
    if (item)
    {
      item->set_enabled(!running);
      item->set_checked(auto_commit());    
    }
  }
  
  if (_toolbar)
  {
    /* deprecated
     _toolbar->set_item_enabled("query.cancel", running);
     _toolbar->set_item_enabled("query.execute", !running);
     _toolbar->set_item_enabled("query.reconnect", !running);
     _toolbar->set_item_enabled("wb.sqlide.executeToTextOutput", !running);
     _toolbar->set_item_enabled("query.execute_current_statement", !running);
     _toolbar->set_item_enabled("query.explain", !running);
     _toolbar->set_item_enabled("query.explain_current_statement", !running);
     
     _toolbar->set_item_enabled("query.commit", !running && !auto_commit());
     _toolbar->set_item_enabled("query.rollback", !running && !auto_commit());
     mforms::ToolBarItem *item = _toolbar->find_item("query.autocommit");
     if (item)
     {
     item->set_enabled(!running);
     item->set_checked(auto_commit());
     }
     }*/
  }
  
  /*
   if (running)
   {
   set_editor_tool_items_enbled("query.cancel", false);
   for (Sql_editors::iterator iter = _sql_editors.begin(); iter != _sql_editors.end(); ++iter)
   if (iter->busy)
   iter->toolbar->set_item_checked("query.cancel", true);
   }
   else*/
  set_editor_tool_items_enbled("query.cancel", running);
  
  set_editor_tool_items_enbled("query.execute", !running);
  set_editor_tool_items_enbled("query.reconnect", !running);
  set_editor_tool_items_enbled("wb.sqlide.executeToTextOutput", !running);
  set_editor_tool_items_enbled("query.execute_current_statement", !running);
  set_editor_tool_items_enbled("query.explain", !running);
  set_editor_tool_items_enbled("query.explain_current_statement", !running);
  
  set_editor_tool_items_enbled("query.commit", !running && !auto_commit());
  set_editor_tool_items_enbled("query.rollback", !running && !auto_commit());    
  set_editor_tool_items_enbled("query.autocommit", !running);
  set_editor_tool_items_checked("query.autocommit", auto_commit());
  set_editor_tool_items_checked("query.stopOnError", !_continue_on_error);
}


void SqlEditorForm::validate_menubar()
{
  if (get_menubar())
    get_menubar()->validate();
}

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

/**
 * Setup of schema browser/object info/quick help sidebars (puzzling: sometimes called side palette).
 */
void SqlEditorForm::setup_sidebars()
{  
  // Right hand side bar (quick help, variables).
  _side_palette = mforms::manage(new QuerySidePalette(shared_from_this()));
  
#ifdef _WIN32
  mforms::Panel* panel;
  panel = mforms::manage(new mforms::Panel(mforms::StyledHeaderPanel));
  panel->set_title(_("SQL Additions"));
  panel->add(_side_palette);
  _side_palette_host = panel; // Once we have the variables view this becomes a splitter too.

#else
  _side_palette_host = _side_palette;
#endif
}

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


void SqlEditorForm::toolbar_command(const std::string& command)
{
  if (command == "query.new_schema")
    _live_tree->tree_create_object(LiveSchemaTree::Schema, "", "");
  else if (command == "query.new_table")
    _live_tree->tree_create_object(LiveSchemaTree::Table, "", "");
  else if (command == "query.new_view")
    _live_tree->tree_create_object(LiveSchemaTree::View, "", "");

  // TODO: Update this to new procedure and new function once the tool bar is updated
  else if (command == "query.new_routine")
    _live_tree->tree_create_object(LiveSchemaTree::Procedure, "", "");
}


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


mforms::View* SqlEditorForm::get_sidebar()
{
  return _live_tree->get_sidebar();
}

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

mforms::View *SqlEditorForm::get_side_palette()
{
  return _side_palette_host;
}

//--------------------------------------------------------------------------------------------------
// Editor Toolbar

static void toggle_continue_on_error(SqlEditorForm *sql_editor_form)
{
  sql_editor_form->continue_on_error(!sql_editor_form->continue_on_error());
}

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

static void toggle_show_special_chars(mforms::ToolBarItem *item, SqlEditorForm *sql_editor_form)
{
  sql_editor_form->active_sql_editor()->show_special_chars(item->get_checked());
}

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

static void toggle_word_wrap(mforms::ToolBarItem *item, SqlEditorForm *sql_editor_form)
{
  sql_editor_form->active_sql_editor()->enable_word_wrap(item->get_checked());
}

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

static void show_find_panel_for_active_editor(SqlEditorForm *sql_editor_form)
{
  Sql_editor::Ref editor(sql_editor_form->active_sql_editor());
  if (editor)
    editor->get_editor_control()->show_find_panel(false);
}

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

boost::shared_ptr<mforms::ToolBar> SqlEditorForm::setup_editor_toolbar()
{
  boost::shared_ptr<mforms::ToolBar> tbar(new mforms::ToolBar(mforms::SecondaryToolBar));
  mforms::ToolBarItem *item;
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.openFile");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_open.png"));
  item->set_tooltip(_("Open a script file in this editor"));
  scoped_connect(item->signal_activated(),boost::bind((void (SqlEditorForm::*)(const std::string&,bool))&SqlEditorForm::open_file, this, "", false));
  tbar->add_item(item);
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.saveFile");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_save.png"));
  item->set_tooltip(_("Save the script to a file."));
  scoped_connect(item->signal_activated(),boost::bind(&SqlEditorForm::save_file, this));
  tbar->add_item(item);
  
  tbar->add_item(mforms::manage(new mforms::ToolBarItem(mforms::SeparatorItem)));
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.execute");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_execute.png"));
  item->set_tooltip(_("Execute the selected portion of the script or everything, if there is no selection"));
  scoped_connect(item->signal_activated(),boost::bind(&SqlEditorForm::run_editor_contents, this, false));
  tbar->add_item(item);
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.execute_current_statement");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_execute-current.png"));
  item->set_tooltip(_("Execute the statement under the keyboard cursor"));
  scoped_connect(item->signal_activated(),boost::bind(&SqlEditorForm::run_editor_contents, this, true));
  tbar->add_item(item);  
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.explain_current_statement");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_explain.png"));
  item->set_tooltip(_("Execute the EXPLAIN command on the statement under the cursor"));
  bool has_json_explain = false;
  {
    int major, minor;
    if ((sscanf(_connection_details["dbmsProductVersion"].c_str(), "%i.%i", &major, &minor) == 2
        && major > 5) || (major == 5 && minor >= 6))
      has_json_explain = true;
  }
  if (has_json_explain)
  {
    _wbsql->get_cmdui()->scoped_connect(item->signal_activated(),
                                        boost::bind((void (wb::CommandUI::*)(const std::string&))&wb::CommandUI::activate_command, 
                                                    _wbsql->get_cmdui(), "plugin:wb.sqlide.visual_explain"));
  }
  else
    scoped_connect(item->signal_activated(),boost::bind(&SqlEditorForm::explain_current_statement, this));
  tbar->add_item(item);  
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.cancel");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_stop.png"));
  item->set_tooltip(_("Stop the query being executed (the connection to the DB server will not be restarted and any open transactions will remain open)"));
  scoped_connect(item->signal_activated(),boost::bind(&SqlEditorForm::cancel_query, this));
  tbar->add_item(item);  
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
  item->set_name("query.stopOnError");
  item->set_alt_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_stop-on-error-on.png"));
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_stop-on-error-off.png"));
  item->set_tooltip(_("Toggle whether execution of SQL script should continue after failed statements"));
  scoped_connect(item->signal_activated(),boost::bind(toggle_continue_on_error, this));
  
  tbar->add_item(item);  
  
  tbar->add_item(mforms::manage(new mforms::ToolBarItem(mforms::SeparatorItem)));
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.commit");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_commit.png"));
  item->set_tooltip(_("Commit the current transaction.\nNOTE: all query tabs in the same connection share the same transaction. To have independent transactions, you must open a new connection."));
  scoped_connect(item->signal_activated(),boost::bind(&SqlEditorForm::commit, this));
  tbar->add_item(item);  
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.rollback");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_rollback.png"));
  item->set_tooltip(_("Rollback the current transaction.\nNOTE: all query tabs in the same connection share the same transaction. To have independent transactions, you must open a new connection."));
  scoped_connect(item->signal_activated(),boost::bind(&SqlEditorForm::rollback, this));
  tbar->add_item(item);  
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
  item->set_name("query.autocommit");
  item->set_alt_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_autocommit-on.png"));
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_autocommit-off.png"));
  item->set_tooltip(_("Toggle autocommit mode. When enabled, each statement will be commited immediately.\nNOTE: all query tabs in the same connection share the same transaction. To have independent transactions, you must open a new connection."));
  scoped_connect(item->signal_activated(),boost::bind(&SqlEditorForm::toggle_autocommit, this));
  tbar->add_item(item);  
  
  tbar->add_item(mforms::manage(new mforms::ToolBarItem(mforms::SeparatorItem)));
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.beautify");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_beautifier.png"));
  item->set_tooltip(_("Beautify/reformat the SQL script"));
  _wbsql->get_cmdui()->scoped_connect(item->signal_activated(),boost::bind((void (wb::CommandUI::*)(const std::string&))&wb::CommandUI::activate_command, 
                                                                           _wbsql->get_cmdui(), "plugin:wb.sqlide.enbeautificate"));
  tbar->add_item(item);  
  
  /*
   item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
   item->set_name("query.beautify_current_statement");
   item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_beautifier_current.png"));
   item->set_tooltip(_("Beautify/reformat the current SQL statement"));
   item->signal_activated().connect(sigc::bind(sigc::mem_fun(this, &bec::RefreshUI::do_partial_ui_refresh), (int)ShowFindPanel));
   tbar->add_item(item);  
   */
  
  tbar->add_item(mforms::manage(new mforms::ToolBarItem(mforms::SeparatorItem)));
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("query.search");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_find.png"));
  item->set_tooltip(_("Show the Find panel for the editor"));
  scoped_connect(item->signal_activated(), boost::bind(show_find_panel_for_active_editor, this));
  tbar->add_item(item);  
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
  item->set_name("query.toggleInvisible");
  item->set_alt_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_special-chars-on.png"));
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_special-chars-off.png"));
  item->set_tooltip(_("Toggle display of invisible characters (spaces, tabs, newlines)"));
  scoped_connect(item->signal_activated(),boost::bind(toggle_show_special_chars, item, this));
  tbar->add_item(item);
  
  item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
  item->set_name("query.toggleWordWrap");
  item->set_alt_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_word-wrap-on.png"));
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_word-wrap-off.png"));
  item->set_tooltip(_("Toggle wrapping of long lines (keep this off for large files)"));
  scoped_connect(item->signal_activated(),boost::bind(toggle_word_wrap, item, this));
  tbar->add_item(item);

  return tbar;
}


void SqlEditorForm::set_editor_tool_items_enbled(const std::string &name, bool flag)
{
  for (Sql_editors::iterator editor = _sql_editors.begin(); editor != _sql_editors.end(); ++editor)
    (*editor)->toolbar->set_item_enabled(name, flag);
}


void SqlEditorForm::set_editor_tool_items_checked(const std::string &name, bool flag)
{
  for (Sql_editors::iterator editor = _sql_editors.begin(); editor != _sql_editors.end(); ++editor)
    (*editor)->toolbar->set_item_checked(name, flag);
}

void SqlEditorForm::set_tool_item_checked(const std::string& name, bool flag)
{
  _toolbar->set_item_checked(name, flag);
}



void SqlEditorForm::show_export_recordset(int editor_index, Recordset::Ptr rs_ptr)
{
  try
  {
    RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs)
    {
      grt::ValueRef option(_grtm->get_app_option("Recordset:LastExportPath"));
      std::string path = option.is_valid() ? grt::StringRef::cast_from(option) : "";
      option = _grtm->get_app_option("Recordset:LastExportExtension");
      std::string extension = option.is_valid() ? grt::StringRef::cast_from(option) : "";
      InsertsExportForm exporter(0/*mforms::Form::main_form()*/, rs_ref, extension);
      exporter.set_title(_("Export Resultset"));
      if (!path.empty())
        exporter.set_path(path);
      path = exporter.run();
      if (path.empty())
        _grtm->replace_status_text(_("Export resultset canceled"));
      else
      {
        _grtm->replace_status_text(strfmt(_("Exported resultset to %s"), path.c_str()));
        _grtm->set_app_option("Recordset:LastExportPath", grt::StringRef(path));
        extension = base::extension(path);
        if (!extension.empty() && extension[0] == '.')
          extension = extension.substr(1);
        if (!extension.empty())
          _grtm->set_app_option("Recordset:LastExportExtension", grt::StringRef(extension));    
      }
    }
  }
  catch (const std::exception &exc)
  {
    mforms::Utilities::show_error("Error exporting recordset", exc.what(), "OK");
  }
}


void SqlEditorForm::show_import_recordset(int editor_index, Recordset::Ptr rs_ptr)
{
  try
  {
    RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs)
    {
      grt::BaseListRef args(_grtm->get_grt());
      
      db_query_ResultsetRef rset;
      db_query_QueryEditorRef qeditor(db_query_QueryEditorRef::cast_from(_sql_editors[editor_index]->editor->grtobj()));
      for (size_t c = qeditor->resultsets().count(), i = 0; i < c; i++)
      {
        if (qeditor->resultsets()[i]->get_data()->recordset.get() == rs)
        {
          rset = qeditor->resultsets()[i];
          break;
        }
      }
      
      if (rset.is_valid())
      {
        args.ginsert(rset);
        grt::Module *module = _grtm->get_grt()->get_module("SQLIDEUtils");
        if (module)
          module->call_function("importRecordsetDataFromFile", args);
      }
    }
  }
  catch (const std::exception &exc)
  {
    mforms::Utilities::show_error("Error importing recordset", exc.what(), "OK");
  }
}


bool SqlEditorForm::run_live_object_alteration_wizard(const std::string &alter_script, bec::DBObjectEditorBE* obj_editor, RowId log_id,
                                                      const std::string &log_context)
{
  on_sql_script_run_error.disconnect_all_slots();
  on_sql_script_run_progress.disconnect_all_slots();
  on_sql_script_run_statistics.disconnect_all_slots();
  
  // Determine the current online DDL settings, so the wizard can initialize its local settings.
  std::string algorithm;
  wbsql()->get_wbui()->get_wb_options_value("", "DbSqlEditor:OnlineDDLAlgorithm", algorithm);
  std::string lock;
  wbsql()->get_wbui()->get_wb_options_value("", "DbSqlEditor:OnlineDDLLock", lock);
  
  SqlScriptRunWizard wizard(_grtm, rdbms()->version(), algorithm, lock);
  if (obj_editor)
    wizard.regenerate_script = boost::bind(&SqlEditorTreeController::generate_alter_script, get_live_tree(),
                                           obj_editor->get_dbobject(), _1, _2);
  scoped_connection c1(on_sql_script_run_error.connect(boost::bind(&SqlScriptApplyPage::on_error, wizard.apply_page, _1, _2, _3)));
  scoped_connection c2(on_sql_script_run_progress.connect(boost::bind(&SqlScriptApplyPage::on_exec_progress, wizard.apply_page, _1)));
  scoped_connection c3(on_sql_script_run_statistics.connect(boost::bind(&SqlScriptApplyPage::on_exec_stat, wizard.apply_page, _1, _2)));
  
  std::string errors;
  
  scoped_connection c4(on_sql_script_run_error.connect(boost::bind(&SqlEditorForm::sql_script_apply_error, this, _1, _2, _3, boost::ref(errors))));
  scoped_connection c5(on_sql_script_run_progress.connect(boost::bind(&SqlEditorForm::sql_script_apply_progress, this, _1)));
  scoped_connection c6(on_sql_script_run_statistics.connect(boost::bind(&SqlEditorForm::sql_script_stats, this, _1, _2)));
  
  wizard.values().gset("sql_script", alter_script);
  wizard.apply_page->apply_sql_script= boost::bind(&SqlEditorForm::apply_object_alter_script, this, _1, obj_editor, log_id);
  wizard.run_modal();
  
  if (wizard.applied() && !wizard.has_errors())
    set_log_message(log_id, DbSqlEditorLog::OKMsg, _("Changes applied"), log_context, "");
  else
    set_log_message(log_id, DbSqlEditorLog::ErrorMsg, errors, log_context, "");
  
  return wizard.applied() && !wizard.has_errors();
}


int SqlEditorForm::sql_script_apply_error(long long code, const std::string& msg, const std::string& stmt, std::string &errors)
{
  if (code >= 0)
    errors.append(strfmt("Error %li: ", (long)code));
  errors.append(msg).append("\n");
  if (!stmt.empty())
    errors.append("SQL Statement:\n").append(stmt).append("\n\n");
  return 0;
}


int SqlEditorForm::sql_script_apply_progress(float)
{
  return 0;
}


int SqlEditorForm::sql_script_stats(long, long)
{
  return 0;
}

