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

#pragma once

#ifndef _WIN32
#include <vector>
#endif

#include "base/ui_form.h"
#include "grt/grt_manager.h"
#include "base/notifications.h"

#include "wb_context_names.h"

#include "grts/structs.workbench.h"
#include "wb_backend_public_interface.h"

#include <grtpp_undo_manager.h>

#include "mforms/utilities.h"
#include "base/trackable.h"
#include "base/threading.h"


#include "wb_version.h"

#define WBContext_VERSION 5

#define WB_DBOBJECT_DRAG_TYPE "mysql-wb.DatabaseObject"
#define WB_CONTROL_DRAG_TYPE "mysql-wb.control"

namespace mdc {
  class CanvasView;
  class CanvasItem;
};

namespace bec {
  class Clipboard;
  class IconManager;
};

class SqlEditorForm;

namespace wb {
  class WBContextUI;
  class WBContextModel;
  class WBContextSQLIDE;
  class WorkbenchImpl;
  class WBComponent;

  class ModelFile;
  
  class TunnelManager;

  enum RefreshType 
  {
    RefreshNeeded, // Front end should schedule a refresh flush asap (usually called in worker thread).
    RefreshNothing,
    RefreshSchemaNoReload,
    RefreshNewDiagram,
    RefreshSelection,
    RefreshCloseEditor, // argument: object-id (close all if "")
    
    RefreshNewModel,

    RefreshOverviewNodeInfo,    // argument: node-id, overview ptr (bec::UIForm*)
    RefreshOverviewNodeChildren,// argument: node-id, overview ptr 

    RefreshDocument,
    RefreshCloseDocument,
    RefreshZoom,
    RefreshTimer,

    RefreshFinishEdits // Force all ongoing edit operations (eg in TreeView cells) to be committed
  };

  struct MYSQLWBBACKEND_PUBLIC_FUNC WBPaperSize
  {
    std::string name;
    std::string caption;
    double width;
    double height;
    bool margins_set;
    double margin_top;
    double margin_bottom;
    double margin_left;
    double margin_right;
    std::string description;
  };

  // basic toolbar names
  #define WB_TOOLBAR_OPTIONS "options"

  // main view types
  #define WB_MAIN_VIEW_DB_QUERY "dbquery"
  

  class ModelDiagramForm;
  class FindDialogBE;

  enum PageOrientation
  {
    Landscape,
    Portrait
  };

  
  enum RequestInputFlag
  {
    InputPassword = (1<<0)
  };
  
  
  struct MYSQLWBBACKEND_PUBLIC_FUNC WBFrontendCallbacks
  {
    // Args: type, title, file extensions
    boost::function<std::string (std::string,std::string,std::string)> show_file_dialog;

    // Show some text in the application's status bar: must be thread-safe
    boost::function<void (std::string)> show_status_text;

    // Open an editor
    // Args: grtmanager, module containing plugin, editor dll, editor class, edited object
    boost::function<NativeHandle (bec::GRTManager*, grt::Module*, std::string, std::string, grt::BaseListRef, bec::GUIPluginFlags)> open_editor;
    // Show/Hide an editor
    // Args: editor handle (e.g: window handle)
    boost::function<void (NativeHandle)> show_editor;
    boost::function<void (NativeHandle)> hide_editor;

    // Execute a built-in command
    boost::function<void (std::string)> perform_command;

    // Create a new diagram.
    boost::function<mdc::CanvasView* (const model_DiagramRef&)> create_diagram;
    // Destroy a previously created canvas view
    boost::function<void (mdc::CanvasView*)> destroy_view;
    // Signals the current view has been changed
    boost::function<void (mdc::CanvasView*)> switched_view;

    // Open the named type of main view tab with the given form object. ownership is passed to frontend
    // Args: type (eg query), bec::UIForm*
    boost::function<void (std::string, boost::shared_ptr<bec::UIForm>) > create_main_form_view;
    boost::function<void (bec::UIForm*)> destroy_main_form_view;
    
    // The tool for the view has been changed
    boost::function<void (mdc::CanvasView*)> tool_changed;

    // Refresh interface
    boost::function<void (RefreshType, std::string, NativeHandle)> refresh_gui;
    boost::function<void (bool)> lock_gui;

    // Closes the application
    boost::function<bool ()> quit_application;
  };


  struct MYSQLWBBACKEND_PUBLIC_FUNC WBOptions
  {
    std::string basedir;
    std::string plugin_search_path;
    std::string struct_search_path;
    std::string module_search_path;
    std::string library_search_path;
    std::string cdbc_driver_search_path;
    std::string user_data_dir;
    std::string open_at_startup_type; // model, query, admin, script
    std::string open_at_startup;
    std::string open_connection;
    std::string run_at_startup; // script to be executed when started
    std::string run_language; // language of the script in run_at_startup
    bool force_sw_rendering;
    bool force_opengl_rendering;
    bool verbose;
    bool quit_when_done;
    bool testing;     // True if we are currently running unit tests.
    bool init_python; // True by default. Can be switched off for testing.
    bool full_init; // True by default. Should be switched off when the options are created for an already running instance of WB.

    WBOptions();
    bool parse_args(char **argv, int argc, int *retval = NULL);
  };

  
#define FOREACH_COMPONENT(list, iter) for (std::vector<WBComponent*>::iterator iter= list.begin(); iter != list.end(); ++iter)

  class MYSQLWBBACKEND_PUBLIC_FUNC WBContext : public base::trackable, base::Observer
  {
    friend class WorkbenchImpl;
    friend class WBComponent;
    friend class WBContextUI;

   public:
    WBContext(WBContextUI *ui, bool verbose = false);
    virtual ~WBContext();

    bool software_rendering_enforced();
    bool opengl_rendering_enforced();

    bool init_(WBFrontendCallbacks *callbacks, WBOptions *options);
    void init_finish_(WBOptions *options);
    void finalize();
    
    bool is_commercial();
    WBContextUI *get_ui() { return _uicontext; }

    bec::UIForm *get_active_form();
    bec::UIForm *get_active_main_form();

    WBContextModel *get_model_context() { return _model_context; }
    WBContextSQLIDE *get_sqlide_context() { return _sqlide_context; }
    
    // Document handling.
    void new_document();
    bool can_close_document(); // returns false for cancelled
    bool close_document(); 
    
    void close_document_finish();
    void new_model_finish();
    
    // save document

    bool save_as(const std::string &path);

    std::string get_filename() const;

    void report_bug(const std::string &errorInfo);

    // plugins
    void execute_plugin(const std::string &plugin_name, 
                        const bec::ArgumentPool &argpool= bec::ArgumentPool());

    void update_plugin_arguments_pool(bec::ArgumentPool &args);

    // DB Querying
    boost::shared_ptr<SqlEditorForm> add_new_query_window(const db_mgmt_ConnectionRef &target, bool restore_session = true);
    boost::shared_ptr<SqlEditorForm> add_new_query_window();
    
    // Admin
    void add_new_admin_window(const db_mgmt_ConnectionRef &target);

    // Generic plugin tabs
    void add_new_plugin_window(const std::string &plugin_id, const std::string &caption);

    // GUI Plugin
    void register_builtin_plugins(grt::ListRef<app_Plugin> plugins);

    void close_gui_plugin(NativeHandle handle);


    //
    void request_refresh(RefreshType type, const std::string &str, NativeHandle ptr= (NativeHandle)0);

    const std::string &get_user_datadir() const { return _user_datadir; }
    // TODO: Temporary solution need to make ModelFile grt class
    workbench_DocumentRef openModelFile(const std::string &file);
    std::string getTempDir();
    int closeModelFile();
    std::string getDbFilePath();

    bool open_document(const std::string &file);
    void open_script_file(const std::string &file);
    void open_recent_document(int index);
    bool has_unsaved_changes();
    bool save_changes();
    
    bool open_file_by_extension(const std::string &path, bool interactive);

    bec::PluginManager *get_plugin_manager() { return _plugin_manager; }
    template<class C> C *get_component()
    {
      return dynamic_cast<C*>(get_component_named(C::name()));
    }

    WBComponent *get_component_named(const std::string &name);

    WBComponent *get_component_handling(const model_ObjectRef &object);

    void foreach_component(const boost::function<void (WBComponent*)> &slot);

    bec::GRTManager *get_grt_manager() const { return _manager; }
    grt::GRT *get_grt() const { return _manager->get_grt(); }
    boost::shared_ptr<WorkbenchImpl> get_workbench() { return _workbench; };

    bec::Clipboard *get_clipboard() const { return _clipboard; }

    workbench_WorkbenchRef get_root();
    workbench_DocumentRef get_document();
    grt::DictRef get_wb_options();

    std::string get_datadir() const { return _datadir; }
    
    bool cancel_idle_tasks();
    void flush_idle_tasks();

 // utilities for error reporting
    void show_exception(const std::string &operation, const std::exception &exc);
    void show_exception(const std::string &operation, const grt::grt_runtime_error &exc);

        
   template<class R>
    R execute_in_main_thread(const std::string &name, 
                             const boost::function<R ()> &function) THROW (grt::grt_runtime_error)
    {
      return _manager->get_dispatcher()->call_from_main_thread/*<R>*/(function, true, false);
    }
    void execute_in_main_thread(const std::string &name, 
                              const boost::function<void ()> &function, bool wait) THROW (grt::grt_runtime_error);
 
    grt::ValueRef execute_in_grt_thread(const std::string &name, 
                                            const boost::function<grt::ValueRef (grt::GRT*)> &function) THROW (grt::grt_runtime_error);

    void execute_async_in_grt_thread(const std::string &name, 
                                     const boost::function<grt::ValueRef (grt::GRT*)> &function) THROW (grt::grt_runtime_error);

    bool activate_live_object(const GrtObjectRef &object);

    std::string create_attached_file(const std::string &group, const std::string &tmpl);
    void save_attached_file_contents(const std::string &name, const char *data, size_t size);
    std::string get_attached_file_contents(const std::string &name);
    std::string get_attached_file_tmp_path(const std::string &name);
    void delete_attached_file(const std::string &name);
    std::string recreate_attached_file(const std::string &name, const std::string &data);
    int export_attached_file_contents(const std::string &name, const std::string &export_to);

    void block_user_interaction(bool flag);
    bool user_interaction_allowed() { return _user_interaction_blocked == 0; }
    
    // State handling.
    std::string read_state(const std::string &name, const std::string &domain, const std::string &default_value);
    int read_state(const std::string &name, const std::string &domain, const int &default_value);
    double read_state(const std::string &name, const std::string &domain, const double &default_value);
    bool read_state(const std::string &name, const std::string &domain, const bool &default_value);
     
    void save_state(const std::string &name, const std::string &domain, const std::string &value);
    void save_state(const std::string &name, const std::string &domain, const int &value);
    void save_state(const std::string &name, const std::string &domain, const double &value);
    void save_state(const std::string &name, const std::string &domain, const bool &value);

  protected:
    friend class WBContextModel; // to access _components
    
    bec::GRTManager *_manager;
    bec::PluginManager *_plugin_manager;

    workbench_WorkbenchRef _wb_root;

    int _user_interaction_blocked;
    bool _send_messages_to_shell;
    bool _asked_for_saving;
    bool _initialization_finished;
    bool _attachments_changed;
    
    std::string _datadir;
    std::string _user_datadir;

    struct RefreshRequest
    {
      RefreshType type;
      std::string str;
      NativeHandle ptr;
      double timestamp;
    };

    // Predicate for pending refresh removal on close.
    struct CancelRefreshCandidate
    {
      bool operator() (RefreshRequest request)
      {
        return (request.type == RefreshNewModel ||
          request.type == RefreshNewDiagram ||
          request.type == RefreshOverviewNodeChildren ||
          request.type == RefreshZoom ||
          request.type == RefreshDocument ||
          request.type == RefreshOverviewNodeInfo);
      }
    };

    std::list<RefreshRequest> _pending_refreshes;
    base::Mutex _pending_refresh_mutex;

    base::RecMutex _block_user_interaction_mutex;
    
    WBContextUI *_uicontext;
    WBContextModel *_model_context;
    WBContextSQLIDE *_sqlide_context;
    
    std::vector<WBComponent*> _components;

    boost::shared_ptr<WorkbenchImpl> _workbench;

    bec::Clipboard *_clipboard;

    ModelFile *_file;
    std::string _filename;
    // only used for comparing pointers
    grt::UndoAction *_save_point;
    
    TunnelManager *_tunnel_manager;

    ModelFile* _model_import_file;
    
    bool _force_sw_rendering;     // Command line switch.
    bool _force_opengl_rendering; // Command line switch.

    grt::ListRef<app_PaperType> get_paper_types(grt::GRT *grt, boost::shared_ptr<grt::internal::Unserializer> unserializer);

    bool _other_connections_loaded;
    // setup
    void init_grt_tree(grt::GRT *grt, WBOptions *options, boost::shared_ptr<grt::internal::Unserializer> unserializer);
    void run_init_scripts_grt(grt::GRT *grt, WBOptions *options);
    void init_plugins_grt(grt::GRT *grt, WBOptions *options);
    void init_plugin_groups_grt(grt::GRT *grt, WBOptions *options);
    void init_object_listeners_grt(grt::GRT *grt);
    void init_properties_grt(workbench_DocumentRef &doc);
    void init_rdbms_modules(grt::GRT *grt);

    void do_close_document(bool destroying);

    grt::ValueRef setup_context_grt(grt::GRT *grt, WBOptions *options);

    void set_default_options(grt::DictRef options);

    void load_app_options(bool update);
    
    bool auto_save_document();
    std::string get_auto_save_dir();

    void cleanup_options();
  public:
    void save_app_options();
    void save_connections();
    void save_instances();

  protected:
    void add_recent_file(const std::string &file);

    void load_app_state(boost::shared_ptr<grt::internal::Unserializer> unserializer);
    void save_app_state();
    void load_starters(boost::shared_ptr<grt::internal::Unserializer> unserializer);
    void save_starters();

    grt::ValueRef save_grt(grt::GRT *grt);
    
    grt::ValueRef execute_plugin_grt(grt::GRT *grt, const app_PluginRef &plugin, const grt::BaseListRef &args);
    void plugin_finished(const grt::ValueRef &result, const app_PluginRef &plugin);

    bool handle_message(const grt::Message &msg);
    void handle_grt_message(bec::MessageListStorage::MessageEntryRef message);

    void reset_document();
    void reset_listeners();

    void option_dict_changed(grt::internal::OwnedDict*dict=0, bool added=false, const std::string& key="");

  private:
    // for base::Observer
    virtual void handle_notification(const std::string &name, void *sender, std::map<std::string, std::string> &info);

  public:
    ModelFile *get_file() { return _file; }

    bool install_module_file(const std::string &path);
    bool uninstall_module(grt::Module *module);
    void run_script_file(const std::string &path);
    
  private:
    bool find_connection_password(const db_mgmt_ConnectionRef &conn, std::string &password);

    void *do_request_password(const std::string &title, const std::string &service, bool reset_password,
      std::string *account, std::string *ret_password);
    void *do_find_connection_password(const std::string &hostId, const std::string &username, std::string *ret_password);

    void load_other_connections();

    void attempt_options_upgrade(xmlDocPtr xmldoc, const std::string &version);

    bool show_error(const std::string& title, const std::string& message);

  public:
    std::string request_connection_password(const db_mgmt_ConnectionRef &conn, bool force_asking);

  public: // front end callbacks
    boost::function<std::string (std::string,std::string,std::string)> show_file_dialog;
    boost::function<void (std::string)> show_status_text;
    boost::function<void (std::string,void*)> show_gui_plugin;

    boost::function<mdc::CanvasView* (const model_DiagramRef&)> create_diagram;
    boost::function<void (mdc::CanvasView*)> destroy_view;
    boost::function<void (mdc::CanvasView*)> switched_view;

    boost::function<void (std::string, boost::shared_ptr<bec::UIForm> )> create_main_form_view;
    boost::function<void (bec::UIForm*)> destroy_main_form_view;

    boost::function<void (mdc::CanvasView*)> tool_changed;

    boost::function<void (RefreshType, std::string, NativeHandle)> refresh_gui;
    boost::function<void (bool)> lock_gui;

    boost::function<void (std::string)> perform_command;
    
    boost::function<bool ()> quit_application;
  };

  struct GUILock
  {
    WBContext *_wb;
    
    GUILock(WBContext *wb, const std::string& message_title, const std::string& message)
      : _wb(wb)
    {
      mforms::Utilities::show_wait_message(message_title, message);
      _wb->block_user_interaction(true);
    }
    ~GUILock()
    {
      _wb->block_user_interaction(false);
      mforms::Utilities::hide_wait_message();
    }
  };
  
};
