/* 
 * Copyright (c) 2007, 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
 */
#ifndef _WB_SQL_EDITOR_FORM_H_
#define _WB_SQL_EDITOR_FORM_H_

// renamed from db_sql_editor_be.h

#include "workbench/wb_backend_public_interface.h"
#include "sqlide/recordset_be.h"
#include "sqlide/sql_editor_be.h"
#include "sqlide/db_sql_editor_log.h"
#include "sqlide/db_sql_editor_history_be.h"
#include "cppdbc.h"
#include "grts/structs.workbench.h"
#include "grts/structs.db.mgmt.h"
#include "base/file_utilities.h"
#include "base/ui_form.h"
#include "base/notifications.h"
#include "grt/refresh_ui.h"
#include "sqlide/wb_context_sqlide.h"
#include "sqlide/wb_live_schema_tree.h"
#include <boost/enable_shared_from_this.hpp>

#include "mforms/task_sidebar.h"

namespace mforms {
  class ToolBar;
  class Splitter;
  class TabView;
  class HyperText;
};

namespace bec
{
  class DBObjectEditorBE;
}


class QuerySidePalette;

typedef std::vector<Recordset::Ref> Recordsets;
typedef boost::shared_ptr<std::vector<Recordset::Ref> > RecordsetsRef;

#define Db_sql_editor SqlEditorForm
class MYSQLWBBACKEND_PUBLIC_FUNC SqlEditorForm : public bec::UIForm, public bec::RefreshUI, base::Observer,
                                                public wb::LiveSchemaTree::FetchDelegate, public wb::LiveSchemaTree::Delegate,
                                                public boost::enable_shared_from_this<SqlEditorForm>
{
public:
#if defined(ENABLE_TESTING)
  friend class Db_sql_editor_tester;
#endif

  enum
  {
    RefreshEditor,             // refresh the text editor using data from backend
    RefreshEditorBackend,      // save text editor contents to backend so that it can be saved to file
    RefreshEditorTitle,        // refresh the caption of active editor
    RefreshRecordsetTitle,     // refresh caption of active recordset
    RunCurrentScript,          // running sql needs to be initiated by front-end
    RunCurrentStatement,       // run only statement under cursor position (if there any statement at all)
    ShowFindPanel,             // show the Find panel for the active editor
    ShowSpecialCharacters,     // show special (tab, space, newline etc) in active editor
    HideSpecialCharacters,     // hide special chars
    RefreshMainTitle,          // refresh the caption of sql editor tab
    SaveRecordsetChanges,      // commit changes being made to active recordset (as if focus was removed)
    DiscardRecordsetChanges   // revert changes being made to active recordset (as if esc was pressed)
  };

public:
  typedef boost::shared_ptr<SqlEditorForm> Ref;
  typedef boost::weak_ptr<SqlEditorForm> Ptr;
  static SqlEditorForm::Ref create(wb::WBContextSQLIDE *wbsql, const db_mgmt_ConnectionRef &conn);
  
protected:
  SqlEditorForm(wb::WBContextSQLIDE *wbsql, const db_mgmt_ConnectionRef &conn);
public:
  virtual ~SqlEditorForm();

public:
  virtual bool close();
  virtual bool is_main_form() { return true; }
  virtual std::string get_form_context_name() const;
  
  virtual mforms::MenuBar *get_menubar();
  virtual mforms::ToolBar *get_toolbar();
  
  void auto_save();
  void save_workspace(const std::string &workspace_name, bool is_autosave);
  bool load_workspace(const std::string &workspace_name);
  
public:
  bec::GRTManager * grt_manager() const { return _grtm; }
  wb::WBContextSQLIDE *wbsql() const { return _wbsql; }
  void context_ui(wb::WBContextUI *val);

  void update_menu_and_toolbar();
  void validate_menubar();
private:
  wb::WBContextSQLIDE *_wbsql;
  wb::WBContextUI *_context_ui;
  bec::GRTManager *_grtm;
  mforms::MenuBar *_menu;
  mforms::ToolBar *_toolbar;
  grt::DictRef _options;
  std::string _connection_info;
  base::LockFile *_autosave_lock;
  std::string _autosave_path;
  bool _autosave_disabled;
  bool _loading_workspace;
  bool _close_done;

  void activate_command(const std::string &command);
  void rename_autosave_files(int from, int to);

public:
  db_mgmt_RdbmsRef rdbms();

public:

  Sql_editor::Ref active_sql_editor();
  Sql_editor::Ref sql_editor(int index);
  int sql_editor_index(Sql_editor::Ref);
  std::string sql_editor_path(int index) { return _sql_editors[index]->filename; }
  std::string sql_editor_caption(int index=-1);
  void sql_editor_caption(int new_index, std::string caption);
  bool sql_editor_dirty(int index) { return _sql_editors[index]->dirty && !_sql_editors[index]->is_scratch; }
  bool sql_editor_is_scratch(int index) { return _sql_editors[index]->is_scratch; }
  bool sql_editor_start_collapsed(int index) { return _sql_editors[index]->start_collapsed; }
  void sql_editor_dirty(int index, bool flag);
  bool sql_editor_will_close(int index);
  bool sql_editor_reorder(Sql_editor::Ref, int new_index);
  void sql_editor_open_file(int index, const std::string &file_path, const std::string &encoding= "");
  int sql_editor_index_for_recordset(long long rset);
  RecordsetsRef sql_editor_recordsets(const int index);
  boost::shared_ptr<mforms::ToolBar> sql_editor_toolbar(int index) { return _sql_editors[index]->toolbar; }

private:
  struct Sql_editor_info {
    typedef boost::shared_ptr<Sql_editor_info> Ref;
    
    boost::shared_ptr<mforms::ToolBar> toolbar;
    std::string filename;
    std::string autosave_filename;
    std::string orig_encoding;
    std::string caption;
    Sql_editor::Ref editor;
    RecordsetsRef recordsets;
    Recordset::Ref active_recordset;
    GMutex *recordset_mutex;
    int rs_sequence;
    bool dirty;
    bool is_scratch;
    bool start_collapsed;
    bool busy;
    
    Sql_editor_info() : rs_sequence(0), dirty(false), is_scratch(false), start_collapsed(false), busy(false) {}
  };
  typedef std::vector<Sql_editor_info::Ref> Sql_editors;
  Sql_editors _sql_editors;
  int _sql_editors_serial;
  int _scratch_editors_serial;
  GMutex *_sql_editors_mutex;
  
  boost::shared_ptr<mforms::ToolBar> setup_editor_toolbar();
  void set_editor_tool_items_enbled(const std::string &name, bool flag);
  void set_editor_tool_items_checked(const std::string &name, bool flag);
public:
  void set_tool_item_checked(const std::string &name, bool flag);

  boost::signals2::signal<void (Sql_editor::Ref, bool)> sql_editor_list_changed;

  int run_sql_in_scratch_tab(const std::string &sql, bool reuse_if_possible, bool start_collapsed);
  int add_sql_editor(bool scratch = false, bool start_collapsed = false); // returns index of the added sql_editor
  void remove_sql_editor(int index);
  int sql_editor_count();
  int active_sql_editor_index() { return _active_sql_editor_index; }
  void active_sql_editor_index(int val);
private:
  int _active_sql_editor_index;
  int _updating_sql_editor;
  
  int count_connection_editors(std::string conn_name);

  Sql_editor::Ref active_sql_editor_or_new_scratch();
private:
  void on_sql_editor_text_change();
  void on_sql_editor_text_selection_change();
  void set_sql_editor_text(const std::string &sql);
public:
  std::string caption() const;
  std::string get_title(bool new_editor);

private:
  std::map<std::string, std::string> _connection_details;

  grt::StringRef do_connect(grt::GRT *grt, sql::AuthenticationSet &authset, sql::AuthenticationError *&autherr_ptr);
  grt::StringRef do_disconnect(grt::GRT *grt);
public:
  void connect();
  bool connected() const;
  void finish_startup();
  void title_changed();
  void cancel_query();
  void reset();
  void commit();
  void rollback();
  bool auto_commit();
  void auto_commit(bool value);
  void toggle_autocommit();
  
  void run_sql();
private:
  void do_commit();
public:  
  db_mgmt_ConnectionRef connection_descriptor() const { return _connection; }

private:
  bool get_session_variable(sql::Dbc_connection_handler::Ref &dbc_conn, const std::string &name, std::string &value);
  void cache_sql_mode();
private:
  std::string _sql_mode;

private:
  void create_connection(sql::Dbc_connection_handler::Ref &dbc_conn, db_mgmt_ConnectionRef db_mgmt_conn, sql::AuthenticationSet *authset, bool autocommit_mode);
  void init_connection(sql::Connection* dbc_conn_ref, const db_mgmt_ConnectionRef& connectionProperties, sql::Dbc_connection_handler::Ref& dbc_conn);
  void close_connection(sql::Dbc_connection_handler::Ref &dbc_conn);
  bec::GMutexLock ensure_valid_dbc_connection(sql::Dbc_connection_handler::Ref &dbc_conn, GMutex *dbc_conn_mutex);
  bec::GMutexLock ensure_valid_aux_connection();
  bec::GMutexLock ensure_valid_usr_connection();

private:
  bec::TimerActionThread *_keep_alive_thread;
  GMutex *_keep_alive_thread_mutex;
private:
  void send_message_keep_alive();
  void reset_keep_alive_thread();
  
  db_mgmt_ConnectionRef _connection;
  // connection for maintenance operations, fetching schema contents & live editors (DDL only)
  sql::Dbc_connection_handler::Ref _aux_dbc_conn;
  GMutex *_aux_dbc_conn_mutex;
  // connection for running sql scripts
  sql::Dbc_connection_handler::Ref _usr_dbc_conn;
  GMutex *_usr_dbc_conn_mutex;

public:
  void exec_sql(std::string &sql, Sql_editor::Ref editor, bool sync, bool wrap_with_non_std_delimiter= false, bool dont_add_limit_clause= false);
  void exec_sql_retaining_editor_contents(const std::string &sql_script, Sql_editor::Ref editor, bool sync, bool dont_add_limit_clause= false);

  RecordsetsRef exec_sql_returning_results(const std::string &sql_script, bool dont_add_limit_clause);
  
  void explain_sql();
  void explain_current_statement();
  bool is_running_query();
private:
  enum ExecFlags {
    Retaining = (1<<0), 
    Wrap_with_non_std_delimiter = (1<<1),
    Dont_add_limit_clause = (1<<2),
    Show_warnings = (1<<3)
  };
  grt::StringRef do_exec_sql(grt::GRT *grt, Ptr self_ptr, std::string &sql, Sql_editor::Ref editor, ExecFlags flags, RecordsetsRef result_list);
  void do_explain_sql(const std::string &sql);
public:
  GrtThreadedTask::Ref exec_sql_task;
private:
  int on_exec_sql_finished();
  bool _is_running_query;

public:
  bool continue_on_error() { return _continue_on_error; }
  void continue_on_error(bool val);
private:
  bool _continue_on_error;

public:
  int recordset_count(int editor);
  Recordset::Ref recordset(int editor, int index);
  void active_recordset(int editor, Recordset::Ref value);
  Recordset::Ref active_recordset(int editor);
  bool recordset_reorder(int editor, Recordset::Ref value, int new_index);
public:
  boost::signals2::signal<void (int, Recordset::Ref, bool)> recordset_list_changed;
  //delme sigc::signal<int, long long> close_recordset_ui;
private:
  void on_close_recordset(Recordset::Ptr rs_ptr);
private:
  void apply_changes_to_recordset(Recordset::Ptr rs_ptr);
  bool run_data_changes_commit_wizard(Recordset::Ptr rs_ptr);
  void apply_data_changes_commit(std::string &sql_script_text, Recordset::Ptr rs_ptr);
  void recall_recordset_query(Recordset::Ptr rs_ptr);
  void update_editor_title_schema(const std::string& schema);

public:
  void show_export_recordset(int editor_index, Recordset::Ptr rs_ptr);
  
  bool can_close();
  bool can_close_(bool interactive);

public:
  typedef boost::signals2::signal<int (const std::string&)> SqlEditorTextInsertSignal;
  SqlEditorTextInsertSignal sql_editor_text_insert_signal;
  int call_sql_editor_text_insert_signal(const std::string& str){return *sql_editor_text_insert_signal(str);}

public:
  void new_sql_script_file();
  void new_sql_scratch_area(bool start_collapsed = false);
  void open_file(const std::string &path, bool in_new_tab);
  void open_file(const std::string &path = "") { open_file(path, true); }
  void save_file();
  bool save_sql_script_file(const std::string &file_path, int editor_index);
  void revert_sql_script_file();

public:
  boost::signals2::signal<int (int)> sql_editor_new_ui;

public:
  void active_schema(const std::string &value);
  std::string active_schema() const;
  int active_schema_index() const;
private:
  void cache_active_schema_name();

public:
  wb::LiveSchemaTree *get_schema_tree();
  wb::LiveSchemaTree *get_base_schema_tree();
  wb::LiveSchemaTree *get_filtered_schema_tree();
  void request_refresh_schema_tree();
  
private:
  // LiveSchemaTree::FetchDelegate
  virtual std::list<std::string> fetch_schema_list();
  virtual bool fetch_schema_contents(bec::NodeId node, const std::string &schema_name, bool include_column_data, const wb::LiveSchemaTree::SchemaContentArrivedSlot &arrived_slot);
  virtual bool fetch_object_details(wb::LiveSchemaTree::ObjectType obj_type, const std::string &schema_name, const std::string &obj_name, short flags, const wb::LiveSchemaTree::ObjectDetailsArrivedSlot &arrived_slot);

private:
  // LiveSchemaTree::Delegate
  virtual void tree_refresh() { refresh_schema_tree(true); }
  virtual void tree_activate_objects(const std::string&, const std::vector<wb::LiveSchemaTree::ChangeRecord>& changes);
  virtual void tree_alter_objects(const std::vector<wb::LiveSchemaTree::ChangeRecord>& changes);
  virtual void tree_drop_objects(const std::vector<wb::LiveSchemaTree::ChangeRecord>& changes);
  virtual void tree_create_object(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name);  
  
private:
  grt::StringRef do_fetch_live_schema_contents(grt::GRT *grt, Ptr self_ptr, bec::NodeId node, const std::string &schema_name, bool include_column_data, wb::LiveSchemaTree::SchemaContentArrivedSlot arrived_slot);
  void notify_of_fetched_schema_contents(Ptr self_ptr, bec::NodeId node);
  void fetch_column_data(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::ViewNode& view_node);
  void fetch_trigger_data(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::TableNode& table_node);
  void fetch_index_data(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::TableNode& table_node);
  void fetch_foreign_key_data(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::TableNode& table_node);

  bool is_editable_select(const std::string& statement, std::string &schema_name, std::string& table_name, std::string& error);
  void table_details_arrived(const wb::LiveSchemaTree::SchemaNode &schema_node, const wb::LiveSchemaTree::ObjectNode *obj_node, wb::LiveSchemaTree::ObjectType obj_type, wb::LiveSchemaTree::TableNode *table_node);
private:
  wb::LiveSchemaTree *_schema_tree;
  wb::LiveSchemaTree _base_schema_tree;
  wb::LiveSchemaTree _filtered_schema_tree;
  GrtThreadedTask::Ref live_schema_fetch_task;
  GMutex *_schema_contents_mutex;

  mforms::TaskSidebar* _side_bar;
  mforms::Splitter *_side_splitter;
  mforms::TabView *_info_tabview;
  mforms::HyperText *_object_info;
  mforms::HyperText *_session_info;
  
  mforms::View* _side_palette_host;
  QuerySidePalette* _side_palette;
private:
  void do_alter_live_object(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name);
  void simple_drop_live_object(wb::LiveSchemaTree::ObjectType type, const std::string schema_name, const std::string obj_name);
  
  std::string get_object_ddl_script(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name);
  void apply_object_alter_script(std::string &alter_script, bec::DBObjectEditorBE* obj_editor, RowId log_id);
  void refresh_live_object_in_editor(bec::DBObjectEditorBE* obj_editor, bool using_old_name);
  void refresh_live_object_in_overview(wb::LiveSchemaTree::ObjectType type, const std::string schema_name, const std::string old_obj_name, const std::string new_obj_name);
  
  bool parse_ddl_into_catalog(db_mgmt_RdbmsRef rdbms, db_CatalogRef client_state_catalog, const std::string &obj_descr, const std::string &ddl_script, std::string sql_mode);
  
  db_SchemaRef create_new_schema(db_CatalogRef owner);
  db_TableRef create_new_table(db_SchemaRef owner);
  db_ViewRef create_new_view(db_SchemaRef owner);
  db_RoutineRef create_new_routine(db_SchemaRef owner);
public:
  void schema_object_activated(const std::string &action, wb::LiveSchemaTree::ObjectType type, const std::string &schema, const std::string &name);
  bool apply_changes_to_object(bec::DBObjectEditorBE* obj_editor, bool dry_run);
  virtual void notify_of_ui_form_creation(bec::UIForm *form);
  bool run_live_object_alteration_wizard(const std::string &alter_script, bec::DBObjectEditorBE* obj_editor, RowId log_id, const std::string &log_context);
public:
  void create_live_table_stubs(bec::DBObjectEditorBE *table_editor);
  bool expand_live_table_stub(bec::DBObjectEditorBE *table_editor, const std::string &schema_name, const std::string &obj_name);

private:
  typedef boost::signals2::signal<int (long long, const std::string&, const std::string&),boost::signals2::last_value<int> > Error_cb;
  typedef boost::signals2::signal<int (float),boost::signals2::last_value<int> > Batch_exec_progress_cb;
  typedef boost::signals2::signal<int (long, long),boost::signals2::last_value<int> > Batch_exec_stat_cb;
  Error_cb on_sql_script_run_error;
  Batch_exec_progress_cb on_sql_script_run_progress;
  Batch_exec_stat_cb on_sql_script_run_statistics;

  
  int sql_script_apply_error(long long, const std::string&, const std::string&, std::string&);
  int sql_script_apply_progress(float);
  int sql_script_stats(long, long);
public:
  bool activate_live_object(GrtObjectRef object);
  bool create_live_object(GrtObjectRef object_type, std::string owner_name, std::string obj_name);
  bool drop_live_object(GrtObjectRef object_type, std::string owner_name, std::string obj_name);
  void refresh_schema_tree(bool hard_refresh);
private:
  grt::StringRef do_refresh_schema_tree_safe(grt::GRT *grt, Ptr self_ptr, bool hard_refresh);
  void do_refresh_schema_tree(bool hard_refresh);
  void do_refresh_schema_tree_ui(const std::list<std::string>& schema_list, bool hard_refresh);
private:
  bool _is_refreshing_schema_tree;
  GrtThreadedTask::Ref live_schemata_refresh_task;

public:
  DbSqlEditorLog::Ref log() { return _log; }
  DbSqlEditorHistory::Ref history() { return _history; }
  std::string restore_sql_from_history(int entry_index, std::list<int> &detail_indexes);
  int exec_sql_error_count() { return _exec_sql_error_count; }
  
  boost::function<void (const std::string&, bool)> output_text_slot;
protected:
  DbSqlEditorLog::Ref _log;
  DbSqlEditorHistory::Ref _history;
  std::string _title;

public:
  void get_log_event_details(int log_event_no, int &log_event_type_code, std::string &log_event_time, std::string &log_event_action, std::string &log_event_message);
  
  std::string get_text_for_actions(const std::list<int> &indexes, bool query_only);
protected:
  RowId add_log_message(int msg_type, const std::string &msg, const std::string &context, const std::string &duration);
  void set_log_message(RowId log_message_index, int msg_type, const std::string &msg, const std::string &context, const std::string &duration);
  void refresh_log_messages(bool ignore_last_message_timestamp);
private:
  bool _has_pending_log_messages;
  time_t _last_log_message_timestamp;
  int _exec_sql_error_count;

protected:
  int _progress_status_update_interval;

private:
  bec::MenuItemList get_live_catalog_menu_items(const std::string &schema, const std::string &object, 
                                                const std::string &type);
  bool call_live_catalog_menu_item(const std::string &item,
                                   const std::string &schema, const std::string &object, 
                                   const std::string &type);

  bec::MenuItemList recordset_popup_menu_items(const std::vector<int> &rows, int column, Recordset::Ptr rs_ptr);
  bool call_recordset_popup_menu_item(const std::string &item, const std::vector<int> &rows,
                                      int column, Recordset::Ptr rs_ptr);

  virtual void handle_notification(const std::string &name, void *sender, base::NotificationInfo &info);
  void setup_sidebars();
  
  void schema_row_selected();
  void side_bar_filter_changed(const std::string& filter);
  
public:
  void toolbar_command(const std::string& command);

  bool save_snippet(const std::string &text);

  mforms::View *get_sidebar();
  mforms::View *get_side_palette();
};


#endif /* _WB_SQL_EDITOR_FORM_H_ */
