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

#include "wb_backend_public_interface.h"
#include <map>

#include "mforms/drawbox.h"
#include "mforms/scrollpanel.h"
#include "mforms/menu.h"
#include "mforms/box.h"
#include "mforms/appview.h"
#include "mforms/sectionbox.h"
#include "mforms/utilities.h"

namespace wb
{
  /**
   * Flag to notify observers which action was triggered on the home screen.
   */
  enum HomeScreenAction
  {
    ActionNone,

    ActionWhatsNew,
    ActionStarter,
    ActionStarterPopup,
    ActionRemoveStarter,

    ActionOpenConnection,
    ActionOpenConnectionFromList,
    ActionNewConnection,
    ActionAddSchemaObject,
    ActionEditTable,
    ActionEditSQLScript,
    ActionManageConnections,

    ActionOpenEERModel,
    ActionOpenEERModelFromList,
    ActionNewEERModel,
    ActionNewModelFromDB,
    ActionNewModelFromScript,
    ActionSyncScript,

    ActionServerAdministration,
    ActionManageInstanceFromList,
    ActionNewServerInstance,
    ActionDumpRestore,
    ActionManageSecurity,
    ActionManageServerInstances,
  };

  /**
   * Determines to which list an item must be added.
   */
  enum HomeScreenListType
  {
    HomeScreenStarters,
    HomeScreenSQLDevList,
    HomeScreenModellingList,
    HomeScreenAdministrationList
  };
  
  enum ActionLinkType
  {
    NormalLink,
    GroupLink,
    GroupedLink
  };
  
  enum ActionLinkGroupState
  {
    Expanded,
    Collapsed
  };

  typedef void (*home_screen_action_callback)(HomeScreenAction action, const grt::ValueRef &object, void* user_data);

  class HomeScreen;

  typedef struct
  {
    app_StarterRef starter;

    cairo_surface_t* icon;
    std::string title_line1;                 // The title is split into at most two lines.
    std::string title_line2;
    MySQL::Geometry::Rect bounds;           // Overall bounds of the entry.
    MySQL::Geometry::Rect icon_bounds;      // Position and size of the icon relative to overall bounds.
    MySQL::Geometry::Point title1_location; // Location of the first title line relative to overall bounds.
    MySQL::Geometry::Point title2_location; // Dito for the second line.
  } StarterEntry;

  /**
   * The upper part of the home screen.
   */
  class WorkbenchCentral : public mforms::DrawBox
  {
  public:
    WorkbenchCentral(HomeScreen* owner, grt::GRT* grt);
    ~WorkbenchCentral();
    
    virtual void repaint(cairo_t *cr, int areax, int areay, int areaw, int areah);
    void add_starter(const std::string& icon_name, const grt::ValueRef& object);
    void clear_starters();
  protected:
    void layout(cairo_t* cr);
    virtual void mouse_click(int button, int x, int y);
    HomeScreenAction action_from_point(int x, int y);
    virtual void mouse_leave();
    virtual void mouse_move(int x, int y);
    double get_next_offset(std::vector<StarterEntry>::iterator iterator, bool is_end_of_list);
    void handle_command(const std::string& command);
    app_StarterRef starter_from_point(int x, int y);
  private:
    grt::GRT* _grt;

    HomeScreen* _owner;
    cairo_surface_t* _main_icon;
    cairo_surface_t* _pointer_icon;
    cairo_surface_t* _title_image;
    MySQL::Geometry::Rect _title_bounds;
    MySQL::Geometry::Rect _chapter1_title_bounds;
    MySQL::Geometry::Rect _chapter1_details_bounds;
    MySQL::Geometry::Rect _arrow1_bounds;

    cairo_surface_t* _more_starters_icon;
    MySQL::Geometry::Rect _more_starters_icon_bounds;
    cairo_surface_t* _default_starter_icon;
    std::vector<StarterEntry> _starters;
    int _starter_area_height;                        // Size of the largest starter entry.

    bool _title1_hot;
    grt::ValueRef _last_hit;

    app_StarterRef _hot_starter;
    mforms::Menu _starter_context_menu;
  };
  
  /**
   * The ActionLink class is a lean wrapper for an icon/label combination which can be used
   * to trigger an action. It needs a container to be useful (here the class Workspace).
   */
  class ActionLink
  {
  public:
    ActionLink(cairo_surface_t *icon, const std::string& title, const std::string& description,
               const grt::ValueRef &object, HomeScreenAction action, bool enabled, ActionLinkType type = NormalLink);
    ~ActionLink();
    
    virtual void paint(cairo_t* cr, MySQL::Geometry::Rect bounds, bool hot, bool active);
    bool contains(double x, double y);
    HomeScreenAction get_action() { return _action; };
    grt::ValueRef get_object() { return _object; }
    bool enabled() { return _enabled; };
    ActionLinkType get_type() { return _type; }
    void set_type(ActionLinkType type) { _type=type; }
    ActionLink* get_parent() { return _parent; }
    void set_parent(ActionLink* parent) { _parent = parent; }
    std::string get_title() { return _title; }

    virtual void mouse_click(int button, int x, int y) {};
    virtual void mouse_double_click(int button, int x, int y) {};
    
  protected:
    virtual void layout(cairo_t* cr);

    cairo_surface_t* _icon;
    std::string _title;
    std::string _description;
    std::string _shortened_title;      // Contains the description shortened and with ellipses if the
                                       // full description doesn't fit into the available space.
    std::string _shortened_description;
    double _last_text_width;           // The last width for which the shortened description has been
                                       // computed. Used to avoid unnecessary re-computation.
    double _title_offset;              // Vertical position of the title.
    double _description_offset;        // Ditto for description.
    double _title_width;               // Width of the (possibly shortened) title. For text decoration.
    MySQL::Geometry::Rect _bounds;     // The link's bounds when it was drawn the last time.
    int _text_height;
    HomeScreenAction _action;          // Which action does this link trigger?
    grt::ValueRef _object;
    bool _enabled;                     // Draw the button in enabled or disabled state.
    ActionLinkType _type;
    ActionLink* _parent;
  };
  
  /**
   * Implements an action link which is a container for other action links
   */
  class ActionLinkGroup: public ActionLink
  {
  public:
    ActionLinkGroup(cairo_surface_t *icon, const std::string& title, const std::string& description,
               const grt::ValueRef &object, HomeScreenAction action, bool enabled, ActionLinkGroupState state = Expanded);
    
    ~ActionLinkGroup();
    
    virtual void paint(cairo_t* cr, MySQL::Geometry::Rect bounds, bool hot, bool active);
    
    void add_link(cairo_surface_t *icon, const std::string& title,
                                   const std::string& description, const grt::ValueRef &object, HomeScreenAction action, bool enabled);
    
    void add_link(ActionLink* link);
    
    int get_item_position(ActionLink* link);
    
    int get_item_count() { return _links.size(); }
    
    void toggle_state();
    
    ActionLinkGroupState get_state() { return _state; }
    
    virtual void mouse_click(int button, int x, int y);
    virtual void mouse_double_click(int button, int x, int y);

    boost::signals2::signal<void (std::string&)>* toggled() { return &_toggled; }

    std::vector<ActionLink*>* get_links() { return &_links; }
    
    
  private:
    ActionLinkGroupState _state;
    cairo_surface_t* _collapsed_icon;
    cairo_surface_t* _expanded_icon;

    virtual void layout(cairo_t* cr);
    
    std::vector<ActionLink*> _links;
    
    boost::signals2::signal<void (std::string&)> _toggled;
    
    MySQL::Geometry::Rect _arrow_bounds;   // The group arrow icon bounds.

    MySQL::Geometry::Rect _bar_bounds;     // The group bar bounds.
    double _count_width;                   // Width of the count of group items.
  };
  
  /**
   * Keeps a list of stop colors along with the position for later cairo gradient construction.
   */
  class WorkspaceGradient
  {
  public:
    void add_stop_color(double position, double r, double g, double b, double a);
    void add_stop_color(double position, int color);
    cairo_pattern_t* create_pattern(int height);
  private:
    struct StopColor {
      double position;
      double red;
      double green;
      double blue;
      double alpha;
    };
    
    std::vector<StopColor> _values;
  };
  
  /**
   * Container for any number of ActionLink instances.
   */
  class ActionLinkBox : public mforms::DrawBox, public base::trackable
  {
  public:
    ActionLinkBox(HomeScreen* owner, const std::string& header_image, WorkspaceGradient* gradient,
      int top_spacing, int bottom_spacing, int left_spacing, int right_spacing);
    ~ActionLinkBox();
    
    void add_link(cairo_surface_t *icon, const std::string& title, const std::string& description,
                  const grt::ValueRef &object, HomeScreenAction action, bool enabled);
    void add_link(const std::string &icon_name, const std::string& title, const std::string& description,
                  const grt::ValueRef &object, HomeScreenAction action, bool enabled);

    void add_link(ActionLink* link);
    void remove_link(ActionLink* link);

    void clear();
    void set_back_color(double red, double green, double blue);
    void set_back_color(int color);
    void can_select(bool selectable);
    void single_click(bool value);
    void select(grt::ValueRef item);
    void set_context_menu(mforms::Menu* menu, bool group_menu = false);
    void set_context_menu_enabled(std::string action, bool enabled, bool group_menu = false);

    ActionLink* selected();

    virtual void repaint(cairo_t *cr, int areax, int areay, int areaw, int areah);
    virtual void mouse_enter();
    virtual void mouse_leave();
    virtual void mouse_move(int x, int y);
    virtual void mouse_down(int button, int x, int y);
    virtual void mouse_up(int button, int x, int y);
    virtual void mouse_click(int button, int x, int y);
    virtual void mouse_double_click(int button, int x, int y);
    virtual void get_layout_size(int* w, int* h);
    boost::signals2::signal<void ()>* signal_changed() { return &_signal_changed; }
    boost::signals2::signal<void (const std::string&)>* group_toggled() { return &_group_toggled; }
    void selection_changed();
    int  selected_index() { return _selected_index; }
    int  link_count() { return _links.size(); }
    void set_handle_groups(bool value) { _handle_groups = value; }
    void toggle_group(const std::string &group_name);
  protected:
    void set_selected(ActionLink* link);
    int find_selected_index();
    void layout();
    ActionLink* action_link_from_point(double x, double y);
  private:
    HomeScreen* _owner;
    WorkspaceGradient* _gradient;
    cairo_pattern_t* _background;
    cairo_surface_t* _image;
    std::vector<ActionLink*> _links;
    int _layout_width;
    int _layout_height;
    int _top_spacing;
    int _bottom_spacing;
    int _left_spacing;
    int _right_spacing;
    double _back_red;
    double _back_green;
    double _back_blue;
    bool _can_select;
    bool _single_click;
    int _selected_index;
    mforms::MouseButton _last_mouse_button;
    boost::signals2::signal<void ()> _signal_changed;
    boost::signals2::signal<void (const std::string&)> _group_toggled;

    ActionLink* _hot_link;
    ActionLink* _selected_link;
    mforms::Menu* _context_menu;
    mforms::Menu* _group_context_menu;
    bool _handle_groups;
  };
  
  /**
   * Special design element used separate columns visually.
   */
  class GradientBox : public mforms::DrawBox
  {
  public:
    GradientBox(WorkspaceGradient* gradient);
    ~GradientBox();

    void set_back_color(double red, double green, double blue);
    void set_back_color(int color);
    virtual void repaint(cairo_t *cr, int areax, int areay, int areaw, int areah);
  private:
    WorkspaceGradient* _gradient;
    cairo_pattern_t* _background;
    double _back_red;
    double _back_green;
    double _back_blue;
  };

  /**
   * Implements a collection of action links forming a scrollable list.
   */
  class ActionList : public mforms::Box
  {
  public:
    ActionList(HomeScreen* owner, int left_spacing, int right_spacing, WorkspaceGradient* spacer_gradient,
      const std::string& icon_name, HomeScreenAction action, bool handle_groups = false);
    ~ActionList();

    void set_spacer_back_color(int color);
    void add_entry(const std::string& title, const std::string& description, const grt::ValueRef &object,
                   cairo_surface_t *icon=0);
    void select(grt::ValueRef item);
    ActionLink* selected();
    void clear();
    void refresh();
    void set_context_menu(mforms::Menu* menu, bool group_menu = false);
    void set_context_menu_enabled(std::string action, bool enabled, bool group_menu = false);
    boost::signals2::signal<void ()>* signal_changed() { return _link_box->signal_changed(); }
    void selection_changed() { _link_box->selection_changed(); }
    int  selected_index() { return _link_box->selected_index(); }
    int  link_count() { return _link_box->link_count(); }
    
    void toggle_group(const std::string &group_name);
    
    bool is_first_selected();
    bool is_last_selected();
    
    void set_collapsed_groups(std::string collapsed_groups);
    std::string get_collapsed_groups();

    
  private:
    HomeScreen* _owner;
    Box _panel_border;
    mforms::ScrollPanel _panel;
    GradientBox* _left_spacer;
    GradientBox* _right_spacer;
    ActionLinkBox* _link_box;
    cairo_surface_t *_icon; // The icon to be used for all entries.
    HomeScreenAction _action;      // One action for single items.
    HomeScreenAction _group_action;      // One action for group items.  
    bool _handle_groups;
    std::map<std::string, ActionLinkGroup*> _link_groups;
    std::map<std::string, bool> _link_group_state;
  };
  
  /**
   * The lower part of the home screen.
   */
  class Workspace : public mforms::Box
  {
  public:
    Workspace(HomeScreen* owner);
    ~Workspace();
    
    void add_list_entry(HomeScreenListType list, const std::string& title, const std::string& description, 
                        const grt::ValueRef &object, const std::string &icon_name);
    void select_list_entry(HomeScreenListType list, grt::ValueRef item);
    void clear_list(HomeScreenListType list);
    void refresh_list(HomeScreenListType list);
    void toggle_list_group(HomeScreenListType list, const std::string &group_name);
    
    void set_context_menu_items(HomeScreenListType list, const bec::MenuItemList &menu_items, bool group_menu = false);
    void set_collapsed_group_data(HomeScreenListType list, std::string group_data);
    std::string get_collapsed_group_data(HomeScreenListType list);
    
  protected:
    void handle_command(const std::string& command, HomeScreenListType list);
    void connection_selection_changed();
    void server_selection_changed();
  private:
    HomeScreen* _owner;
    mforms::Box _left_column;
    mforms::Box _middle_column;
    mforms::Box _right_column;
    
    ActionLinkBox* _sql_top;
    ActionLinkBox* _sql_bottom;
    ActionLinkBox* _eer_top;
    ActionLinkBox* _eer_bottom;
    ActionLinkBox* _admin_top;
    ActionLinkBox* _admin_bottom;
    
    ActionList* _connections;
    ActionList* _models;
    ActionList* _server_instances;

    WorkspaceGradient* _top_gradient;
    WorkspaceGradient* _separator_gradient;
  };
  
  class CommandUI;

  /**
   * This class implements the main (home) screen in MySQL Workbench, containing
   * workbench central and the workspace sections.
   */
  class MYSQLWBBACKEND_PUBLIC_FUNC HomeScreen : public mforms::AppView
  {
  private:
    mforms::SectionBox _workbench_central_section;
    WorkbenchCentral* _central_content;
    mforms::SectionBox _workspace_section;
    Workspace* _workspace_content;
    home_screen_action_callback _callback;
    void* _user_data;

    mforms::Box *_auto_save_warning;
  public:
    HomeScreen(CommandUI *cmdui, grt::GRT *grt);
    ~HomeScreen();
    
    void set_wb_central_expanded(bool flag);
    bool get_wb_central_expanded();
    
    void show_autosave_warning(int model, int sqleditor);
    
    boost::function<void (HomeScreenListType, grt::ValueRef, std::string)> handle_context_menu;
    
    void set_context_menu_items(HomeScreenListType list, const bec::MenuItemList &menu_items, bool group_menu = false);
    void set_collapsed_group_data(HomeScreenListType list, std::string group_data);
    std::string get_collapsed_group_data(HomeScreenListType list);
    

    void set_callback(home_screen_action_callback callback, void* user_data);
    void trigger_callback(HomeScreenAction action, const grt::ValueRef &object);
    void add_list_entry(HomeScreenListType list, const std::string& title, const std::string& description,
      const grt::ValueRef& object, const std::string& icon_name = "");
    void select_list_entry(HomeScreenListType list, grt::ValueRef item);
    void clear_list(HomeScreenListType list);
    void refresh_list(HomeScreenListType list);
    void toggle_list_group(HomeScreenListType list, const std::string &group_name);
  };
}

#endif // _HOME_SCREEN_H_
