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


#include "stdafx.h"

#include "wb_overview_diagram.h"
#include "wb_overview_physical.h"
#include "wb_overview_physical_schema.h"

#include "workbench/wb_context.h"
#include "workbench/wb_context_ui.h"
#include "workbench/wb_command_ui.h"
#include "wb_component_physical.h"

#include "grt/incremental_list_updater.h"

#include "wb_overview_privileges.h"

#include "grt/icon_manager.h"
#include "grt/clipboard.h"
#include "base/ui_form.h"
#include "grt/exceptions.h"

#include "grts/structs.db.h"
#include "base/string_utilities.h"

#include "mforms/toolbar.h"
#include "mforms/menubar.h"

#include "base/trackable.h"

/**
 * @file  wb_overview_physical.cpp
 * @brief Physical Model specific panels for the overview window
 */

using namespace bec;
using namespace wb;
using namespace wb::internal;
using namespace base;

# define SCRIPT_NODE NodeId(3)
# define NOTE_NODE NodeId(4)


PhysicalSchemataNode::PhysicalSchemataNode(workbench_physical_ModelRef amodel)
: ContainerNode(OverviewBE::OGroup)
{  
  type= OverviewBE::ODivision;
  object= amodel->catalog();
  model= amodel;
  label= _("Physical Schemata");
  small_icon= 0;
  large_icon= 0;
  expanded= true;
  display_mode= OverviewBE::MSmallIcon;
}


void PhysicalSchemataNode::init()
{
  grt::ListRef<db_Schema> schemata= model->catalog()->schemata();
  for (size_t c= schemata.count(), i= 0; i < c; i++)
    children.push_back(create_child_node(schemata.get(i)));
}


OverviewBE::Node * PhysicalSchemataNode::create_child_node(db_SchemaRef schema)
{
  PhysicalSchemaNode *node= new PhysicalSchemaNode(schema);
  node->init();
  return node;
}


bool PhysicalSchemataNode::add_object(WBContext *wb)
{
  wb->get_component<WBComponentPhysical>()->add_new_db_schema(model);
  return true;
}


void PhysicalSchemataNode::delete_object(WBContext *wb)
{
  //dynamic_cast<WBComponentPhysical*>(wb->get_component("physical"))->delete_
}


class SchemaListUpdater : 
  public IncrementalListUpdater<std::vector<OverviewBE::Node*>::iterator, OverviewBE::Node*, size_t>
{
  virtual dest_iterator get_dest_iterator()
  {
    return _nodes.begin();
  }

  virtual source_iterator get_source_iterator()
  {
    return 0;
  }
  
  virtual dest_iterator increment_dest(dest_iterator &iter)
  {
    return ++iter;
  }

  virtual source_iterator increment_source(source_iterator &iter)
  {
    return ++iter;
  }

  virtual bool has_more_dest(dest_iterator iter)
  {
    return iter != _nodes.end();
  }

  virtual bool has_more_source(source_iterator iter)
  {
    return _schemata.is_valid() && iter < _schemata.count();
  }
  
  virtual bool items_match(dest_iterator diter, source_iterator siter)
  {
    return (*diter)->object == _schemata.get(siter);
  }

  virtual dest_ref get_dest(dest_iterator iter)
  {
    _reused_items.insert(*iter);
    return *iter;
  }

  virtual void update(dest_ref dest_item, source_iterator source_item)
  {
    dest_item->refresh();
  }

  // begin adding items to the begginning of the dest list
  virtual dest_iterator begin_adding()
  {
    for (dest_iterator i= _nodes.begin(); i != _nodes.end(); ++i)
    {
      if (_reused_items.find(*i) == _reused_items.end())
         delete *i;
    }
    _nodes.clear();

    return _nodes.end();
  }
 
  virtual dest_iterator add(dest_iterator &iter, source_iterator source_item)
  {
    return ++_nodes.insert(iter, _schema_node_instantiation_slot(_schemata.get(source_item)));
  }

  virtual dest_iterator add(dest_iterator &iter, dest_ref item)
  {
    // store the items that are reused so they're not deleted later
    _reused_items.insert(item);

    return ++_nodes.insert(iter, item);
  }

  // end adding items to the dest item, stuff after the last item added must be removed
  virtual void end_adding(dest_iterator iter)
  {
  }

  std::vector<OverviewBE::Node*> &_nodes;
  std::set<OverviewBE::Node*> _reused_items;
  grt::ListRef<db_Schema> _schemata;
  typedef boost::function<OverviewBE::Node * (db_SchemaRef)> SchemaNodeInstantiationSlot;
  SchemaNodeInstantiationSlot _schema_node_instantiation_slot;

public:
  SchemaListUpdater(std::vector<OverviewBE::Node*> &nodes, const grt::ListRef<db_Schema> &schemata, SchemaNodeInstantiationSlot schema_node_instantiation_slot)
    : _nodes(nodes), _schemata(schemata), _schema_node_instantiation_slot(schema_node_instantiation_slot)
  {
  }
};


void PhysicalSchemataNode::refresh_children()
{
  focused= 0;

  SchemaListUpdater updater(children, db_CatalogRef::cast_from(object)->schemata(), boost::bind(&PhysicalSchemataNode::create_child_node, this, _1));

  updater.execute();
}


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

class ModelObjectNode : public OverviewBE::ObjectNode, public base::trackable
{
public:
  std::string member;
  
  virtual void delete_object(WBContext *wb)
  {
    grt::AutoUndo undo(wb->get_grt());
    // removal from the list will trigger deletion of the file automatically

    grt::ListRef<GrtObject>::cast_from(object->owner().get_member(member)).remove_value(object);

    undo.end(strfmt(_("Delete '%s'"), object->name().c_str()));
  }

  virtual bool is_deletable()
  {
    return true;
  }

  virtual bool is_renameable() 
  { 
    return true;
  }

  virtual bool rename(WBContext *wb, const std::string &name)
  {
    //grt::GRT *grt= wb->get_grt_manager()->get_grt();
    //QQQgrt->lock_tree_write();

    workbench_physical_ModelRef model(workbench_physical_ModelRef::cast_from(object->owner()));
    grt::ListRef<GrtStoredNote> notes;
    
    if (object.is_instance(db_Script::static_class_name()))
      notes= grt::ListRef<GrtStoredNote>::cast_from(model->scripts());
    else
      notes= model->notes();

    for (size_t c= notes.count(), i= 0; i < c; i++)
    {
      GrtStoredNoteRef note(notes[i]);

      if (note != object && *note->name() == name)
      {
        //QQQgrt->unlock_tree_write();
        throw bec::validation_error(_("Duplicate object name."));
      }
    }

    grt::AutoUndo undo(wb->get_grt());
    object->name(name);
    undo.end(strfmt(_("Rename '%s' to '%s'"), 
      object->name().c_str(), name.c_str()));
    //QQQgrt->unlock_tree_write();
    return true;
  }

  //XXX hack to remove Edit Notes... from script menu.. remove once bug is fixed
  virtual int get_popup_menu_items(WBContext *wb, bec::MenuItemList &items)
  {
    int c= OverviewBE::ObjectNode::get_popup_menu_items(wb, items);

    if (object.is_instance(db_Script::static_class_name()))
    {
      for (bec::MenuItemList::iterator iter= items.begin();
        iter != items.end(); ++iter)
      {
        if (iter->name == "plugin:wb.plugin.edit.stored_note")
        {
          items.erase(iter);
          c--;
          break;
        }
      }
    }

    return c;
  }
};


SQLScriptsNode::SQLScriptsNode(workbench_physical_ModelRef model, PhysicalOverviewBE *owner)
: ContainerNode(OverviewBE::OItem), _owner(owner), _model(model)
{
  object= model;
  id= model->id() + "/scripts";

  type= OverviewBE::ODivision;
  label= _("SQL Scripts");
  expanded= false;
  display_mode= OverviewBE::MLargeIcon;
 
  refresh_children();
}


int SQLScriptsNode::get_popup_menu_items(WBContext *wb, bec::MenuItemList &items)
{
  bec::MenuItem item;

  item.type= bec::MenuSeparator;
  items.push_back(item);

  item.type= bec::MenuAction;
  item.name= "builtin:add_script_file";
  item.caption= _("Add Script File...");
  items.push_back(item);

  return 2;
}


bool SQLScriptsNode::add_new(WBContext *wb)
{
  WBComponentPhysical *compo= wb->get_component<WBComponentPhysical>();
  
  compo->add_new_stored_script(_model, "");
  return true;
}

static void script_object_changed(const std::string &member, const grt::ValueRef &value,
                           PhysicalOverviewBE *owner)
{
  if (member == "name")
    owner->send_refresh_scripts();
}

void SQLScriptsNode::refresh_children()
{
  clear_children();
  if (_model->scripts().is_valid())
  {
    for (size_t c= _model->scripts().count(), i= 0; i < c; i++)
    {
      db_ScriptRef script(_model->scripts()[i]);
      ModelObjectNode *node= new ModelObjectNode();
      
      node->member= "scripts";
      node->label= script->name();
      node->object= script;
      node->small_icon= IconManager::get_instance()->get_icon_id(script.get_metaclass(), Icon16);
      node->large_icon= IconManager::get_instance()->get_icon_id(script.get_metaclass(), Icon48);
      
      script->signal_changed()->connect(boost::bind(script_object_changed, _1, _2, _owner));
      children.push_back(node);
    }
    
    OverviewBE::AddObjectNode *add_node= new OverviewBE::AddObjectNode(boost::bind(&SQLScriptsNode::add_new, this, _1));
    
    add_node->label= _("Add Script");
    add_node->small_icon= IconManager::get_instance()->get_icon_id("db.Script.$.png", Icon16, "add");
    add_node->large_icon= IconManager::get_instance()->get_icon_id("db.Script.$.png", Icon48, "add");
    children.insert(children.begin(), add_node);
  }
}


NotesNode::NotesNode(workbench_physical_ModelRef model, PhysicalOverviewBE *owner)
: ContainerNode(OverviewBE::OItem), _owner(owner), _model(model)
{
  object= model;
  id= model->id() + "/notes";

  type= OverviewBE::ODivision;
  label= _("Model Notes");
  expanded= false;
  display_mode= OverviewBE::MLargeIcon;
  
  refresh_children();
}


int NotesNode::get_popup_menu_items(WBContext *wb, bec::MenuItemList &items)
{
  bec::MenuItem item;

  item.type= bec::MenuSeparator;
  items.push_back(item);

  item.type= bec::MenuAction;
  item.name= "builtin:add_note_file";
  item.caption= _("Add Note File...");
  items.push_back(item);

  return 2;
}


bool NotesNode::add_new(WBContext *wb)
{
  WBComponentPhysical *compo= wb->get_component<WBComponentPhysical>();
  
  compo->add_new_stored_note(_model, "");
  return true;
}


static void note_object_changed(const std::string &member, const grt::ValueRef &value,
                           PhysicalOverviewBE *owner)
{
  if (member == "name")
    owner->send_refresh_notes();
}


void NotesNode::refresh_children()
{
  clear_children();
  if (_model->notes().is_valid())
  {  
    for (size_t c= _model->notes().count(), i= 0; i < c; i++)
    {
      GrtStoredNoteRef note(_model->notes()[i]);
      ModelObjectNode *node= new ModelObjectNode();
      
      node->member= "notes";
      node->label= note->name();
      node->object= note;
      node->small_icon= IconManager::get_instance()->get_icon_id(note.get_metaclass(), Icon16);
      node->large_icon= IconManager::get_instance()->get_icon_id(note.get_metaclass(), Icon48);
      
      note->signal_changed()->connect(boost::bind(note_object_changed, _1, _2, _owner));
      children.push_back(node);
    }
    
    OverviewBE::AddObjectNode *add_node= new OverviewBE::AddObjectNode(boost::bind(&NotesNode::add_new, this, _1));
    add_node->label= _("Add Note");
    add_node->small_icon= IconManager::get_instance()->get_icon_id("GrtStoredNote.$.png", Icon16, "add");
    add_node->large_icon= IconManager::get_instance()->get_icon_id("GrtStoredNote.$.png", Icon48, "add");
    children.insert(children.begin(), add_node);
  }
}




class PhysicalRootNode : public OverviewBE::ContainerNode
{
public:
  PhysicalRootNode(workbench_physical_ModelRef model, PhysicalOverviewBE *owner)
    : ContainerNode(OverviewBE::ODivision)
  {
    type= OverviewBE::ORoot;
    if (model->rdbms().is_valid())
      label= strfmt("%s Model", model->rdbms()->caption().c_str());
    expanded= true;
    object= model;
    display_mode= OverviewBE::MSmallIcon;

    children.push_back(new DiagramListNode(model));

    {
      internal::PhysicalSchemataNode *node= new internal::PhysicalSchemataNode(model);
      node->init();
      children.push_back(node);
    }

    children.push_back(new internal::PrivilegeInfoNode(model->catalog(), owner));

    children.push_back(new internal::SQLScriptsNode(model, owner));
    children.push_back(new internal::NotesNode(model, owner));
  }
};




PhysicalOverviewBE::PhysicalOverviewBE(WBContext *wb)
: OverviewBE(wb), _menu(0), _toolbar(0)
{
  _schemata_node_index= 1;
  _menu = 0;
}


PhysicalOverviewBE::~PhysicalOverviewBE()
{
  delete _menu;
}

mforms::ToolBar *PhysicalOverviewBE::get_toolbar()
{
  if (!_toolbar)
  {
    _toolbar = _wb->get_ui()->get_command_ui()->create_toolbar("data/model_toolbar.xml");
  }
  return _toolbar;
}

OverviewBE::ContainerNode * PhysicalOverviewBE::create_root_node(workbench_physical_ModelRef model, PhysicalOverviewBE *owner)
{
  return new PhysicalRootNode(model, owner);
}

bool PhysicalOverviewBE::can_close()
{
  return _wb->can_close_document();
}

bool PhysicalOverviewBE::close()
{
  return _wb->close_document();
}

std::string PhysicalOverviewBE::identifier() const
{
  return "overview.physical";
}


std::string PhysicalOverviewBE::get_form_context_name() const
{
  return WB_CONTEXT_PHYSICAL_OVERVIEW;
}

std::string PhysicalOverviewBE::get_title()
{
  const char* dirty_mark = "";
  if (_wb->has_unsaved_changes())
    dirty_mark= "*";
  if (_model.is_valid() && _model->rdbms().is_valid())
      return strfmt(_("%s Model%s"), _model->rdbms()->caption().c_str(), dirty_mark);
  return std::string(_("MySQL Model")) + dirty_mark;
}


void PhysicalOverviewBE::set_model(workbench_physical_ModelRef model)
{  
  if (_root_node)
    delete _root_node;

  _model= model;
  _root_node= create_root_node(model, this);

  tree_changed();
}


model_ModelRef PhysicalOverviewBE::get_model()
{
  return _model;
}


static bool has_selection(PhysicalOverviewBE *overview)
{
  grt::ListRef<GrtObject> selection(overview->get_selection());
  return selection.is_valid() && selection.count() > 0;
}

mforms::MenuBar *PhysicalOverviewBE::get_menubar()
{
  if (!_menu)
  {
    _menu = _wb->get_ui()->get_command_ui()->create_menubar_for_context(WB_CONTEXT_PHYSICAL_OVERVIEW);
    
    static const char* diagram_only_items[] = {
      "diagram_size",
      "fnotation",
      "rnotation",
      "wb.edit.goToNextSelected",
      "wb.edit.goToPreviousSelected",
      "wb.edit.selectSimilar",
      "wb.edit.selectConnected",
      "wb.edit.toggleGridAlign",
      "wb.edit.toggleGrid",
      "wb.edit.togglePageGrid",
      "wb.view.zoomDefault",
      "wb.view.zoomIn",
      "wb.view.zoomOut",
      "wb.view.setFigureNotation",
      "wb.view.setRelationshipNotation",
      "wb.view.setMarker:1",
      "wb.view.setMarker:2",
      "wb.view.setMarker:3",
      "wb.view.setMarker:4",
      "wb.view.setMarker:5",
      "wb.view.setMarker:6",
      "wb.view.setMarker:7",
      "wb.view.setMarker:8",
      "wb.view.setMarker:9",
      "wb.view.goToMarker:1",
      "wb.view.goToMarker:2",
      "wb.view.goToMarker:3",
      "wb.view.goToMarker:4",
      "wb.view.goToMarker:5",
      "wb.view.goToMarker:6",
      "wb.view.goToMarker:7",
      "wb.view.goToMarker:8",
      "wb.view.goToMarker:9",
      NULL
    };
    
    for (int i= 0; diagram_only_items[i]; i++)
      _menu->set_item_enabled(diagram_only_items[i], false);
    
    std::vector<mforms::MenuItem*> items(_menu->find_item("arrange")->get_subitems());
    for (std::vector<mforms::MenuItem*>::const_iterator i = items.begin(); i != items.end(); ++i)
    {
      (*i)->set_enabled(false);
    }
    
    mforms::MenuItem *item = _menu->find_item("wb.edit.editObject");
    if (item)
      item->set_validator(boost::bind(has_selection, this));
    item = _menu->find_item("wb.edit.editObjectInNewWindow");
    if (item)
      item->set_validator(boost::bind(has_selection, this));    
  }
  return _menu;
}


internal::PhysicalSchemaNode *PhysicalOverviewBE::get_active_schema_node()
{
  NodeId node(get_focused_child(NodeId(_schemata_node_index)));
  
  if (node.is_valid())
    return dynamic_cast<PhysicalSchemaNode*>(get_node(node));
  return 0;
}

void PhysicalOverviewBE::send_refresh_diagram(const model_DiagramRef &view)
{
  if (view.is_valid())
  {
    NodeId node= get_node_child_for_object(NodeId(0), view);
    
    send_refresh_node(node);
  }
  else
  {
    send_refresh_children(NodeId(0));
  }
}


void PhysicalOverviewBE::send_refresh_roles()
{
  send_refresh_children(NodeId(2).append(1));
}

void PhysicalOverviewBE::send_refresh_users()
{
  send_refresh_children(NodeId(2).append(0));
}


void PhysicalOverviewBE::send_refresh_scripts()
{
  send_refresh_children(SCRIPT_NODE);
}


void PhysicalOverviewBE::send_refresh_notes()
{
  send_refresh_children(NOTE_NODE);
}


void PhysicalOverviewBE::send_refresh_schema_list()
{
  send_refresh_children(NodeId(_schemata_node_index));
}


void PhysicalOverviewBE::send_refresh_for_schema(const db_SchemaRef &schema, bool refresh_object_itself)
{
  NodeId schema_node= get_node_child_for_object(NodeId(_schemata_node_index), schema);

  if (schema_node.is_valid() && refresh_object_itself)
    send_refresh_node(schema_node);
  else
    send_refresh_children(NodeId(_schemata_node_index));
}


void PhysicalOverviewBE::send_refresh_for_schema_object(const GrtObjectRef &object, bool refresh_object_itself)
{
  NodeId schema_node;

  NodeId schemata_node= NodeId(_schemata_node_index);
  schema_node= get_node_child_for_object(schemata_node, object->owner());

  if (object.is_instance(db_Table::static_class_name()))
    schema_node.append(0);
  else if (object.is_instance(db_View::static_class_name()))
    schema_node.append(1);
  else if (object.is_instance(db_Routine::static_class_name()))
    schema_node.append(2);
  else if (object.is_instance(db_RoutineGroup::static_class_name()))
    schema_node.append(3);

  if (refresh_object_itself)
  {
    // find the object node in the list
    NodeId object_node= get_node_child_for_object(schema_node, object);

    if (object_node.is_valid())
      send_refresh_node(object_node);

    return;
  }

  if (schema_node.is_valid())
    send_refresh_children(schema_node);
}


void PhysicalOverviewBE::refresh_node(const bec::NodeId &node_id, bool children)
{
  Node *node= get_node(node_id);
  if ( node )
  {
    node->refresh();

    if ( children )
    {
      OverviewBE::ContainerNode* n = dynamic_cast<OverviewBE::ContainerNode*>(node);
      if ( n )
        n->refresh_children();
    }
  }
}


std::string PhysicalOverviewBE::get_node_drag_type(const bec::NodeId &node)
{
  // for schema objects
  if (node.depth() > 1 && node[0] == 1)
    return WB_DBOBJECT_DRAG_TYPE;
  else if (node == SCRIPT_NODE)
    return "file";
  else if (node == NOTE_NODE)
    return "file";
  return OverviewBE::get_node_drag_type(node);
}


bool PhysicalOverviewBE::should_accept_file_drop_to_node(const bec::NodeId &node, const std::string &path) 
{
  return true;
}


void PhysicalOverviewBE::add_file_to_node(const bec::NodeId &node, const std::string &path)
{
  if (node == SCRIPT_NODE)
    _wb->get_component<WBComponentPhysical>()->add_new_stored_script(_model, path);
  else if (node == NOTE_NODE)
    _wb->get_component<WBComponentPhysical>()->add_new_stored_note(_model, path);
  else
    throw std::logic_error("Cannot add file to node");
}


bool PhysicalOverviewBE::get_file_data_for_node(const bec::NodeId &node, char *&data, size_t &length)
{
  GrtStoredNoteRef note(GrtStoredNoteRef::cast_from(get_node(node)->object));

  data= 0;

  if (note.is_valid())
  {
    std::string text= _wb->get_attached_file_contents(note->filename());
    
    data= (char*)g_memdup(text.data(), text.size());
    length= text.size();

    return true;
  }

  return false;
}


std::string PhysicalOverviewBE::get_file_for_node(const bec::NodeId &node)
{
  GrtStoredNoteRef note(GrtStoredNoteRef::cast_from(get_node(node)->object));

  if (note.is_valid())
    return _wb->get_attached_file_tmp_path(note->filename());
  return "";
}


bool PhysicalOverviewBE::can_undo()
{
  return _wb->get_grt()->get_undo_manager()->can_undo();
}

bool PhysicalOverviewBE::can_redo()
{
  return _wb->get_grt()->get_undo_manager()->can_redo();
}


void PhysicalOverviewBE::undo()
{
  _wb->get_grt()->get_undo_manager()->undo();
}

void PhysicalOverviewBE::redo()
{
  _wb->get_grt()->get_undo_manager()->redo();
}

