/* 
 * Copyright (c) 2009, 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_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 ROUTINES_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 TABLES_CAPTION;
    static const std::string VIEWS_CAPTION;
    static const std::string ROUTINES_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 int TABLES_NODE_INDEX;
    static const int VIEWS_NODE_INDEX;
    static const int ROUTINES_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,
      RoutineCollection,

      ColumnCollection,
      IndexCollection,
      TriggerCollection,
      ForeignKeyCollection,

      Trigger,
      TableColumn,
      ViewColumn,
      ForeignKey,
      Index,

      ForeignKeyColumn,
      IndexColumn,
      Any
    };

    // 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
    };

    // 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;
    };

    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){_type = type;}
      std::string type;
      std::string default_value;
      bool is_pk;
      bool is_fk;
      bool is_id;
      
      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;

      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;

      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return LiveSchemaTree::Index; }
      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_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"); }
    };

    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"); }
    };

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

      virtual void copy(LSTData* other);
      virtual ObjectType get_type() {return LiveSchemaTree::View; }
      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);
    };
    
    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, std::list<std::string>* tables, std::list<std::string>* views, std::list<std::string>* procedures, std::list<std::string>* 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, std::list<std::string>&, 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;
    };
    
    struct Delegate
    {
      virtual void tree_refresh() = 0;
      virtual void tree_activate_objects(const std::string&, const std::vector<ChangeRecord>&) = 0;
      virtual void tree_alter_objects(const std::vector<ChangeRecord>&) = 0;
      virtual void tree_create_object(ObjectType, const std::string&, const std::string&) = 0;
      virtual void tree_drop_objects(const std::vector<ChangeRecord>&) = 0;
    };
    
    
    typedef boost::signals2::signal<int (const std::string&)> SqlEditorTextInsertSignal;
    typedef boost::function<bec::MenuItemList (std::string, std::string, std::string)> ObjectPluginMenuItemsSlot;
    typedef boost::function<bool (std::string, std::string, std::string, std::string)> CallObjectPluginMenuItemsSlot;

    SqlEditorTextInsertSignal sql_editor_text_insert_signal;
    ObjectPluginMenuItemsSlot object_plugin_items_slot;
    CallObjectPluginMenuItemsSlot call_object_plugin_items_slot;

  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;

    boost::function<bool (const std::string& first, const std::string& second)> compare_func;
    void schema_contents_arrived(const std::string &schema_name, std::list<std::string> *tables, std::list<std::string> *views, std::list<std::string> *procedures, std::list<std::string> *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_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);

    // 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 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);
    
    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);
    
    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, std::list<std::string>& children, ObjectType type, bool sorted = false, bool just_append = false);
    void update_change_data(mforms::TreeNodeRef parent, std::list<std::string>& 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(std::list<std::string> &schema_list);
    bool compare_identifiers(const std::string &first, const std::string &second);
    int string_compare(const std::string &first, const std::string &second);
    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 find_routine_ranges(const mforms::TreeNodeRef& parent, int& pstart, int& pend, int& fstart, int& fend);
    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;

    static const char* _schema_tokens[15];
    
    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
