/*
 *  wb_physical_model_diagram_features.cpp
 *  MySQLWorkbench
 *
 *  Created by Alfredo Kojima on 14/Mar/09.
 *  Copyright 2009 Sun Microsystems Inc. All rights reserved.
 *
 */

#include "stdafx.h"
#include "wb_physical_model_diagram_features.h"
#include "wbcanvas/model_diagram_impl.h"
#include "wbcanvas/workbench_physical_connection_impl.h"
#include "wbcanvas/workbench_physical_tablefigure_impl.h"

#include "wbcanvas/badge_figure.h"

#include "wb_component.h"
#include "wb_model_diagram_form.h"

using namespace wb;

#define TOOLTIP_DELAY 1.0

#define DOUBLE_CLICK_DISTANCE_THRESHOLD 4.0

class wb::Tooltip : public mdc::Box
{
  mdc::TextFigure text;
  
public:
  Tooltip(mdc::Layer *layer)
  : mdc::Box(layer), text(layer)
  {
    set_padding(4, 4);
    
    set_draw_background(true);
    set_background_color(mdc::Color(1, 1, 0.8));
    set_border_color(mdc::Color(0.5, 0.5, 0.5));
    
    mdc::FontSpec font(text.get_font());
#ifdef _WIN32
    font.family= "Verdana";
#elif __APPLE__
    font.family= "Lucida Grande";
#else
    font.family= "Bitstream Vera Sans";
#endif
    font.size= 11;
    text.set_font(font);
    text.set_pen_color(mdc::Color(0.3, 0.3, 0.3));
    text.set_multi_line(true);
    
    add(&text, true, true);
  }
  
  void set_text(const std::string &text)
  {
    this->text.set_text(text);
  }
};




void PhysicalModelDiagramFeatures::on_figure_click(const model_ObjectRef &owner, mdc::CanvasItem *item, const mdc::Point &pos, mdc::MouseButton button, mdc::EventState state)
{
  if (button == mdc::ButtonLeft)
  {
    mdc::Timestamp now= mdc::get_time();
    
    if (mdc::points_distance(_last_click_pos, pos) < DOUBLE_CLICK_DISTANCE_THRESHOLD &&
        now - _last_click < DOUBLE_CLICK_DELAY)
    {
      activate_item(owner, item, state);
      _last_click= 0;
    }
    else
      _last_click= now;
    _last_click_pos= pos;
  }
}


void PhysicalModelDiagramFeatures::on_figure_mouse_button(const model_ObjectRef &owner, mdc::CanvasItem *item, bool press, const mdc::Point &pos, mdc::MouseButton button, mdc::EventState state)
{

}


void PhysicalModelDiagramFeatures::on_figure_crossed(const model_ObjectRef &owner, mdc::CanvasItem *over, bool enter, const mdc::Point &pos)
{
  if (enter) 
  {
    if (over != _last_over_item)
    {
      _last_over_item= over;
      tooltip_setup(owner);
    }
  }
  else
  {
    tooltip_cancel();
  }
  
  if (owner.is_instance<workbench_physical_Connection>())
  {
    workbench_physical_ConnectionRef conn(workbench_physical_ConnectionRef::cast_from(owner));
    
    highlight_connection(conn, enter);
  }
  
  if (owner.is_instance<workbench_physical_TableFigure>())
  {
    workbench_physical_TableFigureRef table(workbench_physical_TableFigureRef::cast_from(owner));
    wbfig::Table *figure= dynamic_cast<wbfig::Table*>(table->get_data()->get_canvas_item());
    
    if (figure && over == figure->get_title())
      highlight_table(table, enter);
    else
    {
      // if over index, highlight it and the columns for it
      db_IndexRef index(table->get_data()->get_index_at(over));
      
      if (index.is_valid())
        highlight_table_index(table, index, enter);
    }
  }
}


void PhysicalModelDiagramFeatures::on_selection_changed()
{
  
}


void PhysicalModelDiagramFeatures::mouse_moved(int x, int y, mdc::EventState state)
{
  // hide tooltip if mouse was moved
  if (_tooltip && _tooltip->get_visible())
    tooltip_cancel();
}



void PhysicalModelDiagramFeatures::activate_item(const model_ObjectRef &owner, mdc::CanvasItem *item, mdc::EventState state)
{
  owner->owner()->signal_objectActivated().emit(owner, (state & mdc::SControlMask) != 0);
}


PhysicalModelDiagramFeatures::PhysicalModelDiagramFeatures(ModelDiagramForm *diagram)
: _diagram(diagram)
{
  _last_over_item= 0;
  _tooltip= 0;
  _tooltip_timer= 0;
  
  
  model_Diagram::ImplData *impl= diagram->get_model_diagram()->get_data();
  
  impl->signal_selection_changed().connect(sigc::hide(sigc::mem_fun(this, &PhysicalModelDiagramFeatures::on_selection_changed)));
  
  impl->signal_item_crossed().connect(sigc::mem_fun(this, &PhysicalModelDiagramFeatures::on_figure_crossed));
  impl->signal_item_click().connect(sigc::mem_fun(this, &PhysicalModelDiagramFeatures::on_figure_click));
  impl->signal_item_mouse_button().connect(sigc::mem_fun(this, &PhysicalModelDiagramFeatures::on_figure_mouse_button));
}





// Table highlighting

void PhysicalModelDiagramFeatures::highlight_table(const workbench_physical_TableFigureRef &table, bool flag)
{
  mdc::Color tocolor(0.0, 0.8, 0.0, 0.4);
  mdc::Color fromcolor(0.0, 0.6, 1.0, 0.4);
  
  if (flag)
    table->get_data()->highlight();
  else
    table->get_data()->unhighlight();
  
  // find all connections that start or end at this table
  grt::ListRef<model_Connection> connections(_diagram->get_model_diagram()->connections());
  
  for (grt::ListRef<model_Connection>::const_iterator conn= connections.begin();
       conn != connections.end(); ++conn)
  {
    db_ForeignKeyRef fk(workbench_physical_ConnectionRef::cast_from(*conn)->foreignKey());
    
    if (!fk.is_valid())
      continue;
    
    if ((*conn)->startFigure() == table)
    {
      workbench_physical_TableFigure::ImplData *dtable= !(*conn)->endFigure().is_valid() ? 0 :
          workbench_physical_TableFigureRef::cast_from((*conn)->endFigure())->get_data();

      if (dtable)
      {
        size_t count= fk->referencedColumns().count();
        for (size_t i= 0; i < count; i++)
        {
          if (flag)
            dtable->set_column_highlighted(fk->referencedColumns()[i], &tocolor);
          else
            dtable->set_column_unhighlighted(fk->referencedColumns()[i]);
        }
        if (flag)
          dtable->highlight(&tocolor);
        else
          dtable->unhighlight();
      }
      
      size_t count= fk->columns().count();
      for (size_t i= 0; i < count; i++)
      {
        if (flag)
          table->get_data()->set_column_highlighted(fk->columns()[i], &tocolor);
        else
          table->get_data()->set_column_unhighlighted(fk->columns()[i]);
      }
      if (flag)
        (*conn)->get_data()->highlight(&tocolor);
      else
        (*conn)->get_data()->unhighlight();
    }
    else if ((*conn)->endFigure() == table)
    {
      workbench_physical_TableFigure::ImplData *stable= !(*conn)->startFigure().is_valid() ? 0 :
          workbench_physical_TableFigureRef::cast_from((*conn)->startFigure())->get_data();
   
      if (stable)
      {
        size_t count= fk->columns().count();
        for (size_t i= 0; i < count; i++)
        {
          if (flag)
            stable->set_column_highlighted(fk->columns()[i], &fromcolor);
          else
            stable->set_column_unhighlighted(fk->columns()[i]);
        }
        
        count= fk->referencedColumns().count();
        for (size_t i= 0; i < count; i++)
        {
          if (flag)
            table->get_data()->set_column_highlighted(fk->referencedColumns()[i], &fromcolor);
          else
            table->get_data()->set_column_unhighlighted(fk->referencedColumns()[i]);
        }
        
        if (flag)
          stable->highlight(&fromcolor);
        else
          stable->unhighlight();
      }
      if (flag)
        (*conn)->get_data()->highlight(&fromcolor);
      else
        (*conn)->get_data()->unhighlight();
    }
  }
}


// Table Index Highlighting

void PhysicalModelDiagramFeatures::highlight_table_index(const workbench_physical_TableFigureRef &table,
                                                         const db_IndexRef &index, bool entered)
{
  wbfig::Table *figure= dynamic_cast<wbfig::Table*> (table->get_data()->get_canvas_item());
  
  if (!figure) return;
  
  size_t index_i= table->table()->indices().get_index(index);
  
  // highlight columns that are in the index + the index itself
  if (index_i != grt::BaseListRef::npos)
  {
    wbfig::Table::ItemList *indexes= figure->get_indexes();
    wbfig::Table::ItemList *columns= figure->get_columns();
    if (indexes && columns)
    {
      wbfig::Table::ItemList::iterator iter;
      iter= indexes->begin();
      int i= index_i;
      while (iter != indexes->end() && --i >= 0) ++iter;
      if (iter != indexes->end())
      {
        // highlight the index
        (*iter)->set_highlight_color(0);
        (*iter)->set_highlighted(entered);
      }
      
      // highlight columns in the index
      for (wbfig::Table::ItemList::const_iterator iter= columns->begin();
           iter != columns->end(); ++iter)
      {
        std::string column_id= (*iter)->get_id();
        bool ok= false;
        
        // lookup the index for matching columns
        for (grt::ListRef<db_IndexColumn>::const_iterator ic= index->columns().begin();
             ic != index->columns().end(); ++ic)
        {
          if ((*ic)->referencedColumn().is_valid() && (*ic)->referencedColumn().id() == column_id)
          {
            ok= true;
            break;
          }
        }
        if (ok)
          (*iter)->set_highlighted(entered);
      }
    }
  }
}


// Connection Highlighting

void PhysicalModelDiagramFeatures::highlight_connection(const workbench_physical_ConnectionRef &conn, bool flag)
{
  workbench_physical_TableFigure::ImplData *stable= !conn->startFigure().is_valid() ? 0 :
    workbench_physical_TableFigureRef::cast_from(conn->startFigure())->get_data();
  workbench_physical_TableFigure::ImplData *dtable= !conn->endFigure().is_valid() ? 0 :
    workbench_physical_TableFigureRef::cast_from(conn->endFigure())->get_data();
  
  if (flag)
    conn->get_data()->highlight();
  else
    conn->get_data()->unhighlight();

  if (stable)
  {
    size_t count= conn->foreignKey().is_valid() ? conn->foreignKey()->columns().count() : 0;
    for (size_t i= 0; i < count; i++)
    {
      if (flag)
        stable->set_column_highlighted(conn->foreignKey()->columns()[i]);
      else
        stable->set_column_unhighlighted(conn->foreignKey()->columns()[i]);
    }
  }
  if (dtable)
  {
    size_t count= (conn->foreignKey().is_valid()) ? conn->foreignKey()->referencedColumns().count() : 0;
    for (size_t i= 0; i < count; i++)
    {
      if (flag)
        dtable->set_column_highlighted(conn->foreignKey()->referencedColumns()[i]);
      else
        dtable->set_column_unhighlighted(conn->foreignKey()->referencedColumns()[i]);
    }
  }
}


// Tooltips

void PhysicalModelDiagramFeatures::tooltip_setup(const model_ObjectRef &object)
{
  if (_tooltip_timer)
  {
    cancel_timer(_tooltip_timer);
    _tooltip_timer= 0;
  }
  
  if (_tooltip && _tooltip->get_visible())
  {
    _tooltip->set_visible(false);
    get_canvas_view()->set_needs_repaint();
  }
  
  mdc::Point position;
  if (_diagram->current_mouse_position(position))
  {      
    if (_tooltip && _tooltip->get_visible())
      show_tooltip(object, _last_over_item);
    else
    {
      if (object.is_valid())
      {
        _tooltip_timer= 
        run_every(sigc::bind_return(sigc::bind(sigc::mem_fun(this, &PhysicalModelDiagramFeatures::show_tooltip), object, _last_over_item), false),
                  TOOLTIP_DELAY);
      }
    }
  }  
}


void PhysicalModelDiagramFeatures::tooltip_cancel()
{
  if (_tooltip_timer)
  {
    cancel_timer(_tooltip_timer);
    _tooltip_timer= 0;
  }
  
  if (_tooltip && _tooltip->get_visible())
  {
    _tooltip->set_visible(false);
    get_canvas_view()->set_needs_repaint();
  }  
}


void PhysicalModelDiagramFeatures::show_tooltip(const model_ObjectRef &object, mdc::CanvasItem *item)
{  
  if (object.is_valid())
  {
    if (_tooltip || _tooltip_timer)
      tooltip_cancel();
    
    std::string text= _diagram->get_owner()->get_wb()->get_object_tooltip(object, item);
    if (!text.empty())
    {
      if (!_tooltip)
      {
        _tooltip= new Tooltip(get_canvas_view()->get_interaction_layer());
        
        get_canvas_view()->get_interaction_layer()->add_item(_tooltip);
      }
      
      _tooltip->set_text(text);
      _tooltip->move_to(item->get_root_position() + mdc::Point(0, 20));
      _tooltip->set_visible(true);
      
      get_canvas_view()->get_interaction_layer()->get_root_area_group()->raise_item(_tooltip);
    }
  }
}


//

mdc::CanvasView *PhysicalModelDiagramFeatures::get_canvas_view()
{
  return _diagram->get_view();
}


bec::GRTManager::Timer *PhysicalModelDiagramFeatures::run_every(const sigc::slot<bool> &slot, double seconds)
{
  return _diagram->get_owner()->get_grt_manager()->run_every(slot, seconds);
}


void PhysicalModelDiagramFeatures::cancel_timer(bec::GRTManager::Timer *timer)
{
  _diagram->get_owner()->get_grt_manager()->cancel_timer(timer);
}
