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

#include "stdafx.h"

#include "grts/structs.workbench.model.h"

#include "model_diagram_impl.h"
#include "model_model_impl.h"
#include "model_layer_impl.h"
#include "model_figure_impl.h"
#include "model_connection_impl.h"

#include "layer_figure.h"
#include "badge_figure.h"

#include "grt/grt_manager.h"
#include "base/string_utilities.h"

using namespace grt;
using namespace base;

// this subclass allows repainting to happen in 3 steps, layer only, connection only and figure only
// to follow a repaint ordering that's independent from logical ordering
class RootAreaGroup : public mdc::AreaGroup
{
public:
  RootAreaGroup(mdc::Layer *owner) : mdc::AreaGroup(owner) {}

  virtual void repaint(const Rect &clipArea, bool direct)
  {
    mdc::CairoCtx *cr= _layer->get_view()->cairoctx();

    std::list<mdc::AreaGroup*> layers;
    std::list<mdc::Line*> connections;
    std::list<mdc::CanvasItem*> figures;

    cr->save();

    for (std::list<mdc::CanvasItem*>::reverse_iterator end= _contents.rend(), iter= _contents.rbegin();
         iter != end; ++iter)
    {
      mdc::CanvasItem *item= *iter;
      
      if (item->get_visible() && item->intersects(clipArea))
      {
        if (dynamic_cast<mdc::Line*>(item))
          connections.push_back(static_cast<mdc::Line*>(item));
        else if (dynamic_cast<mdc::AreaGroup*>(item))
          layers.push_back(static_cast<mdc::AreaGroup*>(item));
        else
          figures.push_back(item);
      }
    }

    // 1st paint all backgrounds of area groups
    for (std::list<mdc::AreaGroup*>::iterator end= layers.end(), iter= layers.begin(); iter != end; ++iter)
    {
      ((mdc::Layouter*)(*iter))->repaint(clipArea, direct);
    }

    // 2nd paint all connections
    for (std::list<mdc::Line*>::iterator end= connections.end(), iter= connections.begin(); iter != end; ++iter)
    {
      (*iter)->repaint(clipArea, direct);
    }

    // 3rd paint figures in root layer
    for (std::list<mdc::CanvasItem*>::iterator end= figures.end(), iter= figures.begin(); iter != end; ++iter)
    {
      (*iter)->repaint(clipArea, direct);
    }

    // 4th paint figures inside area groups
    for (std::list<mdc::AreaGroup*>::iterator end= layers.end(), iter= layers.begin(); iter != end; ++iter)
    {
      Rect localClipArea(clipArea);
      localClipArea.pos= localClipArea.pos - (*iter)->get_position();
      (*iter)->repaint_contents(localClipArea, direct);
    }

    cr->restore();
  }
};





model_Diagram::ImplData::ImplData(model_Diagram *self)
  : _self(self), _canvas_view(0)
{
  _updating_selection= 0;
  _connected_update= false;
  
  scoped_connect(self->signal_changed(),boost::bind(&model_Diagram::ImplData::member_changed, this, _1 ,_2));
  scoped_connect(self->signal_list_changed(),boost::bind(&model_Diagram::ImplData::member_list_changed, this, _1, _2, _3));
}


model_Diagram::ImplData::~ImplData()
{
  unrealize();
}


bool model_Diagram::ImplData::is_realizable()
{
  return true;
}


void model_Diagram::ImplData::set_page_counts(int x, int y)
{
  Size pageSize(get_size_for_page(_self->owner()->get_data()->get_page_settings()));

  _self->width(pageSize.width * x);
  _self->height(pageSize.height * y);

  if (_self->_rootLayer.is_valid())
  {
    _self->_rootLayer->width(_self->_width);
    _self->_rootLayer->height(_self->_height);
  }
  
  update_size();
}

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

mdc::CanvasView* model_Diagram::ImplData::get_canvas_view()
{
  // Create a canvas view if none exists yet.
  if (_canvas_view == NULL)
    realize();
  return _canvas_view;
}

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

Size model_Diagram::ImplData::get_size_for_page(const app_PageSettingsRef &page)
{
  Size size;

  if (page.is_valid() && page->paperType().is_valid())
  {
    std::string name= page->paperType()->name();
    std::string id= page->paperType().id();

    size.width= (page->paperType()->width() - (page->marginLeft() + page->marginRight())) * page->scale();

    size.height= (page->paperType()->height() - (page->marginTop() + page->marginBottom())) * page->scale();
  }
  else
  {
    size.width= 1000;
    size.height= 1000;
  }

  if (page.is_valid() && page->orientation() == "landscape")
  {
    std::swap(size.width, size.height);
  }

  return size;
}


void model_Diagram::ImplData::block_updates(bool flag)
{
}


void model_Diagram::ImplData::notify_object_realize(const model_ObjectRef &object)
{
  _realize_object_signal(object);
}


void model_Diagram::ImplData::notify_object_will_unrealize(const model_ObjectRef &object)
{
  _will_unrealize_object_signal(object);
}


void model_Diagram::ImplData::update_from_page_size()
{
  if (_canvas_view)
  {
    Size pageSize(get_size_for_page(_self->owner()->get_data()->get_page_settings()));
    _canvas_view->set_page_size(pageSize);

    mdc::Count xc, yc;
    _canvas_view->get_page_layout(xc, yc);

    _self->_width= xc * pageSize.width;
    _self->_height= yc * pageSize.height;

    _self->_rootLayer->width(_self->_width);
    _self->_rootLayer->height(_self->_height);
  }
}


void model_Diagram::ImplData::update_size()
{
  if (_canvas_view)
  {
    Size pageSize(get_size_for_page(_self->owner()->get_data()->get_page_settings()));

    if (!is_main_thread())
      run_later(boost::bind(&mdc::CanvasView::set_page_size, _canvas_view, pageSize));
    else
      _canvas_view->set_page_size(pageSize);

    mdc::Count xc, yc;

    xc= (mdc::Count)ceil(*_self->_width / pageSize.width);
    yc= (mdc::Count)ceil(*_self->_height / pageSize.height);

    if (xc < 1) xc= 1;
    if (yc < 1) yc= 1;

    if (!is_main_thread())
      run_later(boost::bind(&mdc::CanvasView::set_page_layout, _canvas_view, xc, yc));
    else
      _canvas_view->set_page_layout(xc, yc);
  }
  if (_self->_rootLayer.is_valid())
  {
    _self->_rootLayer->width(_self->_width);
    _self->_rootLayer->height(_self->_height);

    _self->_rootLayer->get_data()->try_realize();
  }
}


void model_Diagram::ImplData::realize_selection()
{
  // apply selection state that was previously saved
  begin_selection_update();  

  for (size_t i= _self->_selection.count(); i > 0; --i)
  {
    model_ObjectRef object(_self->_selection.get(i-1));

    if (object.is_instance<model_Figure>())
    {
      model_Figure::ImplData *elem= dynamic_cast<model_Figure::ImplData*>(object->get_data());

      if (elem && elem->get_canvas_item())
        _canvas_view->get_selection()->add(elem->get_canvas_item());
      else // unselect items that don't exist in the canvas
        self()->unselectObject(object);
    }
    else if (object.is_instance<model_Connection>())
    {
      model_Connection::ImplData *conn= dynamic_cast<model_Connection::ImplData*>(object->get_data());

      if (conn && conn->get_canvas_item())
        _canvas_view->get_selection()->add(conn->get_canvas_item());
      else // unselect items that don't exist in the canvas
        self()->unselectObject(object);
    }
    else if (object.is_instance<model_Layer>())
    {
      model_Layer::ImplData *layer= dynamic_cast<model_Layer::ImplData*>(object->get_data());

      if (layer && layer->get_area_group())
        _canvas_view->get_selection()->add(layer->get_area_group());
      else // unselect items that don't exist in the canvas
        self()->unselectObject(object);
    }
    else
      g_warning("Unknown object in selection %s", object.class_name().c_str());
  }

  end_selection_update();  
  
  if (_canvas_view)
    g_return_if_fail(_canvas_view->get_selection()->get_contents().size() == _self->_selection.count());
}


void model_Diagram::ImplData::update_options(const std::string &key)
{
  if (key == "workbench.physical.Diagram:DrawLineCrossings" || key.empty())
  {
    model_Model::ImplData *model= _self->owner()->get_data();
    if (_canvas_view)
      _canvas_view->set_draws_line_hops(model->get_int_option("workbench.physical.Diagram:DrawLineCrossings", 1) == 1);
  }
}




void model_Diagram::ImplData::realize_contents()
{
  _self->_rootLayer->get_data()->realize();

  for (size_t c= _self->_layers.count(), i= 0; i < c; i++)
  {
    _self->_layers[i]->get_data()->realize();
  }
  for (size_t c= _self->_figures.count(), i= 0; i < c; i++)
  {
    _self->_figures[i]->get_data()->realize();
  }
  for (size_t c= _self->_connections.count(), i= 0; i < c; i++)
  {
    _self->_connections[i]->get_data()->realize();
  }
}


bool model_Diagram::ImplData::realize()
{
  if (!is_realizable())
    return false;
  
  if (!is_main_thread())
  {
    run_later(boost::bind(&model_Diagram::ImplData::realize, this));
    return true;
  }

  if (!_canvas_view)
  {
    model_Model::ImplData *model= _self->owner()->get_data();
    
    if (!_connected_update)
      scoped_connect(model->signal_options_changed(),boost::bind(&model_Diagram::ImplData::update_options, this, _1));
    _connected_update= true;

    _canvas_view= model->get_delegate()->create_diagram(model_DiagramRef(_self));

    _canvas_view->get_current_layer()->set_root_area(new RootAreaGroup(_canvas_view->get_current_layer()));

    update_options("");

    _selection_signal_conn= _canvas_view->get_selection()->signal_changed()->connect(
      boost::bind(&model_Diagram::ImplData::canvas_selection_changed, this, _1, _2));

    update_size();

    if (*_self->_zoom < 0.1)
      _self->_zoom= 0.1;
    
    _canvas_view->set_zoom((float)*_self->_zoom);
    
    realize_contents();

    run_later(boost::bind(&model_Diagram::ImplData::realize_selection, this));
  }
  if (!_canvas_view)
  {
    if (!_self->owner().is_valid())
      throw std::logic_error("Owner model of view not specified");
    else
      throw std::logic_error("Could not get bridge for owner model of view");
  }
  return true;
}


void model_Diagram::ImplData::unrealize()
{
  if (_selection_signal_conn.connected())
    _selection_signal_conn.disconnect();

  for (size_t c= _self->_figures.count(), i= 0; i < c; i++)
  {
    _self->_figures[i]->get_data()->unrealize();
  }
  for (size_t c= _self->_connections.count(), i= 0; i < c; i++)
  { 
    _self->_connections[i]->get_data()->unrealize();
  }
  for (size_t c= _self->_layers.count(), i= 0; i < c; i++)
  {
    _self->_layers[i]->get_data()->unrealize();
  }
  if (_self->_rootLayer.is_valid() && _self->_rootLayer->get_data())
    _self->_rootLayer->get_data()->unrealize();

  if (_canvas_view)
  {
    _canvas_view->pre_destroy();
    if (_self->owner()->get_data()->get_delegate() != NULL)
      _self->owner()->get_data()->get_delegate()->free_canvas_view(_canvas_view);

    _canvas_view = NULL;
  }
}



void model_Diagram::ImplData::member_changed(const std::string &name, const grt::ValueRef &ovalue)
{
  if (name == "zoom")
  {
    if (*_self->_zoom <= 0.1)
      _self->_zoom= 0.1;
    else if (*_self->_zoom > 2.0)
      _self->_zoom= 2.0;

    if (_canvas_view)
      _canvas_view->set_zoom((float)*_self->_zoom);
  }
  else
    if (name == "x" || name == "y")
    {
      if (_canvas_view)
        _canvas_view->set_offset(base::Point(_self->_x, _self->_y));
    }
    else if (name == "pageSettings")
    {
      update_size();
    }
    else if (name == "width" || name == "height")
    {
      update_size();
    }
}



void model_Diagram::ImplData::member_list_changed(grt::internal::OwnedList *alist, bool added, const grt::ValueRef &value)
{
  grt::BaseListRef list(alist);

  if (list == self()->_figures)
  {
    model_FigureRef figure(model_FigureRef::cast_from(value));
    figure->get_data()->set_in_view(added);
  }
  else if (list == self()->_connections)
  {
    model_ConnectionRef conn(model_ConnectionRef::cast_from(value));
    conn->get_data()->set_in_view(added);
  }
  else if (list == self()->_layers)
  {
    if (value != self()->_rootLayer)
    {
      model_LayerRef layer(model_LayerRef::cast_from(value));
      layer->get_data()->set_in_view(added);
    }
  }
  else if (list == self()->_selection)
  {
    // consistency check, selection changes shouldn't be tracked in undo history
    if (list.get_grt()->get_undo_manager()->is_enabled() && list.get_grt()->tracking_changes())
      g_warning("Undo tracking was enabled during selection change");
  }
}


void model_Diagram::ImplData::add_figure(const model_FigureRef &figure)
{  
  _self->_figures.insert(figure);
  if (figure->layer().is_valid())
    figure->layer()->figures().insert(figure);
  else
    _self->rootLayer()->figures().insert(figure);
}


void model_Diagram::ImplData::add_connection(const model_ConnectionRef &conn)
{
  _self->_connections.insert(conn);
}


void model_Diagram::ImplData::remove_figure(const model_FigureRef &figure)
{
  _self->_figures.remove_value(figure);
  if (figure->layer().is_valid())
  {
    figure->layer()->figures().remove_value(figure);
  }
}


void model_Diagram::ImplData::remove_connection(const model_ConnectionRef &conn)
{
  _self->_connections.remove_value(conn);
}


void model_Diagram::ImplData::delete_layer(const model_LayerRef &layer)
{  
  grt::AutoUndo undo(self()->get_grt(), !self()->is_global());
  
  model_LayerRef root(self()->rootLayer());

  // remove objects contained in this
  for (size_t c= layer->figures().count(), i= 0; i < c; i++)
  {
    model_FigureRef object(layer->figures().get(c-i-1));

    layer->figures().remove(c-i-1);
    root->figures().insert(object);
    object->layer(root);
  }

  _self->_layers.remove_value(layer);
  
  undo.end(_("Delete Layer from View"));
}


void model_Diagram::ImplData::select_object(const model_ObjectRef &object)
{
  if (_self->_selection.get_index(object) != grt::BaseListRef::npos)
    return;
  
  if (object.is_instance<model_Figure>())
  {
    model_Figure::ImplData *elem= dynamic_cast<model_Figure::ImplData*>(object->get_data());

    begin_selection_update();  
    if (elem && elem->get_canvas_item())
      _canvas_view->get_selection()->add(elem->get_canvas_item());

    _self->get_grt()->get_undo_manager()->disable();
    _self->_selection.insert(object);
    _self->get_grt()->get_undo_manager()->enable();
  }
  else if (object.is_instance<model_Connection>())
  {
    model_Connection::ImplData *conn= dynamic_cast<model_Connection::ImplData*>(object->get_data());

    begin_selection_update();  
    if (conn && conn->get_canvas_item())
      _canvas_view->get_selection()->add(conn->get_canvas_item());

    _self->get_grt()->get_undo_manager()->disable();
    _self->_selection.insert(object);
    _self->get_grt()->get_undo_manager()->enable();
  }
  else if (object.is_instance<model_Layer>())
  {
    model_Layer::ImplData *layer= dynamic_cast<model_Layer::ImplData*>(object->get_data());

    begin_selection_update();  
    if (layer && layer->get_area_group())
      _canvas_view->get_selection()->add(layer->get_area_group());

    _self->get_grt()->get_undo_manager()->disable();
    _self->_selection.insert(object);
    _self->get_grt()->get_undo_manager()->enable();
  }
  else
    throw std::runtime_error("trying to select invalid object");
  
  end_selection_update();
}


void model_Diagram::ImplData::unselect_object(const model_ObjectRef &object)
{
  if (object.is_instance<model_Figure>())
  {
    model_Figure::ImplData *elem= dynamic_cast<model_Figure::ImplData*>(object->get_data());

    begin_selection_update();
    if (elem && elem->get_canvas_item())
      _canvas_view->get_selection()->remove(elem->get_canvas_item());
    
    _self->get_grt()->get_undo_manager()->disable();
    _self->_selection.remove_value(object);
    _self->get_grt()->get_undo_manager()->enable();
  }
  else if (object.is_instance<model_Connection>())
  {
    model_Connection::ImplData *conn= dynamic_cast<model_Connection::ImplData*>(object->get_data());
    
    begin_selection_update();
    if (conn && conn->get_canvas_item())
      _canvas_view->get_selection()->remove(conn->get_canvas_item());

    _self->get_grt()->get_undo_manager()->disable();
    _self->_selection.remove_value(object);
    _self->get_grt()->get_undo_manager()->enable();
  }
  else if (object.is_instance<model_Layer>())
  {
    model_Layer::ImplData *layer= dynamic_cast<model_Layer::ImplData*>(object->get_data());

    begin_selection_update();
    if (layer && layer->get_area_group())
      _canvas_view->get_selection()->remove(layer->get_area_group());

    _self->get_grt()->get_undo_manager()->disable();
    _self->_selection.remove_value(object);
    _self->get_grt()->get_undo_manager()->enable();
  }
  else
    throw std::runtime_error("trying to deselect invalid object");
  
  end_selection_update();
}


void model_Diagram::ImplData::unselect_all()
{
  begin_selection_update();

  _canvas_view->get_selection()->clear();

  _self->get_grt()->get_undo_manager()->disable();
  while (_self->_selection.count() > 0)
    _self->_selection.remove(0);
  _self->get_grt()->get_undo_manager()->enable();

  end_selection_update();  
}

/**
 * Increases the selection lock count and returns true if the caller can continue.
 */
bool model_Diagram::ImplData::begin_selection_update()
{
  return ++_updating_selection == 1;
}

/**
 * Removes one lock level from the selection lock and triggers the change event if
 * it reaches 0.
 */
void model_Diagram::ImplData::end_selection_update()
{
  _updating_selection--;
  if (_updating_selection == 0)
    _selection_changed_signal(model_DiagramRef(_self));
}

void model_Diagram::ImplData::canvas_selection_changed(bool added, mdc::CanvasItem *item)
{
  if (begin_selection_update())
  {
    if (added)
    {
      model_ObjectRef object;

      if (item)
      {
        object= find_object_in_list(_self->_figures, item->get_tag());
        if (!object.is_valid())
          object= find_object_in_list(_self->_connections, item->get_tag());
        if (!object.is_valid())
          object= find_object_in_list(_self->_layers, item->get_tag());

        if (object.is_valid())
        {
          _self->get_grt()->get_undo_manager()->disable();
          if (!find_object_in_list(_self->_selection, item->get_tag()).is_valid())
            _self->_selection.insert(object);
          _self->get_grt()->get_undo_manager()->enable();
        }
      }
      else abort();
    }
    else // removed
    {
      if (item)
      {
        model_ObjectRef object(find_object_in_list(_self->_selection, item->get_tag()));

        _self->get_grt()->get_undo_manager()->disable();
        if (object.is_valid())
          _self->_selection.remove_value(object);
        _self->get_grt()->get_undo_manager()->enable();
      }
      else
      {
        _self->get_grt()->get_undo_manager()->disable();
        while (_self->_selection.count() > 0)
          _self->_selection.remove(0);
        _self->get_grt()->get_undo_manager()->enable();
      }
    }
  }

  end_selection_update();
}

static mdc::CanvasItem *get_first_realized_layer_under(const grt::ListRef<model_Layer> &list,
                                                const model_LayerRef &layer)
{
  bool found= false;
  if (!layer.is_valid())
    found= true;
  
  for (grt::ListRef<model_Layer>::const_reverse_iterator it= list.rbegin(); it != list.rend(); ++it)
  {
    if (found)
    {
      model_Layer::ImplData *layer= (*it)->get_data();
      if (layer && layer->get_area_group())
        return layer->get_area_group();
    }
    if (!found && *it == layer)
      found= true;
  }
  return 0;
}


static mdc::CanvasItem *get_first_realized_connection_under(const grt::ListRef<model_Connection> &list,
                                                     const model_ConnectionRef &connection)
{
  bool found= false;
  if (!connection.is_valid())
    found= true;
  for (grt::ListRef<model_Connection>::const_reverse_iterator it= list.rbegin(); it != list.rend(); ++it)
  {
    if (found)
    {
      model_Connection::ImplData *conn= (*it)->get_data();
      if (conn && conn->get_canvas_item())
        return conn->get_canvas_item();
    }
    if (!found && *it == connection)
      found= true;
  }
  return 0;
}


static mdc::CanvasItem *get_first_realized_figure_under(const grt::ListRef<model_Figure> &list,
                                                 const model_FigureRef &figure)
{
  bool found= false;
  if (!figure.is_valid())
    found= true;
  for (grt::ListRef<model_Figure>::const_reverse_iterator it= list.rbegin(); it != list.rend(); ++it)
  {
    if (found)
    {
      model_Figure::ImplData *fig= (*it)->get_data();
      if (fig && fig->get_canvas_item())
        return fig->get_canvas_item();
    }
    if (!found && *it == figure)
      found= true;
  }
  return 0;
}


void model_Diagram::ImplData::stack_layer(const model_LayerRef &layer, mdc::CanvasItem *layer_item)
{
  mdc::CanvasItem *item_under;

  item_under= get_first_realized_layer_under(_self->_layers, layer);

  if (item_under)
    _canvas_view->get_current_layer()->get_root_area_group()->raise_item(layer_item, item_under);
  else
    _canvas_view->get_current_layer()->get_root_area_group()->lower_item(layer_item);
}


void model_Diagram::ImplData::stack_connection(const model_ConnectionRef &conn, mdc::CanvasItem *connection_item)
{
  mdc::CanvasItem *item_under;

  item_under= get_first_realized_connection_under(_self->_connections, conn);
  if (!item_under)
    item_under= get_first_realized_layer_under(_self->_layers, model_LayerRef());

  if (item_under)
    _canvas_view->get_current_layer()->get_root_area_group()->raise_item(connection_item, item_under);
  else
    _canvas_view->get_current_layer()->get_root_area_group()->lower_item(connection_item);
}


void model_Diagram::ImplData::stack_figure(const model_FigureRef &figure, mdc::CanvasItem *figure_item)
{
  mdc::CanvasItem *item_under;

  item_under= get_first_realized_figure_under(figure->layer()->figures(), figure);
  
  _canvas_view->get_current_layer()->get_root_area_group()->raise_item(figure_item, item_under);
}


model_LayerRef model_Diagram::ImplData::get_layer_under_figure(const model_FigureRef &figure)
{
  Rect bounds;

  mdc::CanvasItem *item= figure->get_data()->get_canvas_item();
  if (item)
    bounds= item->get_root_bounds();
  else
  {
    model_LayerRef currentLayer(figure->layer());
    if (currentLayer.is_valid())
    {
      bounds.pos.x= figure->left() + currentLayer->left();
      bounds.pos.y= figure->top() + currentLayer->top();
    }
    else
    {
      bounds.pos.x= figure->left();
      bounds.pos.y= figure->top();
    }
    bounds.size.width= figure->width();
    bounds.size.height= figure->height();
  }

  for (grt::ListRef<model_Layer>::const_reverse_iterator it= _self->layers().rbegin(); it != _self->layers().rend(); ++it)
  {
    model_LayerRef layer(*it);
    Rect layerBounds;

    layerBounds.pos.x= layer->left();
    layerBounds.pos.y= layer->top();
    layerBounds.size.width= layer->width();
    layerBounds.size.height= layer->height();
    if (mdc::bounds_contain_bounds(layerBounds, bounds))
      return layer;
  }

  return _self->rootLayer();
}


bool model_Diagram::ImplData::update_layer_of_figure(const model_FigureRef &figure)
{
  bool relocated= false;
  grt::AutoUndo undo(self()->get_grt());

  model_LayerRef layer(get_layer_under_figure(figure));

  if (layer != _self->rootLayer())
  {
    // figure is in some layer
    if (layer != figure->layer())
    {
      if (figure->layer().is_valid())
        figure->layer()->figures().remove_value(figure);

      figure->layer(layer);
      layer->figures().insert(figure);
      relocated= true;
    }
    else
    {
      mdc::CanvasItem *item= figure->get_data()->get_canvas_item();
      mdc::AreaGroup *parent= layer->get_data()->get_area_group();
      // if figure stays in same layer as before, make sure that it has correct canvas parent
      if (item && parent && item->get_parent() != parent)
      {
        Point pos= item->get_root_position() - parent->get_root_position();
        parent->add(item);
        item->move_to(pos);
      }
    }
  }
  else
  {
    // figure is outside of any layer
    if (figure->layer() != _self->rootLayer())
    {
      if (figure->layer().is_valid())
        figure->layer()->figures().remove_value(figure);

      figure->layer(_self->rootLayer());
      _self->rootLayer()->figures().insert(figure);
      relocated= true;
    }
  }

  if (relocated)
    undo.end(base::strfmt(_("Updated Layer for '%s'"), figure->name().c_str()));
  else
    undo.cancel();
  
  return relocated;
}

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

bool model_Diagram::ImplData::figure_click(const model_ObjectRef &owner, mdc::CanvasItem *target,
                                           const Point &point, mdc::MouseButton button,
                                           mdc::EventState state)
{
  _item_click_signal(owner, target, point, button, state);
  
  return false;
}

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

bool model_Diagram::ImplData::figure_double_click(const model_ObjectRef &owner, mdc::CanvasItem *target,
                                                  const Point &point, mdc::MouseButton button,
                                                  mdc::EventState state)
{
  _item_double_click_signal(owner, target, point, button, state);

  return false;
}

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

bool model_Diagram::ImplData::figure_button_press(const model_ObjectRef &owner, mdc::CanvasItem *target, const Point &point, mdc::MouseButton button, mdc::EventState state)
{
  _item_mouse_button_signal(owner, target, true, point, button, state);
  
  return false;
}


bool model_Diagram::ImplData::figure_button_release(const model_ObjectRef &owner, mdc::CanvasItem *target, const Point &point, mdc::MouseButton button, mdc::EventState state)
{
  _item_mouse_button_signal(owner, target, false, point, button, state);
  
  return false;
}


bool model_Diagram::ImplData::figure_enter(const model_ObjectRef &owner, mdc::CanvasItem *target, const Point &point)
{
  _item_crossed_signal(owner, target, true, point);
  
  return false;
}


bool model_Diagram::ImplData::figure_leave(const model_ObjectRef &owner, mdc::CanvasItem *target, const Point &point)
{
  _item_crossed_signal(owner, target, false, point);
  
  return false;
}


static void update_badge(const std::string &name, const ValueRef& ovalue, const meta_TagRef &tag, BadgeFigure *badge)
{
  if (name == "label")
  {
    badge->set_text(tag->label());
  }
  else if (name == "color")
  {
    badge->set_gradient_from_color(Color::parse(tag->color()));
  }
}


void model_Diagram::ImplData::add_tag_badge_to_figure(const model_FigureRef &figure, const meta_TagRef &tag)
{
  BadgeFigure *badge= new BadgeFigure(get_canvas_view()->get_current_layer());

  badge->set_badge_id(tag->id());
  badge->set_text(tag->label());
  badge->set_gradient_from_color(Color::parse(*tag->color()));
  badge->set_text_color(Color::parse("#ffffff"));

  badge->updater_connection= tag->signal_changed()->connect(boost::bind(&update_badge, _1, _2, tag, badge));

  get_canvas_view()->get_current_layer()->add_item(badge,
                                                   get_canvas_view()->get_current_layer()->get_root_area_group());
  
  figure->get_data()->add_badge(badge);
}


void model_Diagram::ImplData::remove_tag_badge_from_figure(const model_FigureRef &figure, const meta_TagRef &tag)
{
  BadgeFigure *badge= figure->get_data()->get_badge_with_id(tag->id());
  
  if (badge)
  {
    figure->get_data()->remove_badge(badge);
    get_canvas_view()->get_current_layer()->remove_item(badge);
    delete badge;
  }
}

