#include "stdafx.h"

#include <algorithm>
#include "grtpp_undo_manager.h"

#include "grtdb/db_object_helpers.h"

#include "grts/structs.h"
#include "grts/structs.db.mgmt.h"
#include "grts/structs.db.mysql.h"
#include "grts/structs.workbench.h"
#include "grts/structs.workbench.physical.h"

#include "grtpp.h"
#include "db_mysql_sql_export.h"

using namespace grt;

#include "diff/diffchange.h"
#include "grtdb/diff_dbobjectmatch.h"

#include "grtsqlparser/sql_facade.h"
#include "db.mysql/src/module_db_mysql.h"
#include "grti/sqlgenerator.h"

#include "db_mysql_sql_script_sync.h"
//#include "cpp/catalog_templates.h"

#include "db.mysql/src/module_db_mysql_shared_code.h"

static void apply_user_datatypes(db_mysql_CatalogRef cat, db_mgmt_RdbmsRef rdbms);

template<typename T>
void replace_list_objects(grt::ListRef<T> list, CatalogMap& obj_map)
{
  CatalogMap::const_iterator end= obj_map.end();
  for(size_t i= 0, count= list.count(); i < count; i++)
  {
    Ref<T> t= list.get(i);
    if(!t.is_valid())
    {
      list.remove(i);
      count--;
      i--;
      continue;
      }
    CatalogMap::const_iterator it= obj_map.find(get_catalog_map_key(t));
    if(it == end)
      continue;
    list.remove(i);
    list.insert(Ref<T>::cast_from(it->second), (long)i);
  }
}

template<>
void replace_list_objects(grt::ListRef<db_mysql_IndexColumn> list, CatalogMap& obj_map)
{
  CatalogMap::const_iterator end= obj_map.end();
  for(size_t i= 0, count= list.count(); i < count; i++)
  {
    db_mysql_IndexColumnRef index_column= list.get(i);
    db_ColumnRef column= index_column->referencedColumn();
    CatalogMap::const_iterator it= 
      obj_map.find(get_catalog_map_key<db_Column>(column));
    if(it == end)
      continue;

    index_column->referencedColumn(db_ColumnRef::cast_from(it->second));
  }
}

template <typename T>
static bool remove_model_only_objects(grt::ListRef<T> obj_list)
{
  for (int n= obj_list.count()-1;  n >= 0; --n)
    if (obj_list.get(n)->modelOnly())
      obj_list.remove(n);
  return true;
}

static bool remove_model_only_objects(db_TableRef table)
{
  //remove_model_only_objects(table->columns()); // modelOnly is not a member of db_Column
  remove_model_only_objects(table->triggers());
  //remove_model_only_objects(table->indices()); // modelOnly is not a member of db_Index
  remove_model_only_objects(table->foreignKeys());
  return true;
}

static bool remove_model_only_objects(db_SchemaRef schema)
{
  remove_model_only_objects(schema->tables());
  schema->tables().foreach(sigc::ptr_fun((bool(*)(db_TableRef))&remove_model_only_objects));
  remove_model_only_objects(schema->views());
  remove_model_only_objects(schema->routines());
  return true;
}

static bool remove_model_only_objects(db_CatalogRef catalog)
{
  remove_model_only_objects(catalog->schemata());
  catalog->schemata().foreach(sigc::ptr_fun((bool(*)(db_SchemaRef))&remove_model_only_objects));
  return true;
}

static bool remove_model_only_objects(db_mysql_CatalogRef catalog)
{
  return remove_model_only_objects(db_CatalogRef(catalog));
}

static inline void update_old_name(GrtNamedObjectRef obj, bool update_only_empty)
{
  if(!update_only_empty || (strlen(obj->oldName().c_str()) == 0))
    obj->oldName(obj->name());
}

template<class _Parent, class _Object>
class ObjectAction
{
protected:
  _Parent owner;
  bool update_only_empty;

public:
  ObjectAction(_Parent ow, bool update_empty) : owner(ow), update_only_empty(update_empty) {}

  virtual void operator() (_Object object)
  {
    object->owner(owner);
    update_old_name(object, update_only_empty);
  }
};

namespace
{
  struct FKAction : public ObjectAction<db_mysql_TableRef, db_mysql_ForeignKeyRef>
  {
    CatalogMap& map;
    FKAction(db_mysql_TableRef ow, bool update_only_empty, CatalogMap& m) 
      : ObjectAction<db_mysql_TableRef, db_mysql_ForeignKeyRef>(ow, update_only_empty), map(m) {}

    void operator() (db_mysql_ForeignKeyRef fk) 
    {
      ObjectAction<db_mysql_TableRef, db_mysql_ForeignKeyRef>::operator ()(fk);
      replace_list_objects(fk->columns(), map);
      replace_list_objects(fk->referencedColumns(), map);
    }
  };

  struct IndexAction : public ObjectAction<db_mysql_TableRef, db_mysql_IndexRef>
  {
    CatalogMap& map;
    IndexAction(db_mysql_TableRef ow, bool update_only_empty, CatalogMap& m) 
      : ObjectAction<db_mysql_TableRef, db_mysql_IndexRef>(ow, update_only_empty), map(m) {}

    void operator() (db_mysql_IndexRef index) 
    {
      ObjectAction<db_mysql_TableRef, db_mysql_IndexRef>::operator ()(index);
      replace_list_objects(index->columns(), map);
    }
  };

  struct TableAction : public ObjectAction<db_mysql_SchemaRef, db_mysql_TableRef>
  {
    CatalogMap& map;
    TableAction(db_mysql_SchemaRef ow, bool update_only_empty, CatalogMap& m) 
      : ObjectAction<db_mysql_SchemaRef, db_mysql_TableRef>(ow, update_only_empty), map(m) {}
    
    void operator() (db_mysql_TableRef table) 
    {
      ObjectAction<db_mysql_SchemaRef, db_mysql_TableRef>::operator ()(table);
      
      ObjectAction<db_mysql_TableRef, db_mysql_ColumnRef> oa_column(table, update_only_empty);
      ct::for_each<ct::Columns>(table, oa_column);
      
      ObjectAction<db_mysql_TableRef, db_mysql_TriggerRef> oa_trigger(table, update_only_empty);
      ct::for_each<ct::Triggers>(table, oa_trigger);
      
      IndexAction ia(table, update_only_empty, map);
      //std::for_each(table->indices().begin(), table->indices().end(), ia);
      ct::for_each<ct::Indices>(table, ia);
      
      FKAction fk_action(table, update_only_empty, map);
      ct::for_each<ct::ForeignKeys>(table, fk_action);
    }
  };

  struct SchemaAction : public ObjectAction<db_mysql_CatalogRef, db_mysql_SchemaRef>
  {
    CatalogMap& map;
    SchemaAction(db_mysql_CatalogRef ow, bool update_only_empty, CatalogMap& m) 
      : ObjectAction<db_mysql_CatalogRef, db_mysql_SchemaRef>(ow, update_only_empty), map(m) {}

    void operator() (db_mysql_SchemaRef schema) 
    {
      ObjectAction<db_mysql_CatalogRef, db_mysql_SchemaRef>::operator ()(schema);

      TableAction table_action(schema, update_only_empty, map);
      ct::for_each<ct::Tables>(schema, table_action);
      
      ObjectAction<db_mysql_SchemaRef, db_mysql_ViewRef> oa_view(schema, update_only_empty);
      ct::for_each<ct::Views>(schema, oa_view);
      
      ObjectAction<db_mysql_SchemaRef, db_mysql_RoutineRef>  oa_routine(schema, update_only_empty);
      ct::for_each<ct::Routines>(schema, oa_routine);
    }
  };
}

WBPLUGINDBMYSQLBE_PUBLIC_FUNC 
void update_all_old_names(db_mysql_CatalogRef cat, bool update_only_empty, CatalogMap& map)
{
  update_old_name(cat, update_only_empty);
  
  SchemaAction sa(cat, update_only_empty, map);
  ct::for_each<ct::Schemata>(cat, sa);
}

DbMySQLScriptSync::DbMySQLScriptSync(bec::GRTManager *grtm)
  : DbMySQLValidationPage(grtm), _diff_tree(NULL)
{
  _manager= grtm;
}

db_mysql_CatalogRef DbMySQLScriptSync::get_model_catalog()
{
  return db_mysql_CatalogRef::cast_from(_manager->get_grt()->get("/wb/doc/physicalModels/0/catalog"));
}

void DbMySQLScriptSync::set_option(const std::string& name, const std::string& value)
{
  if(name.compare("InputFileName1") == 0)
    _input_filename1= value;
  else if(name.compare("InputFileName2") == 0)
    _input_filename2= value;
  else if(name.compare("OutputFileName") == 0)
    _output_filename= value;
}

void DbMySQLScriptSync::start_sync()
{
  bec::GRTTask *task= new bec::GRTTask("SQL sync", 
    _manager->get_dispatcher(), 
    sigc::bind<grt::StringRef>(sigc::mem_fun(this, &DbMySQLScriptSync::sync_task), grt::StringRef()));

  task->signal_finished().connect(sigc::mem_fun(this, &DbMySQLScriptSync::sync_finished));
  _manager->get_dispatcher()->add_task(task);
}

void DbMySQLScriptSync::sync_finished(grt::ValueRef res)
{
  _manager->get_grt()->send_output(grt::StringRef::cast_from(res).c_str());
}

static void dump_alter_map(grt::DictRef alter_map)
{
  for(DictRef::const_iterator iterator= alter_map.begin(); iterator != alter_map.end(); iterator++)
  {
    std::string key= iterator->first;
    grt::ValueRef value= iterator->second;

    if (grt::StringListRef::can_wrap(value))
    {
      grt::StringListRef list= grt::StringListRef::cast_from(value);
      for (size_t listcount= list.count(), j= 0; j < listcount; j++)
      {
        std::cout << list.get(j).c_str() << std::endl;
      }
    }
    else if (grt::StringRef::can_wrap(value))
    {
      std::cout << grt::StringRef::cast_from(value).c_str() << std::endl;
    }
  }
}

// this function gets catalog from file or (if filename is empty) from the GRT tree
db_mysql_CatalogRef DbMySQLScriptSync::get_cat_from_file_or_tree(std::string filename, 
                                                              std::string& error_msg)
{
  db_mysql_CatalogRef ref_cat= get_model_catalog();

  if (filename.empty())
  {
    ref_cat->name("default");
    ref_cat->oldName("default");
    return ref_cat;
  }

  DbMySQLImpl *diffsql_module= _manager->get_grt()->find_native_module<DbMySQLImpl>("DbMySQL");

  if (diffsql_module == NULL)
  {
    error_msg.assign("Internal error. Not able to load 'MySQLModuleDbMySQL' module");
    return db_mysql_CatalogRef();
  }

  if (!ref_cat.is_valid())
  {
    error_msg.assign("Internal error. Catalog is invalid");
    return db_mysql_CatalogRef();
  }

  workbench_physical_ModelRef pm= workbench_physical_ModelRef::cast_from(ref_cat->owner());

  db_mysql_CatalogRef cat(_manager->get_grt());
  cat->version(pm->rdbms()->version());
  grt::replace_contents(cat->simpleDatatypes(), pm->rdbms()->simpleDatatypes());
  cat->name("default");
  cat->oldName("default");

  GError *file_error= NULL;
  char *sql_input_script= NULL;
  size_t sql_input_script_length= 0;
  
  if (!g_file_get_contents(filename.c_str(), &sql_input_script, &sql_input_script_length, &file_error))
  {
    std::string file_error_msg("Error reading input file: ");
    file_error_msg.append(file_error->message);
    error_msg.assign(file_error_msg.c_str());
    return db_mysql_CatalogRef();
  }

  SqlFacade *sql_parser= SqlFacade::instance_for_rdbms(pm->rdbms());
  sql_parser->parseSqlScriptString(cat, sql_input_script);
  g_free(sql_input_script);

  return cat;
}

namespace
{
  struct Column_action
  {
    db_mysql_CatalogRef catalog;
    db_mgmt_RdbmsRef rdbms;

    Column_action(db_mysql_CatalogRef c, db_mgmt_RdbmsRef r) 
      : catalog(c), rdbms(r)
    {}

    void operator() (db_mysql_ColumnRef column) 
    {
      db_UserDatatypeRef udt= column->userType();
      if(udt.is_valid())
      {
        column->setParseType(std::string(udt->sqlDefinition()), catalog->simpleDatatypes());
        /*
        bec::ColumnHelper::parse_column_type(
          rdbms, catalog->userDatatypes(),
          std::string(udt->sqlDefinition()),
          column);
         */
      }
    }
  };

  struct Table_action
  {
    db_mysql_CatalogRef catalog;
    db_mgmt_RdbmsRef rdbms;

    Table_action(db_mysql_CatalogRef c, db_mgmt_RdbmsRef r) 
      : catalog(c), rdbms(r)
    {}

    void operator() (const db_mysql_TableRef &table) 
    {
      Column_action ca(catalog, rdbms);
      ct::for_each<ct::Columns>(table, ca);
    }
  };

  struct Schema_action
  {
    db_mysql_CatalogRef catalog;
    db_mgmt_RdbmsRef rdbms;

    Schema_action(db_mysql_CatalogRef c, db_mgmt_RdbmsRef r) 
      : catalog(c), rdbms(r)
    {}

    void operator() (const db_mysql_SchemaRef &schema) 
    {
      Table_action table_action(catalog, rdbms);
      ct::for_each<ct::Tables>(schema, table_action);
    }
  };
}

static void apply_user_datatypes(db_mysql_CatalogRef cat, db_mgmt_RdbmsRef rdbms)
{
  Schema_action sa(cat, rdbms);
  ct::for_each<ct::Schemata>(cat, sa);
}

ValueRef DbMySQLScriptSync::sync_task(grt::GRT* grt, grt::StringRef)
{
  DbMySQLImpl *diffsql_module= _manager->get_grt()->find_native_module<DbMySQLImpl>("DbMySQL");

  std::string err;

  db_mysql_CatalogRef mod_cat= get_cat_from_file_or_tree(std::string(), err);

  if (!err.empty())
    return StringRef(err);

  db_mysql_CatalogRef org_cat= get_cat_from_file_or_tree(_input_filename1, err);

  if (!err.empty())
    return StringRef(err);

  db_mgmt_RdbmsRef rdbms= db_mgmt_RdbmsRef::cast_from(grt->get("/wb/rdbmsMgmt/rdbms/0"));

  db_mysql_CatalogRef org_cat_copy= db_mysql_CatalogRef::cast_from(grt::copy_object(_manager->get_grt(), org_cat));
  db_mysql_CatalogRef mod_cat_copy= db_mysql_CatalogRef::cast_from(grt::copy_object(_manager->get_grt(), mod_cat));

  remove_model_only_objects(mod_cat_copy);

  apply_user_datatypes(org_cat_copy, rdbms);
  apply_user_datatypes(mod_cat_copy, rdbms);

  grt::DbObjectMatchAlterOmf omf;
  DiffChange* alter_change= diff_make(org_cat_copy, mod_cat_copy, &omf,NormalizedComparer<grt::GRT*>(_manager->get_grt()));

  // nothing changed
  if(!alter_change)
    return grt::StringRef("");  

  grt::DictRef options(_manager->get_grt());
  grt::StringListRef alter_list(_manager->get_grt());
  options.set("OutputContainer", alter_list);
  options.set("UseFilteredLists", grt::IntegerRef(0));
  options.set("KeepOrder", grt::IntegerRef(1));
  grt::ListRef<GrtNamedObject> alter_object_list(_manager->get_grt());
  options.set("OutputObjectContainer", alter_object_list);

  char buf1[128];
  sprintf(buf1, "%p", alter_change);

  diffsql_module->generateSQL(org_cat, options, std::string(buf1));

  int res= diffsql_module->makeSQLSyncScript(options, alter_list, alter_object_list);
  if (res)
    return StringRef("SQL Script Export Module Returned Error");

  grt::StringRef script= grt::StringRef::cast_from(options.get("OutputScript"));

  g_file_set_contents(_output_filename.c_str(), script.c_str(), (gssize)strlen(script.c_str()), NULL);

  return grt::StringRef("");
}

void DbMySQLScriptSync::update_model_old_names()
{
  CatalogMap cm;
  update_all_old_names(get_model_catalog(), false, cm);
}

DiffTreeBE *DbMySQLScriptSync::init_diff_tree(const ValueRef &ext_cat, const ValueRef &cat2)
{
  std::vector<std::string> schemata;

  db_mysql_CatalogRef catalog= db_mysql_CatalogRef::cast_from(ext_cat);
  grt::ListRef<db_mysql_Schema> sch(catalog->schemata());
  for (size_t i= 0; i < sch.count(); i++)
    schemata.push_back(sch.get(i)->name().c_str());

  return init_diff_tree(schemata, ext_cat, cat2);
}


DiffTreeBE *DbMySQLScriptSync::init_diff_tree(const std::vector<std::string>& schemata, const ValueRef &ext_cat, 
                                              const ValueRef &cat2)
{
  std::string err;

  schemata_list.assign(schemata.begin(), schemata.end());

  // 1. load db_Catalog-s
  db_mysql_CatalogRef mod_cat;

  if (!cat2.is_valid())
  {
    mod_cat= get_cat_from_file_or_tree(_input_filename2, err);
    if (!err.empty())
      throw DbMySQLScriptSyncException(err);
  }
  else
  {
    mod_cat= db_mysql_CatalogRef::cast_from(cat2);
  }

  _mod_cat_copy= db_mysql_CatalogRef::cast_from(grt::copy_object(_manager->get_grt(), mod_cat));
  bec::CatalogHelper::apply_defaults(_mod_cat_copy);

  CatalogMap catalog_map;
  build_catalog_map(_mod_cat_copy, catalog_map);

  update_all_old_names(_mod_cat_copy, true, catalog_map);

  if (!ext_cat.is_valid())
  {
    _org_cat= get_cat_from_file_or_tree(_input_filename1, err);
    if (!err.empty())
      throw DbMySQLScriptSyncException(err);
  }
  else
  {
    _org_cat= db_mysql_CatalogRef::cast_from(ext_cat);
  }

  bec::CatalogHelper::apply_defaults(_org_cat);

  CatalogMap catalog_map2;
  build_catalog_map(_org_cat, catalog_map2);
  update_all_old_names(_org_cat, true, catalog_map2);

  // 2. diff with model
  db_mgmt_RdbmsRef rdbms= db_mgmt_RdbmsRef::cast_from(_manager->get_grt()->get("/wb/rdbmsMgmt/rdbms/0"));

  remove_model_only_objects(_mod_cat_copy);

  apply_user_datatypes(_org_cat, rdbms);
  apply_user_datatypes(_mod_cat_copy, rdbms);

  grt::DbObjectMatchAlterOmf omf;
  _alter_change= diff_make(_org_cat, _mod_cat_copy, &omf,NormalizedComparer<grt::GRT*>(_manager->get_grt()));

  // 3. build the tree
  return _diff_tree= new ::DiffTreeBE(
    schemata, _mod_cat_copy, _org_cat, _alter_change);
}

std::string DbMySQLScriptSync::generate_diff_tree_script()
{
  DbMySQLImpl *diffsql_module= _manager->get_grt()->find_native_module<DbMySQLImpl>("DbMySQL");
  if (diffsql_module == NULL)
    return NULL;

  std::vector<grt::ValueRef> vec;
  _diff_tree->get_object_list_for_script(vec);

  std::vector<std::string> schemata;
  std::vector<std::string> tables;
  std::vector<std::string> triggers;
  std::vector<std::string> views;
  std::vector<std::string> routines;

  for(std::vector<grt::ValueRef>::const_iterator e= vec.end(), it= vec.begin(); it != e; it++)
  {
    grt::ValueRef v= *it;
    if(!GrtNamedObjectRef::can_wrap(v))
      continue;

    std::string name(get_full_object_name_for_key(GrtNamedObjectRef::cast_from(v)));

    if(db_mysql_SchemaRef::can_wrap(v))
    {
      db_mysql_SchemaRef schema= db_mysql_SchemaRef::cast_from(v);
      schemata.push_back(name);
    }
    else if(db_mysql_TableRef::can_wrap(v))
    {
      db_mysql_TableRef table= db_mysql_TableRef::cast_from(v);
      tables.push_back(name);
    }
    else if(db_mysql_ViewRef::can_wrap(v))
    {
      db_mysql_ViewRef view= db_mysql_ViewRef::cast_from(v);
      views.push_back(name);
    }
    else if(db_mysql_RoutineRef::can_wrap(v))
    {
      db_mysql_RoutineRef routine= db_mysql_RoutineRef::cast_from(v);
      routines.push_back(name);
    }
    else if(db_mysql_TriggerRef::can_wrap(v))
    {
      db_mysql_TriggerRef trigger= db_mysql_TriggerRef::cast_from(v);
      triggers.push_back(name);
    }
  }

  grt::DictRef options(_manager->get_grt());
  options.set("SchemaFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), schemata));
  options.set("TableFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), tables));
  options.set("ViewFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), views));
  options.set("RoutineFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), routines));
  options.set("TriggerFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), triggers));

  options.set("KeepOrder", grt::IntegerRef(1));

  grt::StringListRef alter_list(_manager->get_grt());
  grt::ListRef<GrtNamedObject> alter_object_list(_manager->get_grt());
  options.set("OutputContainer", alter_list);
  options.set("OutputObjectContainer", alter_object_list);

  if(_alter_change)
  {
    //_alter_change->dump_log(0);
    char buf1[128];
    sprintf(buf1, "%p", _alter_change);
    diffsql_module->generateSQL(_org_cat, options, std::string(buf1));
  }

  int res= diffsql_module->makeSQLSyncScript(options, alter_list, alter_object_list);
  if (res)
    return "";

  grt::StringRef script= grt::StringRef::cast_from(options.get("OutputScript"));

  return std::string(script.c_str());
}

std::string DbMySQLScriptSync::generate_diff_tree_report()
{
  DbMySQLImpl *diffsql_module= _manager->get_grt()->find_native_module<DbMySQLImpl>("DbMySQL");

  if (diffsql_module == NULL)
    return NULL;

  std::vector<grt::ValueRef> vec;
  _diff_tree->get_object_list_for_script(vec);

  std::vector<std::string> schemata;
  std::vector<std::string> tables;
  std::vector<std::string> triggers;
  std::vector<std::string> views;
  std::vector<std::string> routines;

  for(std::vector<grt::ValueRef>::const_iterator e= vec.end(), it= vec.begin(); it != e; it++)
  {
    grt::ValueRef v= *it;
    if(!GrtNamedObjectRef::can_wrap(v))
      continue;

    std::string name(get_object_name_for_key(GrtNamedObjectRef::cast_from(v)));

    if(db_mysql_SchemaRef::can_wrap(v))
    {
      db_mysql_SchemaRef schema= db_mysql_SchemaRef::cast_from(v);
      //name.append(get_old_name_or_name(schema));
      schemata.push_back(name);
    }
    else if(db_mysql_TableRef::can_wrap(v))
    {
      db_mysql_TableRef table= db_mysql_TableRef::cast_from(v);
      //name.append(get_old_name_or_name(GrtNamedObjectRef::cast_from(table->owner()))).append(".").append(get_old_name_or_name(table));
      tables.push_back(name);
    }
    else if(db_mysql_ViewRef::can_wrap(v))
    {
      db_mysql_ViewRef view= db_mysql_ViewRef::cast_from(v);
      //name.append(get_old_name_or_name(GrtNamedObjectRef::cast_from(view->owner()))).append(".").append(get_old_name_or_name(view));
      views.push_back(name);
    }
    else if(db_mysql_RoutineRef::can_wrap(v))
    {
      db_mysql_RoutineRef routine= db_mysql_RoutineRef::cast_from(v);
      //name.append(get_old_name_or_name(GrtNamedObjectRef::cast_from(routine->owner()))).append(".").append(get_old_name_or_name(routine));
      routines.push_back(name);
    }
    else if(db_mysql_TriggerRef::can_wrap(v))
    {
      db_mysql_TriggerRef trigger= db_mysql_TriggerRef::cast_from(v);
      //name.append(get_old_name_or_name(GrtNamedObjectRef::cast_from(trigger->owner()->owner()))).append(".").append(get_old_name_or_name(trigger));
      triggers.push_back(name);
    }
  }

  grt::DictRef options(_manager->get_grt());
  options.set("SchemaFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), schemata));
  options.set("TableFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), tables));
  options.set("ViewFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), views));
  options.set("RoutineFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), routines));
  options.set("TriggerFilterList", convert_string_vector_to_grt_list(_manager->get_grt(), triggers));
  options.set("TemplateFile", 
    grt::StringRef(_manager->get_data_file_path("modules/data/db_mysql_catalog_reporting/Basic_Text.tpl/basic_text_report.txt.tpl").c_str()));
  char buf1[128];
  sprintf(buf1, "%p", _alter_change);
  grt::StringRef output_string(diffsql_module->generateReport(_org_cat, options, std::string(buf1)));

  CatalogMap ca;
  update_all_old_names(get_model_catalog(), false, ca);

  return std::string(output_string.c_str());
}

size_t DbMySQLScriptSync::find_schema_by_old_name(db_mysql_CatalogRef cat, const char *schema_old_name)
{
  for(size_t sz= cat->schemata().count(), i= 0; i < sz; i++)
  {
    if(strcmp(cat->schemata().get(i)->name().c_str(), schema_old_name) == 0)
      return i;
  }
  return BaseListRef::npos;
}

size_t DbMySQLScriptSync::find_table_by_old_name(db_mysql_SchemaRef schema, const char *table_old_name) 
{
  for(size_t sz= schema->tables().count(), i= 0; i < sz; i++)
  {
    if(strcmp(schema->tables().get(i)->name().c_str(), table_old_name) == 0)
      return i;
  }
  return BaseListRef::npos;
}

size_t DbMySQLScriptSync::find_view_by_old_name(db_mysql_SchemaRef schema, const char *view_old_name) 
{
  for(size_t sz= schema->views().count(), i= 0; i < sz; i++)
  {
    if(strcmp(schema->views().get(i)->name().c_str(), view_old_name) == 0)
      return i;
  }
  return BaseListRef::npos;
}

size_t DbMySQLScriptSync::find_routine_by_old_name(db_mysql_SchemaRef schema, const char *routine_old_name) 
{
  for(size_t sz= schema->routines().count(), i= 0; i < sz; i++)
  {
    if(strcmp(schema->routines().get(i)->name().c_str(), routine_old_name) == 0)
      return i;
  }
  return BaseListRef::npos;;
}

size_t DbMySQLScriptSync::find_trigger_by_old_name(db_mysql_TableRef table, const char *trigger_old_name) 
{
  for(size_t sz= table->triggers().count(), i= 0; i < sz; i++)
  {
    if(strcmp(table->triggers().get(i)->name().c_str(), trigger_old_name) == 0)
      return i;
  }
  return BaseListRef::npos;
}

db_ColumnRef DbMySQLScriptSync::find_column_by_old_name(db_mysql_TableRef table, const char *column_old_name) 
{
  for(size_t sz= table->columns().count(), i= 0; i < sz; i++)
  {
    db_ColumnRef col= table->columns().get(i);
    if(strcmp(col->name().c_str(), column_old_name) == 0)
      return col;
  }
  return db_ColumnRef();
}

void DbMySQLScriptSync::copy_table_children(db_mysql_TableRef from, db_mysql_TableRef to)
{
  for(size_t sz= from->triggers().count(), i= 0; i < sz; i++)
  {
    db_mysql_TriggerRef trigger= from->triggers().get(i);
    to->triggers().insert(trigger);
    trigger->owner(to);
  }
}

void DbMySQLScriptSync::copy_schema_children(db_mysql_SchemaRef from, db_mysql_SchemaRef to)
{
  for(size_t sz= from->views().count(), i= 0; i < sz; i++)
  {
    db_mysql_ViewRef view= from->views().get(i);
    to->views().insert(view);
    view->owner(to);
  }
  for(size_t sz= from->routines().count(), i= 0; i < sz; i++)
  {
    db_mysql_RoutineRef routine= from->routines().get(i);
    to->routines().insert(routine);
    routine->owner(to);
  }
  for(size_t sz= from->tables().count(), i= 0; i < sz; i++)
  {
    db_mysql_TableRef table= from->tables().get(i);
    to->tables().insert(table);
    table->owner(to);
  }
}

void DbMySQLScriptSync::apply_changes_to_model()
{
  grt::AutoUndo undo(_manager->get_grt());
  
  std::vector<grt::ValueRef> vec;
  _diff_tree->get_object_list_to_apply_to_model(vec);
  
  db_mysql_CatalogRef mod_cat= get_model_catalog();

  // the algorithm:
  // 1. update leaf objects (views, routines, triggers) that have parents in the model (schema or table)
  // 2. update tables that have parent schema, fill table replacement map
  // 3. update schema and insert new ones
  // 4. insert tables which were omited on step 2.
  // 5. insert objects that were omited on step 1.
  // 6. use table replacement map to update table references from FKs and model diagrams

  std::vector<db_mysql_ViewRef> views_to_insert;
  std::vector<db_mysql_RoutineRef> routines_to_insert;
  std::vector<db_mysql_TriggerRef> triggers_to_insert;
  std::vector<db_mysql_TableRef> tables_to_insert;

  typedef std::map<grt::ValueRef, grt::ValueRef> Table_replacement_map;
  Table_replacement_map tr_map;
  Table_replacement_map rev_tr_map;

  // 1.
  for(std::vector<grt::ValueRef>::const_iterator e= vec.end(), it= vec.begin(); it != e; it++)
  {
    grt::ValueRef v= *it;
    if(db_mysql_ViewRef::can_wrap(v))
    {
      db_mysql_ViewRef view= db_mysql_ViewRef::cast_from(v);
      size_t parent_index= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(view->owner())->oldName().c_str());
      if(parent_index == BaseListRef::npos)
      {
        views_to_insert.push_back(view);
        continue;
      }
      db_mysql_SchemaRef parent= mod_cat->schemata().get(parent_index);
      size_t index= find_view_by_old_name(parent, view->oldName().c_str());
      if(index != BaseListRef::npos)
        parent->views().remove(index);
      parent->views().insert(view, index);
      view->owner(parent);
    }
    else if(db_mysql_RoutineRef::can_wrap(v))
    {
      db_mysql_RoutineRef routine= db_mysql_RoutineRef::cast_from(v);
      size_t parent_index= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(routine->owner())->oldName().c_str());
      if(parent_index == BaseListRef::npos)
      {
        routines_to_insert.push_back(routine);
        continue;
      }
      db_mysql_SchemaRef parent= mod_cat->schemata().get(parent_index);
      size_t index= find_routine_by_old_name(parent, routine->oldName().c_str());
      if(index != BaseListRef::npos)
        parent->routines().remove(index);
      parent->routines().insert(routine, index);
      routine->owner(parent);
    }
    else if(db_mysql_TriggerRef::can_wrap(v))
    {
      db_mysql_TriggerRef trigger= db_mysql_TriggerRef::cast_from(v);
      size_t parent_schema_index= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(trigger->owner()->owner())->oldName().c_str());
      if(parent_schema_index == BaseListRef::npos)
      {
        triggers_to_insert.push_back(trigger);
        continue;
      }
      size_t parent_index= find_table_by_old_name(mod_cat->schemata().get(parent_schema_index), db_mysql_TableRef::cast_from(trigger->owner())->oldName().c_str());
      if(parent_index == BaseListRef::npos)
      {
        triggers_to_insert.push_back(trigger);
        continue;
      }
      db_mysql_TableRef parent= mod_cat->schemata().get(parent_schema_index)->tables().get(parent_index);
      size_t index= find_trigger_by_old_name(parent, trigger->oldName().c_str());
      if(index != BaseListRef::npos)
        parent->triggers().remove(index);
      parent->triggers().insert(trigger, index);
      trigger->owner(parent);
    }
  }

  // 2.
  for(std::vector<grt::ValueRef>::const_iterator e= vec.end(), it= vec.begin(); it != e; it++)
  {
    grt::ValueRef v= *it;
    if(db_mysql_TableRef::can_wrap(v))
    {
      db_mysql_TableRef table= db_mysql_TableRef::cast_from(v);
      table->triggers().remove_all();
      size_t parent_index= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(table->owner())->oldName().c_str());
      if(parent_index == BaseListRef::npos)
      {
        tables_to_insert.push_back(table);
        continue;
      }
      db_mysql_SchemaRef parent= mod_cat->schemata().get(parent_index);
      size_t index= find_table_by_old_name(parent, table->oldName().c_str());
      if(index  != BaseListRef::npos)
      {
        db_mysql_TableRef old_table= parent->tables().get(index);
        parent->tables().remove(index);
        copy_table_children(old_table, table);
        tr_map[old_table]= table;
        rev_tr_map[table]= old_table;
      }

      parent->tables().insert(table, index);
      table->owner(parent);
    }
  }

  // 3.
  for(std::vector<grt::ValueRef>::const_iterator e= vec.end(), it= vec.begin(); it != e; it++)
  {
    grt::ValueRef v= *it;
    if(db_mysql_SchemaRef::can_wrap(v))
    {
      db_mysql_SchemaRef schema= db_mysql_SchemaRef::cast_from(v);
      schema->views().remove_all();
      schema->routines().remove_all();
      schema->tables().remove_all();
      size_t index= find_schema_by_old_name(mod_cat, schema->oldName().c_str());

      if(index  != BaseListRef::npos)
      {
        db_mysql_SchemaRef old_schema= mod_cat->schemata().get(index);
        mod_cat->schemata().remove(index);
        copy_schema_children(old_schema, schema);
      }

      mod_cat->schemata().insert(schema, index);
      schema->owner(mod_cat);
    }
  }

  // 4.
  for (std::vector<db_mysql_TableRef>::const_iterator e= tables_to_insert.end(), it= tables_to_insert.begin(); it != e; it++)
  {
    db_mysql_TableRef table= *it;
    size_t parent= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(table->owner())->oldName().c_str());
    db_mysql_SchemaRef schema= mod_cat->schemata().get(parent);
    schema->tables().insert(table);
    table->owner(schema);
  }

  // 5.
  for (std::vector<db_mysql_ViewRef>::const_iterator e= views_to_insert.end(), it= views_to_insert.begin(); it != e; it++)
  {
    db_mysql_ViewRef view= *it;
    size_t parent= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(view->owner())->oldName().c_str());
    db_mysql_SchemaRef schema= mod_cat->schemata().get(parent);
    schema->views().insert(view);
    view->owner(schema);
  }
  for (std::vector<db_mysql_RoutineRef>::const_iterator e= routines_to_insert.end(), it= routines_to_insert.begin(); it != e; it++)
  {
    db_mysql_RoutineRef routine= *it;
    size_t parent= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(routine->owner())->oldName().c_str());
    db_mysql_SchemaRef schema= mod_cat->schemata().get(parent);
    schema->routines().insert(routine);
    routine->owner(schema);
  }
  for (std::vector<db_mysql_TriggerRef>::const_iterator e= triggers_to_insert.end(), it= triggers_to_insert.begin(); it != e; it++)
  {
    db_mysql_TriggerRef trigger= *it;
    size_t parent_schema= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(trigger->owner()->owner())->oldName().c_str());
    size_t parent= find_table_by_old_name(mod_cat->schemata().get(parent_schema), db_mysql_TableRef::cast_from(trigger->owner())->oldName().c_str());
    db_mysql_TableRef table= mod_cat->schemata().get(parent_schema)->tables().get(parent);
    table->triggers().insert(trigger);
    trigger->owner(table);
  }

  std::vector<db_ForeignKeyRef> new_fks;
  
  // 6a. update FKs
  for(size_t i= 0; i < mod_cat->schemata().count(); i++)
  {
    db_mysql_SchemaRef schema= mod_cat->schemata().get(i);
    for(size_t j= 0; j < schema->tables().count(); j++)
    {
      db_mysql_TableRef table= schema->tables().get(j);
      //
      Table_replacement_map::const_iterator it= 
        rev_tr_map.find(table);
      if (it != rev_tr_map.end())
      {
        grt::StringRef ename = table->tableEngine();
        db_mysql_StorageEngineRef engine = bec::TableHelper::get_engine_by_name(_manager->get_grt(), ename);
        if (!engine->supportsForeignKeys())
        {

          db_mysql_TableRef old_table= db_mysql_TableRef::cast_from(it->second);
          for(size_t sz= old_table->foreignKeys().count(), i= 0; i < sz; ++i)
          {
            db_mysql_ForeignKeyRef fk= old_table->foreignKeys().get(i);
            table->foreignKeys().insert(fk);
            fk->owner(table);
            std::list<db_ColumnRef> new_cols; 
            for(size_t l= 0; l < fk->columns().count(); ++l) 
            { 
              db_ColumnRef old_col= fk->columns().get(l); 
              size_t parent_index= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(old_col->owner()->owner())->oldName().c_str()); 
              if(parent_index == BaseListRef::npos) 
                continue; 

              db_mysql_SchemaRef parent= mod_cat->schemata().get(parent_index); 
              size_t index= find_table_by_old_name(parent, db_mysql_TableRef::cast_from(old_col->owner())->oldName().c_str()); 
              if(index  == BaseListRef::npos) 
                continue; 
              db_mysql_TableRef fktable = parent->tables().get(index); 
              new_cols.push_back(find_column_by_old_name(fktable, old_col->oldName().c_str())); 
            } 
            fk->columns().remove_all(); 
            std::list<db_ColumnRef>::const_iterator e= new_cols.end(); 
            for(std::list<db_ColumnRef>::const_iterator cit= new_cols.begin(); cit != e; ++cit) 
              fk->columns().insert(*cit);

          }
        }
      }
      //
      for(size_t k= 0; k < table->foreignKeys().count(); k++)
      {
        db_mysql_ForeignKeyRef fk= table->foreignKeys().get(k);
        Table_replacement_map::const_iterator it= 
          tr_map.find(fk->referencedTable());


        db_mysql_TableRef new_table;
        if (it == tr_map.end())
        {
          if(!fk->referencedTable().is_valid())
            continue;
          size_t parent_index= find_schema_by_old_name(mod_cat, db_mysql_SchemaRef::cast_from(fk->referencedTable()->owner())->oldName().c_str());
          if(parent_index == BaseListRef::npos)
            continue;
          db_mysql_SchemaRef parent= mod_cat->schemata().get(parent_index);
          size_t index= find_table_by_old_name(parent, fk->referencedTable()->oldName().c_str());
          if(index  == BaseListRef::npos)
            continue;
          new_table= parent->tables().get(index);
        }else
          new_table= db_mysql_TableRef::cast_from(it->second);

        fk->referencedTable(new_table);
        std::list<db_ColumnRef> new_cols;
        for(size_t l= 0; l < fk->referencedColumns().count(); l++)
        {
          db_ColumnRef old_col= fk->referencedColumns().get(l);
          new_cols.push_back(find_column_by_old_name(new_table, old_col->oldName().c_str()));
        }
        fk->referencedColumns().remove_all();
        std::list<db_ColumnRef>::const_iterator e= new_cols.end();
        for(std::list<db_ColumnRef>::const_iterator cit= new_cols.begin(); cit != e; cit++)
          fk->referencedColumns().insert(*cit);
      }
    }
  }

  // 6b. update model diagrams
  grt::ListRef<workbench_physical_Diagram> model_views(grt::ListRef<workbench_physical_Diagram>::cast_from(
    _manager->get_grt()->get("/wb/doc/physicalModels/0/diagrams")));

  for(size_t i= 0; i < model_views.count(); i++)
  {
    workbench_physical_DiagramRef mv= model_views.get(i);
    grt::ListRef<model_Figure> figures= mv->figures();
    for(size_t j= 0; j < figures.count(); j++)
    {
      model_FigureRef f= figures.get(j);
      if (!workbench_physical_TableFigureRef::can_wrap(f))
        continue;

      workbench_physical_TableFigureRef tf=
        workbench_physical_TableFigureRef::cast_from(f);

      if (!tf->table().is_valid())
        continue;

      Table_replacement_map::const_iterator it= 
        tr_map.find(tf->table());

      if (it == tr_map.end())
        continue;

      // Replace table ref in the table figure.
      tf->table(db_TableRef::cast_from(it->second));      
    }

    for(size_t j= 0; j < figures.count(); j++)
    {
      model_FigureRef f= figures.get(j);
      if (!workbench_physical_TableFigureRef::can_wrap(f))
        continue;

      workbench_physical_TableFigureRef tf=
        workbench_physical_TableFigureRef::cast_from(f);

      if (!tf->table().is_valid())
        continue;

      // Make sure all FKs have matching connections
      GRTLIST_FOREACH(db_ForeignKey, tf->table()->foreignKeys(), fk)
      {
        mv->createConnectionForForeignKey(*fk);
      }
    }    
  }

  undo.end(_("Apply Changes from DB to Model"));
}

std::string DbMySQLScriptSync::save_script_to_file(const std::string& script, const std::string& filename)
{
  GError *err= NULL;
  if(!g_file_set_contents(filename.c_str(), script.c_str(), (gssize)strlen(script.c_str()), &err))
    return std::string(err->message);
  return std::string();
}
