#include "stdafx.h"

#include <errno.h>
#include "wb_component_basic.h"
#include "wb_model_diagram_form.h"

#include "wb_model_file.h"

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


using namespace wb;
using namespace grt;
using namespace bec;


struct HandToolContext
{
  mdc::Point mouse_pos;
  mdc::Point viewport_pos;
  bool moving;

  HandToolContext()
    : mouse_pos(0., 0.), viewport_pos(0, 0), moving(false)
  {
  }
};



WBComponentBasic::WBComponentBasic(WBContext *wb)
: WBComponent(wb)
{

}


WBComponentBasic::~WBComponentBasic()
{
}


void WBComponentBasic::load_app_options(bool update)
{
  if (!update)
  {
    grt::GRT *grt= _wb->get_grt_manager()->get_grt();

    app_ToolbarRef tools_toolbar(app_ToolbarRef::cast_from(
      grt->unserialize(make_path(_wb->get_datadir(), "data/tools_toolbar_basic.xml"))));

    _toolbars[tools_toolbar->name()]= tools_toolbar;

    app_ToolbarRef options_toolbar;

    options_toolbar= app_ToolbarRef::cast_from(
      grt->unserialize(make_path(_wb->get_datadir(), "data/model_option_toolbar_layer.xml")));
    _toolbars[options_toolbar->name()]= options_toolbar;

    options_toolbar= app_ToolbarRef::cast_from(
      grt->unserialize(make_path(_wb->get_datadir(), "data/model_option_toolbar_note.xml")));
    _toolbars[options_toolbar->name()]= options_toolbar;

    _shortcuts= grt::ListRef<app_ShortcutItem>::cast_from(
      grt->unserialize(make_path(_wb->get_datadir(),"data/shortcuts_basic.xml")));
  }
}




void WBComponentBasic::setup_canvas_tool(ModelDiagramForm *view, const std::string &tool)
{
  void *data= 0;

  if (tool == WB_TOOL_SELECT)
  {
    view->set_cursor("select");
    _wb->show_status_text("");
  }
  else if (tool == WB_TOOL_HAND)
  {
    data= new HandToolContext;
    view->set_cursor("hand");
    _wb->show_status_text(_("Drag the canvas to move it around."));
  }
  else if (tool == WB_TOOL_DELETE)
  {
    view->set_cursor("rubber");
    _wb->show_status_text(_("Click the object to delete."));
  }
  else if (tool == WB_TOOL_LAYER)
  {
    view->set_cursor("layer");
    _wb->show_status_text(_("Select an area for the new layer."));
  }
  else if (tool == WB_TOOL_NOTE)
  {
    view->set_cursor("note");
    _wb->show_status_text(_("Select an area for a text object."));
  }
  else if (tool == WB_TOOL_IMAGE)
  {
    view->set_cursor("image");
    _wb->show_status_text(_("Select a location for the image object."));
  }
  else
  {
    _wb->show_status_text("Invalid tool "+tool);
    return;
  }

  view->set_button_callback(sigc::bind<void*>(sigc::mem_fun(this, &WBComponentBasic::handle_button_event), data));
  view->set_motion_callback(sigc::bind<void*>(sigc::mem_fun(this, &WBComponentBasic::handle_motion_event), data));
  view->set_reset_tool_callback(sigc::bind<void*>(sigc::mem_fun(this, &WBComponentBasic::reset_tool), data));
}


grt::ListRef<app_ToolbarItem> WBComponentBasic::get_toolbar_items(const std::string &toolbar)
{
  if (toolbar == WB_TOOLBAR_OPTIONS)
  {
    ModelDiagramForm *form= dynamic_cast<ModelDiagramForm*>(_wb->get_active_form());

    if (form)
    {
      if (_toolbars.find(toolbar+"/"+form->get_tool()) != _toolbars.end())
        return _toolbars[toolbar+"/"+form->get_tool()]->items();
    }
    return grt::ListRef<app_ToolbarItem>();
  }
  else
    return _toolbars[toolbar]->items();
}


std::vector<std::string> WBComponentBasic::get_command_dropdown_items(const std::string &option)
{
  std::vector<std::string> items;
  if (has_suffix(option, ":Color"))
  {
    std::string colors= _wb->get_wb_options().get_string("workbench.model.Figure:ColorList");
    std::vector<std::string> colorList;

    colorList= split_string(colors, "\n");

    if (!colorList.empty())
    {
      for (size_t c= colorList.size(), i= 0; i < c; i++)
      {
        if (!colorList[i].empty() && colorList[i][0]=='#')
          items.push_back(colorList[i]);
      }
    }
    else
    {
      items.push_back("#FFAAAA");
      items.push_back("#FFFFAA");
      items.push_back("#AAFFFF");
    }
    ModelDiagramForm *form= dynamic_cast<ModelDiagramForm*>(_wb->get_active_form());
    if (form)
    {
      std::string selected= form->get_tool_argument(option);
      if (selected.empty())
        selected= _wb->get_wb_options().get_string(option);

      if (!selected.empty() && std::find(items.begin(), items.end(), selected) == items.end())
        items.push_back(selected);

      form->set_tool_argument(option, selected);
    }
  }
  else
    throw std::logic_error("Unknown option "+option);
  return items;
}


grt::ListRef<app_ShortcutItem> WBComponentBasic::get_shortcut_items()
{
  return _shortcuts;
}

//--------------------------------------------------------------------------------
// Object Placement



bool WBComponentBasic::handles_figure(const model_ObjectRef &object)
{
  if (object.is_instance(model_Layer::static_class_name()) ||
    object.is_instance(workbench_model_NoteFigure::static_class_name()) ||
    object.is_instance(workbench_model_ImageFigure::static_class_name()))
    return true;
  return false;
}



grt::ValueRef WBComponentBasic::place_layer_grt(grt::GRT *grt, ModelDiagramForm *form,
                                             const mdc::Rect &rect)
{
  model_DiagramRef view(form->get_model_diagram());

  std::string color;

  if (form->get_tool_argument("workbench.model.Layer:Color").empty())
    color= _wb->get_wb_options().get_string("workbench.model.Layer:Color");
  else
    color= form->get_tool_argument("workbench.model.Layer:Color");

  model_LayerRef layer;

  grt::AutoUndo undo(grt);
  
  layer= view->placeNewLayer(rect.xmin(), rect.ymin(), rect.width(), rect.height(), _("New Layer"));

  if (layer.is_valid())
    layer->color(color);

  undo.end(_("Place New Layer"));
  
  return layer;
}


void WBComponentBasic::place_layer(ModelDiagramForm *view, const mdc::Rect &rect)
{
  try
  {
    _wb->execute_in_grt_thread("Place new layer in model",
                        sigc::bind<ModelDiagramForm*,mdc::Rect>(sigc::mem_fun(this, &WBComponentBasic::place_layer_grt),
                                                           view,
                                                           rect));
    _wb->show_status_text(_("Created new layer."));
  }
  catch (std::exception &exc)
  {
    _wb->show_exception(_("Place new layer"), exc);
  }
}


//-------------------------------------------------------------------------------------
// Deletion


void WBComponentBasic::delete_selection()
{
  ModelDiagramForm *form;

  if ((form= dynamic_cast<ModelDiagramForm*>(_wb->get_active_form())))
  {
    grt::ListRef<model_Object> selection= form->get_selection();

    std::vector<model_ObjectRef> objects;

    for (size_t c= selection.count(), i= 0; i < c; i++)
    {
      if (selection.get(i).is_instance(model_Object::static_class_name()))
        objects.push_back(model_ObjectRef::cast_from(selection.get(i)));
    }

    std::map<std::string, bool> options;

    {
      std::string option;

      option= get_wb()->get_wb_options().get_string("workbench.physical:DeleteObjectConfirmation", "ask");
      if (option == "delete")
        options["deletedbobjects"]= true;
      else if (option == "keep")
        options["deletedbobjects"]= false;
    }

    for (size_t c= objects.size(), i= 0; i < c; i++)
      _wb->delete_object(objects[i], options);
  }
}


bool WBComponentBasic::delete_model_object(const model_ObjectRef &object, std::map<std::string,bool> &options)
{
  grt::AutoUndo undo(_wb->get_grt());

  if (object.is_instance(model_Figure::static_class_name()))
  {
    model_FigureRef figure(model_FigureRef::cast_from(object));
  
    figure->layer()->figures().remove_value(figure);    
    model_DiagramRef::cast_from(figure->owner())->figures().remove_value(figure);
    
    undo.end(strfmt(_("Delete '%s' Figure"), figure.get_metaclass()->get_attribute("caption").c_str()));
  }
  else if (object.is_instance(model_Layer::static_class_name()))
  {
    model_LayerRef layer(model_LayerRef::cast_from(object));
    model_DiagramRef view(model_DiagramRef::cast_from(layer->owner()));

    view->deleteLayer(layer);

    undo.end(strfmt(_("Delete '%s' Layer"), layer.get_metaclass()->get_attribute("caption").c_str()));
  }
  else 
    return false;

  return true;
}


void WBComponentBasic::delete_object(ModelDiagramForm *view, const mdc::Point &pos)
{
  model_ObjectRef object(view->get_object_at(pos));

  if (object.is_valid())
  {
    std::map<std::string, bool> options;

    if (_wb->delete_object(object, options))
      _wb->show_status_text(strfmt(_("Deleted %s"), object.get_metaclass()->get_attribute("caption").c_str()));
  }
}


void WBComponentBasic::activate_canvas_object(const model_ObjectRef &figure, bool newwindow)
{
  if (figure.is_instance(workbench_model_NoteFigure::static_class_name()))
    _wb->open_object_editor(figure, newwindow);
  else if (figure.is_instance(workbench_model_ImageFigure::static_class_name()))
    _wb->open_object_editor(figure, newwindow);
  else if (figure.is_instance(model_Layer::static_class_name()))
    _wb->open_object_editor(figure, newwindow);
}



model_ObjectRef WBComponentBasic::clone_object(const model_ObjectRef &object, const model_LayerRef &destlayer, bool copy_only)
{
  grt::AutoUndo undo(_wb->get_grt());

  if (object.is_instance(model_Figure::static_class_name()))
  {
    model_FigureRef figure(model_FigureRef::cast_from(grt::copy_object(_wb->get_grt_manager()->get_grt(), object)));

    if (destlayer.is_valid())
    {
      figure->layer(destlayer);
      figure->owner(destlayer->owner());

      if (!copy_only)
      {
        if (grt::find_named_object_in_list(destlayer->owner()->figures(), object->name()).is_valid())
          figure->name(grt::get_name_suggestion_for_list_object(destlayer->owner()->figures(),
            *object->name()+"_copy"));

        destlayer->owner()->addFigure(figure);
        undo.end("Duplicate Object");
      }
      else
        destlayer->figures().insert(figure);
    }
    else
    {
      figure->owner(model_DiagramRef());
      figure->layer(model_LayerRef());    
    }
    return figure;
  }
  else if (object.is_instance(model_Layer::static_class_name()))
  {
    std::set<std::string> skip;

    skip.insert("figures");

    model_LayerRef original(model_LayerRef::cast_from(object));

    model_LayerRef layer(model_LayerRef::cast_from(grt::copy_object(_wb->get_grt_manager()->get_grt(), object, skip)));

    if (!copy_only && destlayer.is_valid())
      layer->owner(destlayer->owner());
/*
    // copy stuff that's to be duplicated (sublayers and figures)
    for (size_t c= original.figures()->count(), i= 0; i < c; i++)
    {
      model_FigureRef figure(original.figures()[i]);

      WBComponent *compo= _wb->get_component_handling(figure);
      if (compo)
      {
        figure= model_FigureRef::cast_from(compo->clone_object(figure, layer, copy_only));
      //    layer->figures().insert(figure);
      }
    }

    for (size_t c= original.subLayers()->count(), i= 0; i < c; i++)
    {
      model_LayerRef sublayer(original.subLayers()[i]);

      sublayer= model_LayerRef::cast_from(clone_object(sublayer, layer, copy_only));
      //layer.subLayers()->insert(sublayer);
    }
*/
    if (!copy_only)
    {
      if (grt::find_named_object_in_list(destlayer->owner()->layers(), object->name()).is_valid())
        layer->name(grt::get_name_suggestion_for_list_object(destlayer->owner()->layers(),
          *object->name()+"_copy"));

      destlayer->owner()->layers().insert(layer);
      undo.end("Duplicate Object");
    }
    else
      layer->owner(model_DiagramRef());


    return layer;
  }
  return model_ObjectRef();
}


//-------------------------------------------------------------------------------------
// Handlers

bool WBComponentBasic::handle_button_event(ModelDiagramForm *view, mdc::MouseButton button, bool press, 
                                         mdc::Point pos, mdc::EventState state, void *data)
{
  std::string tool= view->get_tool();

  if (tool == WB_TOOL_HAND && (button == mdc::ButtonLeft || button == mdc::ButtonMiddle))
  {
    HandToolContext *hcontext= reinterpret_cast<HandToolContext*>(data);
    hcontext->moving= press;
    if (press)
    {
      mdc::Rect r= view->get_view()->get_viewport();
      int wx, wy;
      hcontext->viewport_pos= r.pos;
      view->get_view()->canvas_to_window(pos, wx, wy);
      hcontext->mouse_pos= mdc::Point(wx, wy);;
    }
    return true;
  }

  if (button != mdc::ButtonLeft)
    return false;

  if (tool == WB_TOOL_DELETE)
  {
    if (press)
      delete_object(view, pos);
    return true;
  }
  else if (tool == WB_TOOL_LAYER)
  {
    if (press)
      view->get_view()->start_dragging_rectangle(pos);
    else
    {
      mdc::Rect rect= view->get_view()->finish_dragging_rectangle();

      if (rect.width() < 5 || rect.height() < 5)
      {
        _wb->show_status_text(_("Please select a larger area."));
      }
      else
      {
        place_layer(view, rect);
        view->reset_tool(true);
      }
    }
    return true;
  }
  else if (tool == WB_TOOL_NOTE)
  {
  /*  if (press)
      view->get_view()->start_dragging_rectangle(pos);
    else
    {
      mdc::Rect rect= view->get_view()->finish_dragging_rectangle();

      if (rect.width() < 5 || rect.height() < 5)
      {
        _wb->show_status_text(_("Please select a larger area."));
      }
      else*/
      if (press) {
        workbench_model_NoteFigureRef note(workbench_model_NoteFigureRef::cast_from(
          place_object(view, pos, "workbench.model.NoteFigure")));
        if (note.is_valid())
        {
          note->name(grt::get_name_suggestion_for_list_object(grt::ObjectListRef::cast_from(view->get_model_diagram()->figures()),
                    "text"));

          note->text("Text");
         // note.width(rect.width());
         // note.height(rect.height());
        }
        view->reset_tool(true);
      }
    //}
    return true;
  }
  else if (tool == WB_TOOL_IMAGE)
  {
    if (press)
    {
      workbench_model_ImageFigureRef image;
      std::string filename= _wb->show_file_dialog("open", _("Place Image"), "PNG Image Files (*.png)|*.png");

      if (!filename.empty())
      {
         {
          // if the file is not a PNG, we get a generic "out of memory" error, so try to detect that
          FILE *f= fopen(filename.c_str(), "r");
          if (!f)
          {
            _wb->show_error(_("Cannot Open Image File"), g_strerror(errno));
            view->reset_tool(true);
            return true;
          }
          char buffer[10];
          if (fread(buffer, 1, sizeof(buffer), f) < 4
            || strncmp(buffer, "\211PNG", 4)!=0)
          {
            fclose(f);
            _wb->show_error(_("Cannot Open Image File"), _("Images must be in PNG format."));
            view->reset_tool(true);
            return true;
          }
          fclose(f);
        }

        image= workbench_model_ImageFigureRef::cast_from(place_object(view, pos, "workbench.model.ImageFigure"));

        if (image.is_valid())
          image->setImageFile(filename);
      }

      view->reset_tool(true);
    }
    return true;
  }
  return false;
}


bool WBComponentBasic::handle_motion_event(ModelDiagramForm *view, mdc::Point pos, mdc::EventState state, void *data)
{
  std::string tool= view->get_tool();

  if (tool == WB_TOOL_HAND)
  {
    HandToolContext *hcontext= reinterpret_cast<HandToolContext*>(data);
    mdc::CanvasView *cview= view->get_view();

    if (hcontext->moving)
    {
      int wx, wy;
      cview->canvas_to_window(pos, wx, wy);
      mdc::Point wpos(wx, wy);

      mdc::Point mouse_pos= hcontext->mouse_pos;
      mdc::Point viewport_pos= hcontext->viewport_pos;

      mdc::Point newp= viewport_pos - wpos + mouse_pos;

      mdc::Rect r= cview->get_viewport();
      mdc::Size sz= cview->get_total_view_size();

      if(newp.x < 0)
        newp.x= 0;
      if(newp.y < 0)
        newp.y= 0;
      if(newp.x > (sz.width - r.size.width))
        newp.x= sz.width - r.size.width;
      if(newp.y > (sz.height - r.size.height))
        newp.y= sz.height - r.size.height;

      cview->set_offset(newp);
      
      return true;
    }
  }
  return false;
}


void WBComponentBasic::reset_tool(ModelDiagramForm *view, void *data)
{
  if (view->get_tool() == WB_TOOL_HAND)
  {
    HandToolContext *hcontext= reinterpret_cast<HandToolContext*>(data);

    delete hcontext;
  }
}

