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

#ifndef _WB_LIVE_SCHEMA_TREE_H_
#define _WB_LIVE_SCHEMA_TREE_H_

#include "grtpp.h"
#include "grt/tree_model.h"
#include "workbench/wb_backend_public_interface.h"
#include "base/string_utilities.h"
#include "mforms/treenodeview.h"
#include "base/trackable.h"

namespace wb
{
  class MYSQLWBBACKEND_PUBLIC_FUNC LiveSchemaTree: base::trackable
  {
  public:
  friend class LiveSchemaTreeTester;

    static const short COLUMN_DATA = 0x01;
    static const short TRIGGER_DATA = 0x02;
    static const short INDEX_DATA = 0x04;
    static const short FK_DATA = 0x08;

    static const std::string SCHEMA_TAG;
    static const std::string TABLES_TAG;
    static const std::string VIEWS_TAG;
    static const std::string PROCEDURES_TAG;
    static const std::string FUNCTIONS_TAG;
    static const std::string TABLE_TAG;
    static const std::string VIEW_TAG;
    static const std::string ROUTINE_TAG;
    static const std::string COLUMNS_TAG;
    static const std::string INDEXES_TAG;
    static const std::string TRIGGERS_TAG;
    static const std::string FOREIGN_KEYS_TAG;
    static const std::string COLUMN_TAG;
    static const std::string INDEX_TAG;
    static const std::string TRIGGER_TAG;
    static const std::string FOREIGN_KEY_TAG;


    static const std::string FETCHING_CAPTION;
    static const std::string ERROR_FETCHING_CAPTION;
    static const std::string TABLES_CAPTION;
    static const std::string VIEWS_CAPTION;
    static const std::string PROCEDURES_CAPTION;
    static const std::string FUNCTIONS_CAPTION;

    static const std::string COLUMNS_CAPTION;
    static const std::string INDEXES_CAPTION;
    static const std::string TRIGGERS_CAPTION;
    static const std::string FOREIGN_KEYS_CAPTION;

    static const std::string LST_INFO_BOX_DETAIL_ROW;

    static const int TABLES_NODE_INDEX;
    static const int VIEWS_NODE_INDEX;
    static const int PROCEDURES_NODE_INDEX;
    static const int FUNCTIONS_NODE_INDEX;
    static const int TABLE_COLUMNS_NODE_INDEX;
    static const int TABLE_INDEXES_NODE_INDEX;
    static const int TABLE_FOREIGN_KEYS_NODE_INDEX;
    static const int TABLE_TRIGGERS_NODE_INDEX;

    enum FilterType
    {
      LocalLike,
      LocalRegexp,
      RemoteLike,
      RemoteRegexp
    };

    enum ObjectType
    {
      Schema,
      Table,
      View,
      Procedure,
      Function,

      TableCollection,
      ViewCollection,
      ProcedureCollection,
      FunctionCollection,

      ColumnCollection,
      IndexCollection,
      TriggerCollection,
      ForeignKeyCollection,

      Trigger,
      TableColumn,
      ViewColumn,
      ForeignKey,
      Index,

      ForeignKeyColumn,
      IndexColumn,
      Any,
      NoneType
    };

    // This will be used on different object type validations
    enum ObjectTypeValidation
    {
      DatabaseObject, // Schema, Table, View, Procedure, Function
      SchemaObject,   // Table, View, Procedure, Function
      TableOrView,     // Table, View
      ColumnObject,     // TableColumn, ViewVolumn
      RoutineObject     // Procedure, Function
    };

    // One entry in a list that describes a single object.
    struct ChangeRecord
    {
      ObjectType type;
      std::string schema;
      std::string name;
      std::string detail;
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC LSTData: public mforms::TreeNodeData
    {
    public:
      std::string details;
      LSTData();
      virtual ObjectType get_type() = 0;
      virtual void copy(LSTData* other);
      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
      virtual std::string get_object_name() = 0;

      // Nodes requiring to enable this functionality should overwrite
      // and return true once their specific criteria is met
      virtual bool is_update_complete() { return false; }
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC ColumnData: public LSTData
    {
    private:
      ObjectType _type;
    public:
      ColumnData(ObjectType type = TableColumn) : 
          LSTData(), is_pk(false), is_fk(false), is_id(false), is_idx(false) { _type = type; }
      // NOTE than name will contain the column name duplicating this info as it
      //      is already on the TreeNode. Duplicating it is better than having
      //      full HTML for all the columns on all the tables in the db.
      std::string name;
      std::string type;
      std::string default_value;
      std::string charset_collation;
      bool is_pk;
      bool is_fk;
      bool is_id;
      bool is_idx;
      
      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return _type; }
      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
      virtual std::string get_object_name() { return _("Column"); }
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC FKData: public LSTData
    {
    public:
      FKData(): LSTData(), update_rule(0), delete_rule(0){}
      unsigned char update_rule;
      unsigned char delete_rule;
      std::string referenced_table;
      std::string from_cols;
      std::string to_cols;

      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return LiveSchemaTree::ForeignKey; }
      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
      virtual std::string get_object_name() { return _("Foreign Key"); }
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC IndexData: public LSTData
    {
    public:
      IndexData():LSTData(), unique(false), type(0){}
      bool unique;
      unsigned char type;
      std::vector<std::string>columns;

      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return LiveSchemaTree::Index; }
      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
      virtual std::string get_object_name() { return _("Index"); }
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC TriggerData: public LSTData
    {
    public:
      TriggerData():LSTData(), event_manipulation(0), timing(0){}
      unsigned char event_manipulation;
      unsigned char timing;

      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return LiveSchemaTree::Trigger; }
      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
      virtual std::string get_object_name() { return _("Trigger"); }
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC ObjectData: public LSTData
    {
    public:
      ObjectData():LSTData(), fetched(false), fetching(false){}
      bool fetched;
      bool fetching;

      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return LiveSchemaTree::Any; }
      virtual std::string get_object_name() { return _("Object"); }
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC ProcedureData: public ObjectData
    {
    public:
      ProcedureData():ObjectData(){}
      virtual ObjectType get_type() {return LiveSchemaTree::Procedure; }
      virtual std::string get_object_name() { return _("Procedure"); }
      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC FunctionData: public ObjectData
    {
    public:
      FunctionData():ObjectData(){}
      virtual ObjectType get_type() {return LiveSchemaTree::Function; }
      virtual std::string get_object_name() { return _("Function"); }
      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC ViewData: public ObjectData
    {
    public:
      ViewData():
          ObjectData(), columns_load_error(false), 
          _loaded_mask(0), _loading_mask(0), _reload_mask(0){}
      bool columns_load_error;
      short _loaded_mask;
      short _loading_mask;
      short _reload_mask;

      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return LiveSchemaTree::View; }
      void set_reload_mask(short mask) { _reload_mask = mask; }
      short get_reload_mask() { return _reload_mask; }
      virtual short get_loaded_mask();
      virtual void set_loaded_data(short mask);
      virtual void set_unloaded_data(short mask);
      bool is_data_loaded(short mask);
      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
      virtual std::string get_object_name() { return _("View"); }
      short get_loading_mask();
      void set_loading_mask(short mask);
      virtual bool is_update_complete();
    };
    
    class MYSQLWBBACKEND_PUBLIC_FUNC TableData: public ViewData
    {
    public:
      TableData():ViewData(){}

      virtual ObjectType get_type() {return LiveSchemaTree::Table; }

      virtual std::string get_details(bool full, const mforms::TreeNodeRef& node);
      virtual std::string get_object_name() { return _("Table"); }
    };

    class MYSQLWBBACKEND_PUBLIC_FUNC SchemaData: public LSTData
    {
    public:
      SchemaData():LSTData(), fetched(false), fetching(false) {};
      bool fetched;
      bool fetching;

      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return LiveSchemaTree::Schema; }
      virtual std::string get_object_name() { return _("Schema"); }
    };

    typedef boost::function<void(const std::string& schema_name, base::StringListPtr tables,
      base::StringListPtr views, base::StringListPtr procedures, base::StringListPtr functions,
      bool just_append)> NewSchemaContentArrivedSlot;
    typedef boost::function<void (const std::string& schema_name, const std::string& object_name,
      ObjectType obj_type, ObjectType child_type, const std::map<std::string, LSTData*> &children)> NewObjectDetailsArrivedSlot;
    typedef boost::function<bool(mforms::TreeNodeRef, base::StringListPtr, ObjectType, bool sorted,
      bool just_append)>NodeChildrenUpdaterSlot;

    struct FetchDelegate
    {
      virtual std::list<std::string> fetch_schema_list() = 0;
      virtual bool fetch_data_for_filter(const std::string &, const std::string &, const NewSchemaContentArrivedSlot&) = 0;
      virtual bool fetch_schema_contents(const std::string &, const NewSchemaContentArrivedSlot&) = 0;
      virtual bool fetch_object_details(const std::string& schema_name, const std::string& object_name, wb::LiveSchemaTree::ObjectType type, short, const NodeChildrenUpdaterSlot&) = 0;
      virtual bool fetch_routine_details(const std::string& schema_name, const std::string& object_name, wb::LiveSchemaTree::ObjectType type) = 0;
    };
    
    struct Delegate
    {
      virtual void tree_refresh() = 0;
      virtual bool sidebar_action(const std::string&) = 0;
      virtual void tree_activate_objects(const std::string&, const std::vector<ChangeRecord>&) = 0;
    };
    
    
    typedef boost::signals2::signal<int (const std::string&)> SqlEditorTextInsertSignal;

    SqlEditorTextInsertSignal sql_editor_text_insert_signal;

  protected:
    boost::weak_ptr<FetchDelegate> _fetch_delegate;
    boost::weak_ptr<Delegate> _delegate;
    grt::GRT* _grt;
    std::string _active_schema;
    mforms::TreeNodeView* _model_view;

    bool _case_sensitive_identifiers;

    void schema_contents_arrived(const std::string &schema_name, base::StringListPtr tables,
      base::StringListPtr views, base::StringListPtr procedures, base::StringListPtr functions,
      bool just_append);
    void load_table_details(mforms::TreeNodeRef& node, int fetch_mask);
    void fetch_table_details(ObjectType object_type, const std::string schema_name,
      const std::string object_name, int fetch_mask);
    void load_routine_details(mforms::TreeNodeRef& node);
    void load_schema_content(mforms::TreeNodeRef& schema_node);
    void reload_object_data(mforms::TreeNodeRef& node);
    void discard_object_data(mforms::TreeNodeRef& node, int data_mask);

    bool identifiers_equal(const std::string &a, const std::string &b);

    std::vector<std::string> overlay_icons_for_tree_node(mforms::TreeNodeRef node);

    // Filtering functions
    std::string get_filter_wildcard(const std::string& filter, FilterType type = LocalLike);
    void clean_filter();
    void filter_children_collection(mforms::TreeNodeRef& source, mforms::TreeNodeRef& target);
    bool filter_children(ObjectType type, mforms::TreeNodeRef& source, mforms::TreeNodeRef& target,
      GPatternSpec* pattern = NULL);
    bool is_object_type(ObjectTypeValidation validation, ObjectType type);
  public:
    LiveSchemaTree(grt::GRT* grtm);
    virtual ~LiveSchemaTree();

    void set_model_view(mforms::TreeNodeView* target);
    void set_delegate(boost::shared_ptr<Delegate> delegate);
    void set_fetch_delegate(boost::shared_ptr<FetchDelegate> delegate);

    void load_table_details(ObjectType object_type, const std::string schema_name, const std::string object_name, int fetch_mask);

    void set_filter(std::string filter);
    void filter_data();
    void load_data_for_filter(const std::string& schema_filter, const std::string& object_filter);

    static unsigned char internalize_token(const std::string& token);
    static std::string externalize_token(unsigned char c);

    void set_active_schema(const std::string &schema);

    void set_no_connection();

    void update_live_object_state(ObjectType type, const std::string &schema_name, const std::string &old_obj_name, const std::string &new_obj_name);

    virtual std::string get_field_description(const mforms::TreeNodeRef& node);
    void set_notify_on_reload(const mforms::TreeNodeRef& node);
    void notify_on_reload(const mforms::TreeNodeRef& node);
    
    mforms::TreeNodeRef get_node_for_object(const std::string &schema_name, ObjectType type, const std::string& name);
    mforms::TreeNodeRef create_node_for_object(const std::string &schema_name, ObjectType type, const std::string& name);

    // Returns a list of db_query_LiveDBObjectRef.
    grt::BaseListRef get_selected_objects();

    virtual bool activate_popup_item_for_nodes(const std::string &name, const std::list<mforms::TreeNodeRef> &orig_nodes);
    virtual bec::MenuItemList get_popup_items_for_nodes(const std::list<mforms::TreeNodeRef> &nodes);

    bool is_schema_contents_enabled() const;
    void is_schema_contents_enabled(bool value);

    void set_base(LiveSchemaTree *base) { _base = base; }

    bool update_node_children(mforms::TreeNodeRef parent, base::StringListPtr children,
      ObjectType type, bool sorted = false, bool just_append = false);
    void update_change_data(mforms::TreeNodeRef parent, base::StringListPtr children,
      ObjectType type, std::vector<mforms::TreeNodeRef>& to_remove);
    mforms::TreeNodeRef insert_node(mforms::TreeNodeRef parent, const std::string& name, ObjectType type);
    void setup_node(mforms::TreeNodeRef node, ObjectType type, mforms::TreeNodeData* data = NULL,
      bool ignore_null_data = false);
    void update_node_icon(mforms::TreeNodeRef node);

    void update_schemata(base::StringListPtr schema_list);
    mforms::TreeNodeRef binary_search_node(const mforms::TreeNodeRef& parent, int first, int last, const std::string &name, ObjectType type, int &position);
    mforms::TreeNodeRef get_child_node(const mforms::TreeNodeRef& parent, const std::string& name, ObjectType type = Any, bool binary_search = true);
    bool find_child_position(const mforms::TreeNodeRef& parent, const std::string& name, ObjectType type, int &position);
    void expand_toggled(mforms::TreeNodeRef node, bool value);
    void node_activated(mforms::TreeNodeRef node, int column);
    void set_case_sensitive_identifiers(bool flag);
    std::string get_schema_name(const mforms::TreeNodeRef& node);
    std::vector<std::string> get_node_path(const mforms::TreeNodeRef& node);
    mforms::TreeNodeRef get_node_from_path(std::vector<std::string> path);
    void enable_events(bool enable) { _enabled_events = enable; }


  private:
    bool _is_schema_contents_enabled;
    bool _enabled_events;

    LiveSchemaTree *_base;
    std::string _filter;
    ObjectType _filter_type;
    GPatternSpec* _schema_pattern;
    GPatternSpec* _object_pattern;
    LSTData *notify_on_reload_data;

    static const char* _schema_tokens[16];
    
    std::map<ObjectType, std::string> _icon_paths;
    
    std::map<ObjectType, mforms::TreeNodeCollectionSkeleton> _node_collections;
    
    void fill_node_icons();
    std::string get_node_icon_path(ObjectType type);
    bec::IconId get_node_icon(ObjectType type);
  };
};

#endif
