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

#include "mforms/mforms.h"
#include "base/log.h"

DEFAULT_LOG_DOMAIN("mforms");

#define SIDE_PADDING 12
#define TAB_SIDE_PADDING 10
#define TAB_TEXT_SPACING 8

#ifdef __APPLE__
  #define TAB_FONT "Lucida Grande"
#elif _WIN32
  #define TAB_FONT "Tahoma"
#else
  #define TAB_FONT "Helvetica"
#endif
#define TITLE_FONT_SIZE 12
#define SUB_TITLE_FONT_SIZE 9
#define TITLE_TEXT_SPACING 4

#define ICON_WIDTH 32
#define ICON_HEIGHT 32

#define INITIAL_TAB_HEIGHT 58


#ifdef _WIN32
#define VERTICAL_STYLE_WIDTH 58
#define VERTICAL_STYLE_HEIGHT 68
#else
#define VERTICAL_STYLE_WIDTH 64
#define VERTICAL_STYLE_HEIGHT 70
#endif

#ifndef _WIN32
using std::max;
#endif

using namespace base;
using namespace mforms;


const int NEXT_BUTTON_INDEX = -2;
const int PREV_BUTTON_INDEX = -3;

//--------------------------------------------------------------------------------------------------

class mforms::TabSwitcherPimpl
{
protected:
  struct TabItem {
    std::string title;
    std::string sub_title;
    cairo_surface_t *icon;
    cairo_surface_t *alt_icon;
    
    TabItem() : icon(0), alt_icon(0) {}
    ~TabItem() { if (icon) cairo_surface_destroy(icon); if (alt_icon) cairo_surface_destroy(alt_icon); }
  };
  
  TabSwitcher *_owner;
  std::vector<TabItem*> _items;

  int _selected;
  int _last_clicked;

  TabSwitcherPimpl(TabSwitcher *owner)
  : _owner(owner), _selected(-1), _last_clicked(-1)
  {
    
  }

public:
  virtual ~TabSwitcherPimpl()
  {
    for (std::vector<TabItem*>::iterator iter= _items.begin(); iter != _items.end(); ++iter)
      delete *iter;
  }
  
  virtual bool set_collapsed(bool flag)
  {
    return false;
  }
  
  virtual bool get_collapsed() = 0;
  
  void set_selected(int index)
  {
    _selected = index;
  }

  int get_selected() { return _selected; }

  virtual void repaint(cairo_t *cr, int areax, int areay, int areaw, int areah) = 0;

  virtual void set_icon(int index, const std::string &icon_path, const std::string &alt_icon_path)
  {
    if (index >= 0 && index < (int) _items.size())
    {
      TabItem *item = _items[index];
      
      if (item->icon != NULL)
        cairo_surface_destroy(item->icon);
      item->icon = Utilities::load_icon(icon_path, true);

      if (item->alt_icon != NULL)
        cairo_surface_destroy(item->alt_icon);
      item->alt_icon= Utilities::load_icon(alt_icon_path, true);
    }
  }
  
  
  virtual int add_item(const std::string &title, const std::string &sub_title,
                        const std::string &icon_path, const std::string &alt_icon_path)
  {
    TabItem *item = new TabItem();
    
    item->title= title;
    item->sub_title= sub_title;
    item->icon= Utilities::load_icon(icon_path, true);
    item->alt_icon= Utilities::load_icon(alt_icon_path, true);

    _items.push_back(item);
    
    if (_selected == -1)
      set_selected((int)_items.size() - 1);

    return (int)_items.size() - 1;
  }

  virtual void remove_item(int index)
  {
    delete _items[index];
    _items.erase(_items.begin()+index);
  }
  
  virtual int index_from_point(int x, int y) = 0;
  
  virtual bool go_back() { return false; }
  virtual bool go_next() { return false; }
};



class VerticalTabSwitcher : public mforms::TabSwitcherPimpl
{
  enum TabElementPart {
    TabActiveBackground  = 0,
    TabInactiveBackground,
    TabActiveForeground,
    TabInactiveForeground,
    
    TabMainCaption,
    TabSubCaption,
    TabLineColor,
    TabLastElement
  };
  
  base::Color _colors[TabLastElement];
  cairo_surface_t *_selection_image;
  cairo_surface_t *_up_arrow;
  cairo_surface_t *_down_arrow;
  int _up_arrow_y;
  int _down_arrow_y;
  int _first_visible;
  int _last_visible;
  bool _collapsed;

  public:
  
  VerticalTabSwitcher(TabSwitcher *owner)
  : TabSwitcherPimpl(owner), _first_visible(0), _last_visible(0), _collapsed(false)
  {
#ifdef _WIN32
    if (base::Color::get_active_scheme() != base::ColorSchemeStandardWin7)
    {
      _colors[TabInactiveBackground]= base::Color::get_application_color(base::AppColorPanelHeader, false);
      _colors[TabInactiveForeground]= base::Color::get_application_color(base::AppColorPanelHeader, true);
      _colors[TabActiveBackground]= base::Color::get_application_color(base::AppColorPanelHeaderFocused, false);
      _colors[TabActiveForeground]= base::Color::get_application_color(base::AppColorPanelHeaderFocused, true);
    }
    else
    {
      _colors[TabInactiveBackground]= base::Color::get_application_color(base::AppColorPanelHeader, false);
      _colors[TabInactiveForeground]= base::Color::get_application_color(base::AppColorPanelHeader, true);
      _colors[TabActiveBackground]= base::Color::get_application_color(base::AppColorMainBackground, false);
      _colors[TabActiveForeground]= base::Color::get_application_color(base::AppColorPanelHeader, true);
    }
#else
    _colors[TabInactiveBackground]= Color(0x48/255.0, 0x48/255.0, 0x48/255.0, 1);
    _colors[TabActiveBackground]= Color(0x26/255.0, 0x26/255.0, 0x26/255.0, 1);
    _colors[TabActiveForeground]= Color(1, 1, 1, 1);
    _colors[TabInactiveForeground]= Color(0.6, 0.6, 0.6, 1);
#endif
    _up_arrow = NULL;
    _down_arrow = NULL;
    _selection_image = Utilities::load_icon("output_type-item_selected.png", true);
  }
  
  virtual ~VerticalTabSwitcher()
  {
    if (_up_arrow)
      cairo_surface_destroy(_up_arrow);
    if (_down_arrow)
      cairo_surface_destroy(_down_arrow);
    if (_selection_image)
      cairo_surface_destroy(_selection_image);
  }
  
  virtual void repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
  {
    Color color;
    
    cairo_save(cr);
    
    color = _colors[TabInactiveBackground];
    cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1);
    cairo_paint(cr);
    
    const int font_size = 10;
    
    cairo_select_font_face(cr, TAB_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size(cr, font_size);
    
    if (_first_visible > 0)
    {
      int items_that_fit = _owner->get_height() / VERTICAL_STYLE_HEIGHT;
      
      if (_first_visible + items_that_fit > (int)_items.size())
      {
        _first_visible = (int)_items.size() - items_that_fit;
        if (_first_visible < 0)
          _first_visible = 0;
      }
    }
    
    double y = 0;
    double iy = 0;
    int ii = 0;

    _last_visible = _first_visible;
    for (std::vector<TabItem*>::iterator i = _items.begin(); i != _items.end(); ++i, ++ii)
    {
      if (ii < _first_visible)
        continue;

      if (y + VERTICAL_STYLE_HEIGHT > _owner->get_height())
        break;

      _last_visible = ii;

      int iwidth, iheight;
      Utilities::get_icon_size((*i)->icon, iwidth, iheight);

      if (_selected == ii)
      {
        color = _colors[TabActiveBackground];
        cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1);
        cairo_rectangle(cr, 0, y, VERTICAL_STYLE_WIDTH, VERTICAL_STYLE_HEIGHT);
        cairo_fill(cr);

        Utilities::paint_icon(cr, _selection_image, 0, iy + (VERTICAL_STYLE_WIDTH - iheight)/2 + iheight/2);
      }
      
      Utilities::paint_icon(cr, (*i)->icon,
                            (VERTICAL_STYLE_WIDTH - (VERTICAL_STYLE_WIDTH/64.0)*iheight)/2,
                            iy + (VERTICAL_STYLE_WIDTH - iheight)/2 - font_size,
                            _selected == ii ? 1.0f : 0.4f);
      if (_selected == ii)
      {
        color = _colors[TabActiveForeground];
      }
      else
      {
        color = _colors[TabInactiveForeground];
      }

      cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1);
      
      std::string::size_type p = (*i)->title.find('\n');
      if (p == std::string::npos)
      {
        cairo_text_extents_t ext;
        cairo_text_extents(cr, (*i)->title.c_str(), &ext);
        
        cairo_move_to(cr, (VERTICAL_STYLE_WIDTH - ext.width)/2, y + iheight);
        cairo_show_text(cr, (*i)->title.c_str());
      }
      else
      {
        std::string l1 = (*i)->title.substr(0, p);
        std::string l2 = (*i)->title.substr(p+1);
        cairo_text_extents_t ext1, ext2;
        cairo_text_extents(cr, l1.c_str(), &ext1);
        cairo_text_extents(cr, l2.c_str(), &ext2);
        
        cairo_move_to(cr, (VERTICAL_STYLE_WIDTH - ext1.width)/2, y + iheight + 4 - (font_size + ext1.y_bearing) + (VERTICAL_STYLE_WIDTH - iheight)/2);
        cairo_show_text(cr, l1.c_str());
        cairo_stroke(cr);
        cairo_move_to(cr, (VERTICAL_STYLE_WIDTH - ext2.width)/2, y + iheight + 4 + font_size - (font_size + ext2.y_bearing) + (VERTICAL_STYLE_WIDTH - iheight)/2);
        cairo_show_text(cr, l2.c_str());
        cairo_stroke(cr);
      }
      y += VERTICAL_STYLE_HEIGHT;
      iy += VERTICAL_STYLE_HEIGHT;
    }
    
    if (_first_visible > 0 || _last_visible < (int)_items.size()-1)
    {
      if (!_up_arrow)
        _up_arrow = mforms::Utilities::load_icon("arrow_up.png");

      if (!_down_arrow)
        _down_arrow = mforms::Utilities::load_icon("arrow_down.png");

      if (_up_arrow && _down_arrow)
      {
        int w = cairo_image_surface_get_width(_up_arrow);
        int bottom = _owner->get_height() - 4;
        int up_h = cairo_image_surface_get_height(_up_arrow);
        int down_h = cairo_image_surface_get_height(_down_arrow);
        
        _up_arrow_y = bottom - up_h - 4 - down_h;
        
        cairo_set_source_surface(cr, _up_arrow, (VERTICAL_STYLE_WIDTH - w) / 2, _up_arrow_y);
        if (_first_visible > 0)
          cairo_paint(cr);
        else
          cairo_paint_with_alpha(cr, 0.4);

        _down_arrow_y = bottom - down_h;
        cairo_set_source_surface(cr, _down_arrow, (VERTICAL_STYLE_WIDTH - w) / 2, _down_arrow_y);
        if (_last_visible < (int)_items.size()-1)
          cairo_paint(cr);
        else
          cairo_paint_with_alpha(cr, 0.4);
      }
      else
        log_error("Could not load arrow_up/down.png\n");
    }
    else
    {
      _up_arrow_y = 0;
      _down_arrow_y = 0;
    }
    
    cairo_restore(cr);
  }
  
  virtual int index_from_point(int x, int y)
  {
    if (_items.size() == 0 || x < 0 || x > _owner->get_width() || y < 0 || y > _owner->get_height())
      return -1;

    if ((_first_visible > 0 || _last_visible < (int)_items.size()-1) && y > _up_arrow_y)
    {
      if (y < _down_arrow_y)
        return PREV_BUTTON_INDEX;
      else
        return NEXT_BUTTON_INDEX;
    }

    for (size_t i = 0; i < _items.size(); i++)
    {
      if (y < (int)(i+1) * VERTICAL_STYLE_HEIGHT)
        return (int)i + _first_visible;
    }
    return -1;
  }
  
  virtual bool set_collapsed(bool flag)
  {
    _collapsed = flag;
    return true;
  }
  
  virtual bool get_collapsed()
  {
    return _collapsed;
  }
  
  virtual bool go_back()
  {
    if (_first_visible > 0)
    {
      _first_visible--;
      _owner->set_selected(get_selected()-1);
      return true;
    }
    return false;
  }
  
  virtual bool go_next()
  {
    if (_last_visible < (int)_items.size()-1)
    {
      _first_visible++;
      _owner->set_selected(get_selected()+1);
      return true;
    }
    return false;
  }
};



//--------------------------------------------------------------------------------------------------

TabSwitcher::TabSwitcher(TabSwitcherType type)
: _tabView(0), _needs_relayout(true), _was_collapsed(false)
{
  _timeout = 0;
  switch (type)
  {
    case VerticalIconSwitcher:
      _pimpl = new VerticalTabSwitcher(this);
      set_size(VERTICAL_STYLE_WIDTH, -1);
      break;
  }
}

//--------------------------------------------------------------------------------------------------

TabSwitcher::~TabSwitcher()
{
  if (_timeout)
    mforms::Utilities::cancel_timeout(_timeout);

  delete _pimpl;
}

//--------------------------------------------------------------------------------------------------

int TabSwitcher::get_preferred_height() 
{
  return INITIAL_TAB_HEIGHT; 
}

//--------------------------------------------------------------------------------------------------

void TabSwitcher::attach_to_tabview(TabView *tabView)
{
  _tabView= tabView;
  set_needs_relayout();
  
  scoped_connect(_tabView->signal_tab_changed(),boost::bind(&TabSwitcher::tab_changed, this));
}

//--------------------------------------------------------------------------------------------------

void TabSwitcher::set_collapsed(bool flag)
{
  if (_pimpl->get_collapsed() != flag)
  {
    if (_pimpl->set_collapsed(flag))
    {
      set_size(_pimpl->get_collapsed() ? 5 : VERTICAL_STYLE_WIDTH, -1);
      _signal_collapse_changed();
    }
    set_layout_dirty();
  }
}

//--------------------------------------------------------------------------------------------------
bool TabSwitcher::get_collapsed()
{
  return _pimpl->get_collapsed();
}

//--------------------------------------------------------------------------------------------------

int TabSwitcher::add_item(const std::string &title, const std::string &sub_title,
                          const std::string &icon_path, const std::string &alt_icon_path)
{
  int i = _pimpl->add_item(title, sub_title, icon_path, alt_icon_path);
  set_needs_relayout();
  return i;
}

//--------------------------------------------------------------------------------------------------

void TabSwitcher::remove_item(int index)
{
  _pimpl->remove_item(index);
}

//--------------------------------------------------------------------------------------------------

/**
 * Replaces the icon pair for the given item with the new values.
 */
void TabSwitcher::set_icon(int index, const std::string &icon_path, const std::string &alt_icon_path)
{
  _pimpl->set_icon(index, icon_path, alt_icon_path);
}

//--------------------------------------------------------------------------------------------------

void TabSwitcher::set_selected(int index)
{
  _pimpl->set_selected(index);
  if (_tabView != NULL)
    _tabView->set_active_tab(index);
  set_needs_repaint();
}

//--------------------------------------------------------------------------------------------------
int TabSwitcher::get_selected()
{
  return _pimpl->get_selected();
}

//--------------------------------------------------------------------------------------------------

void TabSwitcher::set_needs_relayout()
{
  _needs_relayout= true;
  set_needs_repaint();
}

//--------------------------------------------------------------------------------------------------

void TabSwitcher::set_layout_dirty()
{
  DrawBox::set_layout_dirty(true);
  set_needs_relayout();
}

//--------------------------------------------------------------------------------------------------

bool TabSwitcher::mouse_down(mforms::MouseButton button, int x, int y)
{
  if (!DrawBox::mouse_down(button, x, y))
  {
    // For now ignore which button was pressed.
    _last_clicked = _pimpl->index_from_point(x, y);
  }

  return true;
}

//--------------------------------------------------------------------------------------------------

bool TabSwitcher::mouse_up(mforms::MouseButton button, int x, int y)
{
  return DrawBox::mouse_up(button, x, y);
}

//--------------------------------------------------------------------------------------------------

bool TabSwitcher::mouse_click(mforms::MouseButton button, int x, int y)
{
  bool handled = DrawBox::mouse_click(button, x, y);

  // Don't change anything if the user clicked outside of any tab.
  if (!handled && _last_clicked == _pimpl->index_from_point(x, y))
  {
    if (_last_clicked > -1)
    {
      set_selected(_last_clicked);
      _signal_changed();
      handled = true;
    }
    else if (_last_clicked == PREV_BUTTON_INDEX)
    {
      if (_pimpl->go_back())
      {
        set_needs_repaint();
        _signal_changed();
        handled = true;
      }
    }
    else if (_last_clicked == NEXT_BUTTON_INDEX)
    {
      if (_pimpl->go_next())
      {
        set_needs_repaint();
        _signal_changed();
        handled = true;
      }
    }
  }
  return handled;
}

//--------------------------------------------------------------------------------------------------

bool TabSwitcher::mouse_enter()
{
  if (DrawBox::mouse_enter())
    return true;

  _was_collapsed = _pimpl->get_collapsed();
  if (_was_collapsed)
    set_collapsed(false);
  return true;
}

//--------------------------------------------------------------------------------------------------

bool TabSwitcher::collapse()
{
  _timeout = 0;
  set_collapsed(true);
  return false;
}

//--------------------------------------------------------------------------------------------------

bool TabSwitcher::mouse_leave()
{
  if (DrawBox::mouse_leave())
    return true;

  if (_was_collapsed)
  {
    _was_collapsed = false;
    _timeout = mforms::Utilities::add_timeout(0.3f, boost::bind(&TabSwitcher::collapse, this));
  }
  return true;
}

//--------------------------------------------------------------------------------------------------

void TabSwitcher::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
  _pimpl->repaint(cr, areax, areay, areaw, areah);
}

//--------------------------------------------------------------------------------------------------

void TabSwitcher::tab_changed()
{
  _pimpl->set_selected(_tabView->get_active_tab());
  set_needs_repaint();
}
