/* 
 * Copyright (c) 2009, 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 "base/log.h"

#include "diff/diffchange.h"
#include "diff/grtdiff.h"
#include "diff/changeobjects.h"
#include "diff/changelistobjects.h"

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

#include <fstream>
#include <set>

#include "grtdb/catalog_templates.h"
#include "module_db_mysql_shared_code.h"

DEFAULT_LOG_DOMAIN("difftree");

//using namespace wb;
using namespace bec;

std::string utf_to_upper(const char *str)
{
  char *n= g_utf8_strup(str, g_utf8_strlen(str, -1));
  std::string retval(n);
  g_free(n);
  return retval;
}

namespace
{
  struct Output {
    std::ostream& ostream;
    Output(std::ostream& os) : ostream(os) {}
    void operator () (const DiffNode *node) { ostream << *node; }
  };
}

std::ostream& operator << (std::ostream& os, const DiffNode& node)
{
  os << "\n<diffnode is_modified='" << (node.is_modified() ? 1 : 0) << "'";

  if(node.get_model_part().is_valid_object())
    os << " model_name='" << node.get_model_part().get_name() << "'";
  if(node.get_db_part().is_valid_object())
    os << " db_name='" << node.get_db_part().get_name() << "'";
  if(node.get_application_direction() == DiffNode::ApplyToModel)
    os << "dir='model'";
  else if(node.get_application_direction() == DiffNode::ApplyToDb)
    os << "dir='db'";
  else if(node.get_application_direction() == DiffNode::DontApply)
    os << "dir='dontapply'";
  
  os << " >";
  

  std::for_each(node.get_children_begin(), node.get_children_end(), Output(os));

  os << "\n</diffnode>";
  return os;
}

namespace
{
  struct CompareName
  {
    std::string name;
    const bool _case_sensitive;

    CompareName(const std::string& n, const bool case_sensitive)
      : name(n.c_str()),_case_sensitive(case_sensitive) {}

    bool operator () (const DiffNode *node) 
    { 
      if(node->get_db_part().is_valid_object())
      {
      std::string node_name(
        get_old_object_name_for_key(node->get_db_part().get_object(), _case_sensitive));

      return (name.compare(node_name) == 0);
      }
      else if(node->get_model_part().is_valid_object())
      {
      std::string node_name(
        get_old_object_name_for_key(node->get_model_part().get_object(), _case_sensitive));

      return (name.compare(node_name) == 0);
      }
      return false;
    }
  };
}


void DiffNode::dump(int depth)
{
  const char *dir = NULL;
  switch (applyDirection)
  {
    case ApplyToModel: dir = "model->";
    case ApplyToDb: dir = "<-db";
    case DontApply: dir = "ignore";
    case CantApply: dir = "n/a";
  }

  log_debug("%*s: %s: %s | %s | %s\n", depth, "-", change ? change->get_type_name().c_str() : "nil",
            db_part.is_valid_object() ? db_part.get_name().c_str() : "nil",
            dir,
            model_part.is_valid_object() ? model_part.get_name().c_str() : "nil");
  for (DiffNodeVector::iterator It = children.begin();  It != children.end(); ++It)
    (*It)->dump(depth+1);
}


DiffNode *DiffNode::find_child_by_db_part_name(const std::string& name)
{

    if(this == NULL)
        throw std::logic_error("invalid object name");

    DiffNodeVector::const_iterator b= children.begin();
    DiffNodeVector::const_iterator e= children.end();
    DiffNodeVector::const_iterator it= std::find_if(b, e, CompareName(name, true));
    if(it == e)
    {
        it= std::find_if(b, e, CompareName(base::toupper(name), false));
        if(it == e)
            return NULL;
    }    
    return *it;
}

DiffNode *DiffNode::find_node_for_object(const grt::ObjectRef obj)
{
    if(get_db_part().is_valid_object())
    {
        if(get_db_part().get_object()->id() == obj->id())
            return this;    
    }else if(get_model_part().is_valid_object())
    {
        if(get_model_part().get_object()->id() == obj->id())
            return this;    
    }
    for(DiffNodeVector::const_iterator It = children.begin(); It != children.end(); ++It)
    {
        DiffNode* node = (*It)->find_node_for_object(obj);
        if(node)
            return node;
    }
    return NULL;
};

DiffNodeController::DiffNodeController()
{
  _directions_map[DiffNode::ApplyToModel] = DiffNode::ApplyToDb;
  _directions_map[DiffNode::ApplyToDb] = DiffNode::DontApply;
  _directions_map[DiffNode::DontApply] = DiffNode::ApplyToModel;
};

DiffNodeController::DiffNodeController(const std::map<DiffNode::ApplicationDirection,DiffNode::ApplicationDirection> directions_map):_directions_map(directions_map)
{

};

void DiffNodeController::set_next_apply_direction(DiffNode *node)const
{
  std::map<DiffNode::ApplicationDirection,DiffNode::ApplicationDirection>::const_iterator newdir = _directions_map.find(node->apply_direction());
  node->apply_direction(newdir == _directions_map.end()?DiffNode::DontApply:newdir->second);
};

void DiffNodeController::set_apply_direction(DiffNode *node, DiffNode::ApplicationDirection dir, bool recursive)const
{
  node->apply_direction(dir);

  if (recursive)
    for(DiffNode::DiffNodeVector::const_iterator It = node->get_children_begin(); It != node->get_children_end(); ++It)
      set_apply_direction(*It,dir,recursive);
};


namespace
{
  struct GetObjectListForScript
  {
    std::vector<grt::ValueRef>& vec;

    GetObjectListForScript(std::vector<grt::ValueRef>& v) : vec(v) {}
    void operator () (const DiffNode *node) { node->get_object_list_for_script(vec); }
  };

  struct NullModelPart
  {
    bool operator() (const DiffNode *node) { return node->get_model_part().get_object().is_valid() == 0; }
  };
}

void DiffNode::get_object_list_for_script(std::vector<grt::ValueRef>& vec) const
{

  bool im_already_there= false;
  
  if(applyDirection == ApplyToDb)
  {
    if(this->get_model_part().get_object().is_valid())
    {
      vec.push_back(this->get_model_part().get_object());
    }
    else
    {
      vec.push_back(this->get_db_part().get_object());
      return;
    }

    im_already_there= true;
  }

  DiffNodeVector::const_iterator b= children.begin();
  DiffNodeVector::const_iterator e= children.end();
  
  if(!im_already_there)
  {
    if(std::find_if(b, e, NullModelPart()) != e)
      vec.push_back(this->get_model_part().get_object());
  }

  std::for_each(b, e, GetObjectListForScript(vec));
}

namespace
{
  struct GetObjectListToApplyToModel
  {
    std::vector<grt::ValueRef>& vec;
    std::vector<grt::ValueRef>& removal_vec;
    GetObjectListToApplyToModel(std::vector<grt::ValueRef>& v, std::vector<grt::ValueRef>& rv) : vec(v),removal_vec(rv) {}
    void operator () (const DiffNode *node) { node->get_object_list_to_apply_to_model(vec, removal_vec); }
  };
}

void DiffNode::get_object_list_to_apply_to_model(std::vector<grt::ValueRef>& vec, std::vector<grt::ValueRef>& removal_vec) const
{

  if(applyDirection == ApplyToModel)
  {
    grt::ValueRef val = this->get_db_part().get_object();
    if (val.is_valid())
      vec.push_back(val);
    else //object must be removed from model;
      removal_vec.push_back(this->get_model_part().get_object());
  }

  DiffNodeVector::const_iterator b= children.begin();
  DiffNodeVector::const_iterator e= children.end();
  std::for_each(b, e, GetObjectListToApplyToModel(vec,removal_vec));
}

void DiffNode::set_modified_and_update_dir(bool m, boost::shared_ptr<DiffChange> c)
{
  change = c;
  modified= m;
  applyDirection= m ? ApplyToDb : CantApply;
}

std::string get_old_name_or_name(GrtNamedObjectRef obj)
{
  if(!obj.is_valid())
    return "";

  if(strlen(obj->oldName().c_str()) && !db_mysql_SchemaRef::can_wrap(obj))
    return std::string(obj->oldName().c_str());
  return std::string(obj->name().c_str());
}

template<>
std::string get_catalog_map_key<db_mysql_Catalog>(db_mysql_CatalogRef cat)
{
  if (!cat.is_valid())
    return "default";
  return std::string("`").append(cat->name()).append("`");
}

template<class _Object>
class ObjectAction
{
protected:
  CatalogMap& map;

public:
  ObjectAction(CatalogMap& m) : map(m) {}

  virtual void operator() (_Object object)
  {
    map[get_catalog_map_key(object)]= object;
  }
};

namespace
{
  struct TableAction : public ObjectAction<db_mysql_TableRef>
  {
  public:
    TableAction(CatalogMap& m) : ObjectAction<db_mysql_TableRef>(m) {}

    virtual void operator() (db_mysql_TableRef table)
    {
      ObjectAction<db_mysql_TableRef>::operator() (table);
      ObjectAction<db_mysql_ColumnRef>  oa_column(map);
      ct::for_each<ct::Columns>(table, oa_column);
      
      ObjectAction<db_mysql_IndexRef> oa_index(map);
      ct::for_each<ct::Indices>(table, oa_index);
      
      ObjectAction<db_mysql_TriggerRef> oa_trigger(map);
      ct::for_each<ct::Triggers>(table, oa_trigger);
      
      ObjectAction<db_mysql_ForeignKeyRef> oa_fk(map);
      ct::for_each<ct::ForeignKeys>(table, oa_fk);
    }
  };

  struct SchemaAction : public ObjectAction<db_mysql_SchemaRef>
  {
  public:
    SchemaAction(CatalogMap& m) : ObjectAction<db_mysql_SchemaRef>(m) {}

    virtual void operator() (db_mysql_SchemaRef schema)
    {
      ObjectAction<db_mysql_SchemaRef>::operator() (schema);
      
      TableAction ta(map);
      ct::for_each<ct::Tables>(schema, ta);
      
      ObjectAction<db_mysql_ViewRef> oa_view(map);
      ct::for_each<ct::Views>(schema, oa_view);
      
      ObjectAction<db_mysql_RoutineRef> oa_routine(map);
      ct::for_each<ct::Routines>(schema, oa_routine);
    }
  };
}

WBPLUGINDBMYSQLBE_PUBLIC_FUNC
void build_catalog_map(db_mysql_CatalogRef catalog, CatalogMap& map)
{
   SchemaAction sa(map);
  ct::for_each<ct::Schemata>(catalog, sa);
}

template<typename T>
T DiffTreeBE::find_object_in_catalog_map(T t, const CatalogMap& map)
{
  if (strlen(t->oldName().c_str()) == 0)
    return T();
  CatalogMap::const_iterator iter= map.find(get_catalog_map_key(t));
  if (iter != map.end())
    return T::cast_from(iter->second);
  return T();
}

void DiffTreeBE::fill_tree(DiffNode *table_node, db_mysql_TableRef table, const CatalogMap& map, bool inverse)
{
  // triggers
  for(size_t k= 0, triggers_count= table->triggers().count(); k < triggers_count; k++)
  {
    db_mysql_TriggerRef trigger= table->triggers().get(k);
    db_mysql_TriggerRef external_trigger= find_object_in_catalog_map(trigger, map);
    DiffNode *trigger_node= new DiffNode(trigger, external_trigger, inverse);
    table_node->append(trigger_node);
  }
}

void DiffTreeBE::fill_tree(DiffNode *schema_node, db_mysql_SchemaRef schema, const CatalogMap& map, bool inverse)
{
  // tables
  for(size_t j= 0, tables_count= schema->tables().count(); j < tables_count; j++)
  {
    db_mysql_TableRef table= schema->tables().get(j);
    db_mysql_TableRef external_table= find_object_in_catalog_map(table, map);
    DiffNode *table_node= new DiffNode(table, external_table, inverse);
    schema_node->append(table_node);
    fill_tree(table_node, table, map, inverse);
  }

  // views
  for(size_t j= 0, views_count= schema->views().count(); j < views_count; j++)
  {
    db_mysql_ViewRef view= schema->views().get(j);
    db_mysql_ViewRef external_view= find_object_in_catalog_map(view, map);
    DiffNode *view_node= new DiffNode(view, external_view, inverse);
    schema_node->append(view_node);
  }
  
  // routines
  for(size_t j= 0, routines_count= schema->routines().count(); j < routines_count; j++)
  {
    db_mysql_RoutineRef routine= schema->routines().get(j);
    db_mysql_RoutineRef external_routine= find_object_in_catalog_map(routine, map);
    DiffNode *routine_node= new DiffNode(routine, external_routine, inverse);
    schema_node->append(routine_node);
  }
}

void DiffTreeBE::fill_tree(DiffNode *root, db_mysql_CatalogRef model_catalog, const CatalogMap& map, bool inverse)
{
  for(size_t i= 0, schemata_count= model_catalog->schemata().count(); i < schemata_count; i++)
  {
    db_mysql_SchemaRef schema= model_catalog->schemata().get(i);
    db_mysql_SchemaRef external_schema= find_object_in_catalog_map(schema, map);
    if(!external_schema.is_valid())
    {
      std::string schema_name(schema->name().c_str());
      if(std::find(_schemata.begin(), _schemata.end(), schema_name) != _schemata.end())
        continue;
    }
    DiffNode *schema_node= new DiffNode(schema, external_schema, inverse);
    root->append(schema_node);
    fill_tree(schema_node, schema, map, inverse);
  }
}

bool is_node_object(const grt::ValueRef v)
{
    return  (db_SchemaRef::can_wrap(v) || db_TableRef::can_wrap(v) 
        || db_ViewRef::can_wrap(v) || db_RoutineRef::can_wrap(v)         
        || db_TriggerRef::can_wrap(v));
}

void DiffTreeBE::update_tree_with_changes(const boost::shared_ptr<DiffChange> diffchange)
{
    if(!diffchange)
        return;

    switch(diffchange->get_change_type())
    {
    case SimpleValue:
        obj_change_detected = true;
        break;

    case grt::ListItemAdded:
        {
            grt::ValueRef v= static_cast<const grt::ListItemAddedChange *>(diffchange.get())->get_value();
            if(is_node_object(v))
                apply_change(GrtObjectRef::cast_from(v), diffchange);
            else
                obj_change_detected = true;
        }
        break;

    case grt::ListItemRemoved:
        {
            grt::ValueRef v= static_cast<const grt::ListItemRemovedChange *>(diffchange.get())->get_value();
            if(is_node_object(v))
                apply_change(GrtObjectRef::cast_from(v), diffchange);
            else
                obj_change_detected = true;
        }
        break;

    case grt::ListModified:
    case grt::ObjectModified:
            std::for_each(diffchange->subchanges()->begin(),diffchange->subchanges()->end(),
                boost::bind(&DiffTreeBE::update_tree_with_changes, this, _1));
        break;

    case grt::ObjectAttrModified:
        update_tree_with_changes(static_cast<const grt::ObjectAttrModifiedChange *>
            (diffchange.get())->get_subchange());
        break;

    case grt::ListItemModified:
        {
            grt::ValueRef v(static_cast<const grt::ListItemModifiedChange *>
                (diffchange.get())->get_old_value());

            bool push = is_node_object(v); 

            obj_change_detected = false;
            update_tree_with_changes(static_cast<const grt::ListItemModifiedChange *>(diffchange.get())->get_subchange());
            if(push && obj_change_detected)
            {
                apply_change(GrtObjectRef::cast_from(v), diffchange);
                obj_change_detected = false;
            }
        }
        break;

    case grt::ListItemOrderChanged:
        {
            const grt::ListItemOrderChange *oc= 
                static_cast<const grt::ListItemOrderChange *>(diffchange.get());
            grt::ValueRef v(oc->get_old_value());
            if(db_ColumnRef::can_wrap(v) || db_IndexColumnRef::can_wrap(v))
                obj_change_detected = true;
            // the list item order change action can have a nested
            // object-modified action
            else if(oc->get_subchange())
                update_tree_with_changes(oc->get_subchange());
        }
        break;

    default:
        break;
    }
}

//TODO check how new DiffNode being deleted
void DiffTreeBE::apply_change(GrtObjectRef obj, boost::shared_ptr<DiffChange> change)
{
    DiffNode *obj_node = _root->find_node_for_object(obj);
    if(obj_node)
        obj_node->set_modified_and_update_dir(true, change);
    else
    {
        DiffNode *owner_node = _root->find_node_for_object(obj->owner() );
        if(!owner_node)
            owner_node = _root;

        DiffNode * newnode = new DiffNode(GrtNamedObjectRef(), GrtNamedObjectRef::cast_from(obj), false, change);
        owner_node->append(newnode);
    }
};

DiffTreeBE::DiffTreeBE(const std::vector<std::string>& schemata,
                       db_mysql_CatalogRef model_catalogRef, 
                       db_mysql_CatalogRef external_catalog, 
                       boost::shared_ptr<DiffChange> diffchange,
                       DiffNodeController controller):
_node_controller(controller)
{
  _root= new DiffNode(model_catalogRef, external_catalog, false);
  _schemata.assign(schemata.begin(), schemata.end());
  
  CatalogMap map;
  build_catalog_map(external_catalog, map);
  fill_tree(_root, model_catalogRef, map, false);
  update_tree_with_changes(diffchange);

  change_nothing_icon= bec::IconManager::get_instance()->get_icon_id("change_nothing.png");
  change_backward_icon= bec::IconManager::get_instance()->get_icon_id("change_backward.png");
  change_forward_icon= bec::IconManager::get_instance()->get_icon_id("change_forward.png");
  change_ignore_icon= bec::IconManager::get_instance()->get_icon_id("change_ignore.png");
  alert_icon= bec::IconManager::get_instance()->get_icon_id("change_alert_thin.png");
  drop_alert_icon= bec::IconManager::get_instance()->get_icon_id("change_alert_drop.png");
}

DiffNode *DiffTreeBE::get_node_with_id(const NodeId &nodeid)
{
  DiffNode *n= _root;
  int i;

  if (!n)
    return NULL;

  if (nodeid.depth() == 0)
    return n;

  for (i= 0; i < nodeid.depth(); i++)
  {
    if (nodeid[i] >= (int)n->get_children_size())
      throw std::logic_error("Invalid node id");

    n= n->get_child(nodeid[i]);
  }
  return n;
}

int DiffTreeBE::count_children(const bec::NodeId &nodeid)
{
  DiffNode *node= get_node_with_id(nodeid);
  if(node)
    return (int)node->get_children_size();
  return 0;
}

bec::NodeId DiffTreeBE::get_child(const bec::NodeId &parentid, int child_idx)
{
  DiffNode *node= get_node_with_id(parentid);
  
  if (node && child_idx < (int)node->get_children_size())
    return NodeId(parentid).append(child_idx);
  
  if (node)
    throw std::logic_error("invalid index");

  return NodeId();
}

bool DiffTreeBE::get_field(const bec::NodeId &node_id, int column, std::string &value)
{
  if ((column != ModelObjectName)
   && (column != DbObjectName))
  {
    return false;
  }

  DiffNode *node= dynamic_cast<DiffNode *>(get_node_with_id(node_id));
  if(!node)
    return false;

  switch(column)
  {
  case ModelObjectName:
    if(node->get_model_part().is_valid_object())
      value= node->get_model_part().get_name();
    else
      value= "N/A";
    break;
  case DbObjectName:
    if(node->get_db_part().is_valid_object())
      value= node->get_db_part().get_name();
    else
      value= "N/A";
    break;
  default:
    value= "";
    return false;
  }

  return true;
}

bec::IconId DiffTreeBE::get_field_icon(const bec::NodeId &node_id, int column, IconSize size)
{
  if ((column != ModelObjectName)
   && (column != ModelChanged) 
   && (column != ApplyDirection)
   && (column != DbChanged)
   && (column != DbObjectName))
  {
    return -1;
  }

  DiffNode *node= dynamic_cast<DiffNode *>(get_node_with_id(node_id));
  if(!node)
    return -1;

  IconId object_icon= 1;
  if(node->get_db_part().is_valid_object())
    object_icon= bec::IconManager::get_instance()->get_icon_id(node->get_db_part().get_object());
  else if(node->get_model_part().is_valid_object())
    object_icon= bec::IconManager::get_instance()->get_icon_id(node->get_model_part().get_object());

  switch(column)
  {
  case ModelChanged:
    return (node->is_modified()) ? ((!node->get_db_part().is_valid_object() || !node->get_model_part().is_valid_object())? drop_alert_icon : alert_icon) : 0;
  case ModelObjectName:
    return object_icon;
  case ApplyDirection:
    switch(node->get_application_direction())
    {
    case DiffNode::ApplyToModel:
      return change_backward_icon;
    case DiffNode::ApplyToDb:
      return change_forward_icon;
    case DiffNode::DontApply:
      return change_ignore_icon;
    case DiffNode::CantApply:
      return change_nothing_icon;
    }
    return -1;
  case DbObjectName:
    return (node->is_modified()) ? ((!node->get_db_part().is_valid_object() || !node->get_model_part().is_valid_object())? drop_alert_icon : alert_icon) : 0;
  default:
    return -1;
  }
}

void DiffTreeBE::set_next_apply_direction(const bec::NodeId &node_id)
{
  DiffNode *node= dynamic_cast<DiffNode *>(get_node_with_id(node_id));
  if(node)
    _node_controller.set_next_apply_direction(node);
}

void DiffTreeBE::set_apply_direction(const bec::NodeId &node_id, DiffNode::ApplicationDirection dir, bool recursive)
{
  DiffNode *node= dynamic_cast<DiffNode *>(get_node_with_id(node_id));
  if(node)
    _node_controller.set_apply_direction(node, dir, recursive);
}


void DiffTreeBE::get_object_list_for_script(std::vector<grt::ValueRef>& vec) const
{
  _root->get_object_list_for_script(vec);
}

void DiffTreeBE::get_object_list_to_apply_to_model(std::vector<grt::ValueRef>& vec, std::vector<grt::ValueRef>& removal_vec) const
{
  _root->get_object_list_to_apply_to_model(vec,removal_vec);
}

//void DiffTreeBE::sync_old_name() const
//{
//  _root->sync_old_name();
//}
