/* 
 * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */
#include "stdafx.h"

//#include "grtdb/db_object_helpers.h"

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

#include "grtpp.h"

#include "diff/diffchange.h"
#include "diff_dbobjectmatch.h"
#include <boost/lambda/lambda.hpp>

//#define _DB_OBJECT_MATCH_DEBUG_

using namespace grt;

std::string get_qualified_schema_object_name(GrtNamedObjectRef object, const bool case_sensitive)
{
  std::string s("`");
  s.append(object->owner()->name().c_str()).append("`.`").append(object->name().c_str()).append("`");
  return case_sensitive?s:base::toupper(s);
}

std::string get_qualified_schema_object_old_name(GrtNamedObjectRef object, const bool case_sensitive)
{

  const char *parent_name= NULL;
  if(GrtNamedObjectRef::can_wrap(object->owner()))
  {
    GrtNamedObjectRef pr= GrtNamedObjectRef::cast_from(object->owner());

    parent_name= pr->oldName().empty() ? 
      pr->name().c_str() : pr->oldName().c_str();
  }
  else
  {
    parent_name=
      object->owner()->name().c_str();
  }

  std::string s("`");
  s.append(parent_name).append("`.`").append(object->oldName().c_str()).append("`");

  return case_sensitive?s:base::toupper(s);
}

/////////////////////////////////////////////////////////////////////////////////////////////
// Alter OMF
/////////////////////////////////////////////////////////////////////////////////////////////

bool grt::DbObjectMatchAlterOmf::less(const grt::ValueRef& l, const grt::ValueRef& r) const
{
  if (l.type() == r.type() && l.type() == ObjectType)
  {
    if(db_IndexColumnRef::can_wrap(l) && db_IndexColumnRef::can_wrap(r))
    {
      db_IndexColumnRef lc= db_IndexColumnRef::cast_from(l);
      db_IndexColumnRef rc= db_IndexColumnRef::cast_from(r);
      return less(lc->referencedColumn(), rc->referencedColumn());
    }
    else if (GrtNamedObjectRef::can_wrap(l) && GrtNamedObjectRef::can_wrap(r))
    {
      GrtNamedObjectRef left = GrtNamedObjectRef::cast_from(l);
      GrtNamedObjectRef right = GrtNamedObjectRef::cast_from(r);

      if (left.valueptr() && right.valueptr())
      {
        std::string l_str, r_str;

        if(strlen(left->oldName().c_str()) > 0)
          l_str= get_qualified_schema_object_old_name(left, case_sensitive);
        else
          l_str= get_qualified_schema_object_name(left, case_sensitive);

        if(strlen(right->oldName().c_str()) > 0)
          r_str= get_qualified_schema_object_old_name(right, case_sensitive);
        else
          r_str= get_qualified_schema_object_name(right, case_sensitive);

        int result= l_str.compare(r_str);

#ifdef _DB_OBJECT_MATCH_DEBUG_
        std::cout << std::endl << "DbObjectMatchAlterOmf::less(" << l_str << ", " << r_str << ") = " << result << std::endl;
#endif
        return result < 0;
      } 
    }
    else if (GrtObjectRef::can_wrap(l) && GrtObjectRef::can_wrap(r))
    {
      GrtObjectRef left = GrtObjectRef::cast_from(l);
      GrtObjectRef right = GrtObjectRef::cast_from(r);

      if (left.valueptr() && right.valueptr())
      {
        return (strcmp(left->name().c_str(), right->name().c_str()) < 0);
      }
    }
    else if (ObjectRef::can_wrap(l) && ObjectRef::can_wrap(r))
    {
      ObjectRef left = ObjectRef::cast_from(l);
      ObjectRef right = ObjectRef::cast_from(r);
      if ((left.class_name() == right.class_name())
        && left.has_member("oldName"))
      {
        const char *l_str= NULL;
        const char *r_str= NULL;

        if(strlen(left.get_string_member("oldName").c_str()) > 0)
          l_str= left.get_string_member("oldName").c_str();
        else
          l_str= left.get_string_member("name").c_str();

        if(strlen(right.get_string_member("oldName").c_str()) > 0)
          r_str= right.get_string_member("oldName").c_str();
        else
          r_str= right.get_string_member("name").c_str();

        return strcmp(l_str, r_str) < 0;
      }
    }
  }
  return std::less<grt::ValueRef>()(l, r);
}

bool grt::DbObjectMatchAlterOmf::equal(const ValueRef& l, const ValueRef& r) const
{
  if (l.type() == r.type() && l.type() == ObjectType)
  {
    if(db_IndexColumnRef::can_wrap(l) && db_IndexColumnRef::can_wrap(r))
    {
      db_IndexColumnRef lc= db_IndexColumnRef::cast_from(l);
      db_IndexColumnRef rc= db_IndexColumnRef::cast_from(r);
      return equal(lc->referencedColumn(), rc->referencedColumn());
    }
    else if (GrtNamedObjectRef::can_wrap(l) && GrtNamedObjectRef::can_wrap(r))
    {
      GrtNamedObjectRef left = GrtNamedObjectRef::cast_from(l);
      GrtNamedObjectRef right = GrtNamedObjectRef::cast_from(r);
      
      if (left.valueptr() && right.valueptr())
      {
        std::string l_str, r_str;

        if(strlen(left->oldName().c_str()) > 0)
          l_str= get_qualified_schema_object_old_name(left, case_sensitive);
        else
          l_str= get_qualified_schema_object_name(left, case_sensitive);

        if(strlen(right->oldName().c_str()) > 0)
          r_str= get_qualified_schema_object_old_name(right, case_sensitive);
        else
          r_str= get_qualified_schema_object_name(right, case_sensitive);

        int result= l_str.compare(r_str);

#ifdef _DB_OBJECT_MATCH_DEBUG_
        std::cout << std::endl << "DbObjectMatchAlterOmf::equal(" << l_str << ", " << r_str << ") = " << result << std::endl;
#endif
        return result == 0;
      } 
    }
    else if (GrtObjectRef::can_wrap(l) && GrtObjectRef::can_wrap(r))
    {
      GrtObjectRef left = GrtObjectRef::cast_from(l);
      GrtObjectRef right = GrtObjectRef::cast_from(r);

      if (left.valueptr() && right.valueptr())
      {
        return (strcmp(left->name().c_str(), right->name().c_str()) == 0);
      }
    }
    else if (ObjectRef::can_wrap(l) && ObjectRef::can_wrap(r))
    {
      ObjectRef left = ObjectRef::cast_from(l);
      ObjectRef right = ObjectRef::cast_from(r);
      if (left.valueptr() && right.valueptr()
        && (left.class_name() == right.class_name())
        && left.has_member("oldName"))
      {
        ObjectRef left = ObjectRef::cast_from(l);
        ObjectRef right = ObjectRef::cast_from(r);

        const char *l_str= NULL;
        const char *r_str= NULL;

        if(strlen(left.get_string_member("oldName").c_str()) > 0)
          l_str= left.get_string_member("oldName").c_str();
        else
          l_str= left.get_string_member("name").c_str();

        if(strlen(right.get_string_member("oldName").c_str()) > 0)
          r_str= right.get_string_member("oldName").c_str();
        else
          r_str= right.get_string_member("name").c_str();

        return strcmp(l_str, r_str) == 0;
      }
    }
  }

  return std::equal_to<grt::ValueRef>()(l, r);
}

bool caseless_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name, const std::string& default_name)
{
    std::string str1= base::toupper(ObjectRef::cast_from(obj1).get_string_member(name));
    std::string str2= base::toupper(ObjectRef::cast_from(obj2).get_string_member(name));
    if (str1 == default_name)
        str1 = "";
    if (str2 == default_name)
        str2 = "";
    return str1 == str2;  
}

bool comment_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name)
{
    std::string str1= ObjectRef::cast_from(obj1).get_string_member(name);
    std::string str2= ObjectRef::cast_from(obj2).get_string_member(name);
    str1 = bec::TableHelper::get_sync_comment(str1);
    str2 = bec::TableHelper::get_sync_comment(str2);
    if (db_mysql_SchemaRef::can_wrap(obj1))
        return true;
    return str1 == str2;
}

bool charset_collation_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name)
    {
        if (db_ColumnRef::can_wrap(obj1))
        {
            std::string fieldname(name == "characterSetName"?"defaultCharacterSetName":"defaultCollationName");
            std::string sql1= ObjectRef::cast_from(obj1).get_string_member(name);
            std::string sql2= ObjectRef::cast_from(obj2).get_string_member(name);
            if (sql1.empty())
                sql1 = GrtNamedObjectRef::cast_from(obj2)->owner().get_string_member(fieldname);
            if (sql2.empty())
                sql2 = GrtNamedObjectRef::cast_from(obj2)->owner().get_string_member(fieldname);
            return sql1 == sql2;
        }
        std::string sql1= ObjectRef::cast_from(obj1).get_string_member(name);
        std::string sql2= ObjectRef::cast_from(obj2).get_string_member(name);
        return sql1.empty() || sql2.empty();
}


bool fk_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name, grt::GRT* grt)
{
    // Here we do not compare the ability for engines to support foreign keys but
    // check if at both engines does not. This can be used to optimize further FK handling
    // (no need to check all the FKs then).
    grt::StringRef ename = db_mysql_TableRef::cast_from(obj1)->tableEngine();
    db_mysql_StorageEngineRef engine = bec::TableHelper::get_engine_by_name(grt, ename);

    //          if (!engine.is_valid() || !engine->supportsForeignKeys())
    //            return true;

    ename = db_mysql_TableRef::cast_from(obj2)->tableEngine();
    db_mysql_StorageEngineRef engine2 = bec::TableHelper::get_engine_by_name(grt, ename);

    if ((engine.is_valid() && !engine->supportsForeignKeys()) && (engine2.is_valid() && !engine2->supportsForeignKeys()))
        return true;

    return false;
}

bool formatted_type_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name, grt::GRT* grt)
{
    std::string sql1= ObjectRef::cast_from(obj1).get_string_member(name);
    std::string sql2= ObjectRef::cast_from(obj2).get_string_member(name);
    SqlFacade* parser = SqlFacade::instance_for_rdbms_name(grt, "Mysql");

    if (!parser)
        return false;

    sql1= parser->removeInterTokenSpaces(sql1);
    sql2= parser->removeInterTokenSpaces(sql2);
    //        if (sql1 != sql2)
    //        std::cout<<"============"<<sql1<<std::endl<<std::endl<<sql2<<"==========================="<<std::endl;
    return sql1 == sql2;
}

bool sql_definition_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name, grt::GRT* grt)
{
    std::string sql1= ObjectRef::cast_from(obj1).get_string_member(name);
    std::string sql2= ObjectRef::cast_from(obj2).get_string_member(name);
    SqlFacade* parser = SqlFacade::instance_for_rdbms_name(grt, "Mysql");

    if (!parser)
        return false;
    std::string schema1 = db_TriggerRef::can_wrap(obj1)?
        GrtObjectRef::cast_from(obj1)->owner()->owner()->name():
    GrtObjectRef::cast_from(obj1)->owner()->name();
    std::string schema2 = db_TriggerRef::can_wrap(obj2)?
        GrtObjectRef::cast_from(obj2)->owner()->owner()->name():
    GrtObjectRef::cast_from(obj2)->owner()->name();
    sql1= parser->normalizeSqlStatement(sql1, schema1);
    sql2= parser->normalizeSqlStatement(sql2, schema2);
    sql1= base::toupper(parser->removeInterTokenSpaces(sql1));
    sql2= base::toupper(parser->removeInterTokenSpaces(sql2));
    //        sql1.erase(std::remove(sql1.begin(), sql1.end(), '\n'), sql1.end());
    //        sql2.erase(std::remove(sql2.begin(), sql2.end(), '\n'), sql2.end());
    //        if (sql1 != sql2)
    //        std::cout<<"\n=======================Def1=======================\n"<<sql1<<std::endl<<"\n=======================Def2=======================\n"<<std::endl<<sql2<<"!!!"<<(sql1 == sql2)<<std::endl;
    return sql1 == sql2;
}

bool ignore_index_col_name(const ValueRef obj1, const ValueRef obj2, const std::string& name)
{
    if(ObjectRef::cast_from(obj1).is_instance("db.IndexColumn") && 
        ObjectRef::cast_from(obj2).is_instance("db.IndexColumn") &&
        StringRef::can_wrap(ObjectRef::cast_from(obj1).get_member(name)) && 
        StringRef::can_wrap(ObjectRef::cast_from(obj2).get_member(name)))
        return true;
    return false;
}

std::string trim_zeros(const std::string& str)
{
  if (str.empty())
    return str;
  size_t pos = str.find_first_not_of("0");
  if (pos == std::string::npos)//there is only zeroes so return "0"
    return std::string("0");
  if (pos == 0)//no leading zeroes return unaltered string
    return str;
  return str.substr(pos);
}

std::string fixDefalutString(const std::string& str)
{
  if (str.empty())
    return str;
  if (str == std::string("0000-00-00 00:00:00")) return std::string("");
  //if (str == std::string("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) return std::string("");
  if (str == std::string("NOW()")) return std::string("CURRENT_TIMESTAMP");
  if (str == std::string("CURRENT_TIMESTAMP()")) return std::string("CURRENT_TIMESTAMP");
//  if (str == std::string("CURRENT_TIMESTAMP")) return std::string("CURRENT_TIMESTAMP");
  if (str == std::string("LOCALTIME()")) return std::string("CURRENT_TIMESTAMP");
  if (str == std::string("LOCALTIME")) return std::string("CURRENT_TIMESTAMP");
  if (str == std::string("LOCALTIMESTAMP")) return std::string("CURRENT_TIMESTAMP");
  if (str == std::string("LOCALTIMESTAMP()")) return std::string("CURRENT_TIMESTAMP");
  if (str == std::string("TRUE")) return std::string("1");
  if (str == std::string("FALSE")) return std::string("0");
  if (strcasecmp(str.c_str(),"NULL") == 0) return std::string("0");
  return trim_zeros(str);
};
//if(name1 == "defaultValue")
bool default_value_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name)
{
    std::string s1= ObjectRef::cast_from(obj1).get_string_member(name);
    std::string s2= ObjectRef::cast_from(obj2).get_string_member(name);
    s1.erase(std::remove_if(s1.begin(),s1.end(),std::bind2nd(std::equal_to<std::string::value_type>(),'\'')),s1.end());
    s2.erase(std::remove_if(s2.begin(),s2.end(),std::bind2nd(std::equal_to<std::string::value_type>(),'\'')),s2.end());
    s1 = fixDefalutString(s1);
    s2 = fixDefalutString(s2);
    return s1 == s2;
}  


bool name_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name)
{
    //db_ColumnRef name and old name should be always compared with case!
    if (db_ColumnRef::can_wrap(obj1))
        return false;
    std::string str1= base::toupper(ObjectRef::cast_from(obj1).get_string_member(name));
    std::string str2= base::toupper(ObjectRef::cast_from(obj2).get_string_member(name));
    return str1 == str2;  
}

bool ref_table_compare(const ValueRef obj1, const ValueRef obj2, const std::string& name)
{
    std::string str1 = db_mysql_ForeignKeyRef::cast_from(obj1)->referencedTable().is_valid()?base::toupper(db_mysql_ForeignKeyRef::cast_from(obj1)->referencedTable()->name()):"";
    std::string str2 = db_mysql_ForeignKeyRef::cast_from(obj1)->referencedTable().is_valid()?base::toupper(db_mysql_ForeignKeyRef::cast_from(obj2)->referencedTable()->name()):"";
    return str1 == str2;
}

grt::NormalizedComparer::NormalizedComparer(grt::GRT* grt, bool case_sensitive):_grt(grt),_case_sensitive(case_sensitive)
{
    rules["owner"].push_back(boost::bind(boost::function<bool ()> (boost::lambda::constant(true))));
    
    if(!_case_sensitive)
    {
    rules["name"].push_back(boost::bind(&name_compare,_1,_2,_3));
    rules["oldName"].push_back(boost::bind(&name_compare,_1,_2,_3));
    rules["referencedTable"].push_back(boost::bind(&ref_table_compare,_1,_2,_3));
    }

    //For IndexColumn names should be ignored
    rules["name"].push_back(boost::bind(&ignore_index_col_name,_1,_2,_3));

    rules["rowFormat"].push_back(boost::bind(&caseless_compare,_1,_2,_3,"DEFAULT"));
    rules["packKeys"].push_back(boost::bind(&caseless_compare,_1,_2,_3,"DEFAULT"));
    rules["tableEngine"].push_back(boost::bind(&caseless_compare,_1,_2,_3,""));
    rules["deleteRule"].push_back(boost::bind(&caseless_compare,_1,_2,_3,"RESTRICT"));
    rules["updateRule"].push_back(boost::bind(&caseless_compare,_1,_2,_3,"RESTRICT"));
    rules["comment"].push_back(boost::bind(&comment_compare,_1,_2,_3));
    rules["collationName"].push_back(boost::bind(&charset_collation_compare,_1,_2,_3));
    rules["characterSetName"].push_back(boost::bind(&charset_collation_compare,_1,_2,_3));
    rules["foreignKeys"].push_back(boost::bind(&fk_compare,_1,_2,_3,_4));
    rules["formattedRawType"].push_back(boost::bind(&formatted_type_compare,_1,_2,_3,_4));
    rules["formattedType"].push_back(boost::bind(&formatted_type_compare,_1,_2,_3,_4));
    rules["sqlDefinition"].push_back(boost::bind(&sql_definition_compare,_1,_2,_3,_4));
    rules["defaultValue"].push_back(boost::bind(&default_value_compare,_1,_2,_3));
};

bool grt::NormalizedComparer::normalizedComparison(const ValueRef obj1, const ValueRef obj2, const std::string name)
{
    std::list<comparison_rule>& rul_list = rules[name];
    for(std::list<comparison_rule>::iterator It = rul_list.begin(); It != rul_list.end(); ++It)
        if ((*It)(obj1,obj2,name,_grt))
            return true;
    return false; 
};

void grt::NormalizedComparer::init_omf(Omf* omf)
{
    omf->case_sensitive = _case_sensitive;
    omf->normalizer = boost::bind(&NormalizedComparer::normalizedComparison,this, _1, _2, _3);
};
