/* 
 * © 2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "stdafx.h"

#include "grtdiff.h"
#include <assert.h>
#include <algorithm>
#include "diffchange.h"
#include "changefactory.h"
#include "grtlistdiff.h"

#include "grtpp_util.h"
#include "grts/structs.h"
#include "myx_util_functions.h"

namespace grt
{

DiffChange *diff_make(const ValueRef &source, const ValueRef &target, const Omf *omf,const sigc::slot<bool, ValueRef , ValueRef ,std::string> sqlDefinitionCmp)
{
  return GrtDiff().diff(source, target, omf, sqlDefinitionCmp);
}

void diff_apply(ValueRef &source, DiffChange &change)
{
  change.apply(source);
}

void diff_dump(const DiffChange &change)
{
  change.dump_log(0);
}


void diff_delete(DiffChange *change)
{
  delete change;
}


bool is_any(const ValueRef &v)
{
  return !v.valueptr() || v.type() == AnyType;
}

inline bool XOR(bool a, bool b)
{
  return a ^ b;
}

bool are_compatible(const ValueRef &source, const ValueRef &target, Type *cmptype)
{
  Type st= source.type();
  Type tt= target.type();

  if (cmptype) 
    *cmptype= (st == tt || tt == AnyType)? st: tt;

  return ((st == tt) && !is_any(source)) || XOR(is_any(source) , is_any(target));
}


bool are_compatible_lists(const BaseListRef &source, const BaseListRef &target, Type *cmptype)
{
  Type stl= is_any(source)? AnyType: source.content_type();
  Type ttl= is_any(target)? AnyType: target.content_type();

  Type type= (stl == ttl || ttl == AnyType)? stl: ttl;
  if (cmptype)
    *cmptype= type;

  return ((stl == ttl) && !is_any(source)) || XOR(is_any(source) , is_any(target)) 
      && (is_simple_type(type) || type == ObjectType);
}

DiffChange *GrtDiff::diff(const ValueRef &source, const ValueRef &target, const Omf* omf,const TSlotNormilizerSlot sqlDefinitionCmp)
{
  this->omf= omf? omf: NULL;
  return on_value(NULL, source, target, sqlDefinitionCmp);
}

DiffChange * GrtDiff::on_value( DiffChange *parent, const ValueRef &source, const ValueRef &target, const TSlotNormilizerSlot sqlDefinitionCmp )
{
  Type type;
  if (!are_compatible(source, target, &type))
    return on_uncompatible(parent, source, target);

  if (is_any(source))
    return ChangeFactory::create_value_added_change(parent, source, target);
  
  if (is_any(target))
    return ChangeFactory::create_value_removed_change(parent, source, target);

  // TODO use omf here to determine if we should replace value

  switch(type)
  {
  case IntegerType:
  case DoubleType:
  case StringType:
    return ChangeFactory::create_simple_value_change(parent, source, target);
    break;
  case ListType:
    return on_list(parent, BaseListRef::cast_from(source), BaseListRef::cast_from(target), sqlDefinitionCmp);
    break;
  case DictType:
    return on_dict(parent, DictRef::cast_from(source), DictRef::cast_from(target),sqlDefinitionCmp);
    break;
  case ObjectType:
    return on_object(parent, ObjectRef::cast_from(source), ObjectRef::cast_from(target),sqlDefinitionCmp);
    break;
  default:
    break;
  }
  assert(0);
  return NULL;
}

typedef unsigned int uint;

std::string GrtDiff::fixDefalutString(std::string str)const
{
  if (str == std::string("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) return std::string("");
  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("NULL");
  return str;

};

void GrtDiff::dbgprint(const ObjectRef &source, const ObjectRef &target)const
{
  for (int i = 0; i < dbgtabs; ++i)
    std::cout<<" ";
  if(GrtNamedObjectRef::can_wrap(source))
    std::cout<<"source "<<GrtNamedObjectRef::cast_from(source)->name().c_str()<<"\t\t\t";
  if(GrtNamedObjectRef::can_wrap(source))
    std::cout<<"target "<<GrtNamedObjectRef::cast_from(target)->name().c_str()<<std::endl;

}

DiffChange *GrtDiff::on_object(DiffChange *parent, const ObjectRef &source, const ObjectRef &target,const TSlotNormilizerSlot sqlDefinitionCmp)
{
  ChangeSet changes;
  MetaClass *meta= source.get_metaclass();
  //  ++dbgtabs;
  //  dbgprint(source,target);
  do
  {
    for (MetaClass::MemberList::const_iterator iter= meta->get_members_partial().begin(); iter != meta->get_members_partial().end(); ++iter)
    {
      if (iter->second.overrides) continue;

      std::string name1= iter->second.name;

      std::string attr;

      int dontfollow= !iter->second.owned_object;

      attr= meta->get_member_attribute(name1, "dontdiff");
      int dontdiff= attr.size() && attr[0] == '1';

      if (dontdiff || (name1 == "owner"))
        continue;

      ValueRef v1= source.get_member(name1);
      ValueRef v2= target.get_member(name1);

      // this block is temporary here: for db.Index::indexType
      // 1. should be no difference between "INDEX" and "FOREIGN"
      // 2. for IndexColumn names should be ignored
      {
        // 1.
        if (name1 == "indexType" && source.is_instance("db.Index") && target.is_instance("db.Index") &&
          StringRef::can_wrap(v1) && StringRef::can_wrap(v2))
        {
          StringRef s1(StringRef::cast_from(v1)), s2(StringRef::cast_from(v2));
          if (s1 == "INDEX" && s2 == "FOREIGN")
            continue;
          if (s2 == "INDEX" && s1 == "FOREIGN")
            continue;
        }
        // 2.
        else if(name1 == "name" && source.is_instance("db.IndexColumn") && target.is_instance("db.IndexColumn") &&
          StringRef::can_wrap(v1) && StringRef::can_wrap(v2))
        {
          continue;
        }

      } 

      if (v1.valueptr() || v2.valueptr())
      {
        if(name1 == "defaultValue")
        {
          std::string s1= StringRef::cast_from(v1).c_str();
          std::string s2= StringRef::cast_from(v2).c_str();
          s1 = fixDefalutString(s1);
          s2 = fixDefalutString(s2);
          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());
          bool eq= (strcmp(s1.c_str(), s2.c_str()) == 0);
          if(eq)
            continue;
        }  

        if((name1 == "name") || (name1 == "oldName"))
        {
          const char *n1= StringRef::cast_from(v1).c_str();
          char *n1up= g_utf8_strup(n1, g_utf8_strlen(n1, -1));
          const char *n2= StringRef::cast_from(v2).c_str();
          char *n2up= g_utf8_strup(n2, g_utf8_strlen(n2, -1));
          bool eq= (strcmp(n1up, n2up) == 0);
          g_free(n1up);
          g_free(n2up);
          if(eq)
            continue;

        }

          if (sqlDefinitionCmp(source,target,name1))
            continue;

//        if (name1 == "sqlDefinition") continue;
//        if (name1 == "routines") continue;
//        if (name1 == "triggers") continue;
//        if (name1 == "returnDatatype") continue;
//        if (name1 == "params") continue;
/*
        if (name1 == "characterSetName") continue;
        if (name1 == "collationName") continue;
        if (name1 == "flags") continue;
*/
//v2 is our model
        DiffChange* change = ChangeFactory::create_object_attr_modified_change(
            parent, source, target, name1, dontfollow?
            ChangeFactory::create_simple_value_change(parent, v1, v2):
            on_value(NULL, v1, v2,sqlDefinitionCmp));

        changes.append(change);
/*
//        if (name1 != "columns") continue;
        if (change)
        {
        std::cout<<name1;
        if (v1.type() == internal::String::static_type())
          std::cout<<" v1("<<StringRef::cast_from(v1).c_str()<<") v2("<<StringRef::cast_from(v2).c_str()<<")";
        std::cout<<std::endl;
//        ChangeFactory::create_object_attr_modified_change(
//        parent, source, target, name1, on_value(NULL, v1, v2,sqlDefinitionCmp));
        };
*/
      }

    }

    meta= meta->parent();
  }
  while (meta != 0);
  //  --dbgtabs;
  return ChangeFactory::create_object_modified_change(parent, source, target, changes);
}
  
  
DiffChange *GrtDiff::on_list(DiffChange *parent, const BaseListRef &source, const BaseListRef &target, const TSlotNormilizerSlot sqlDefinitionCmp)
{
  Type type;

  if (!are_compatible_lists(source, target, &type))
    return on_uncompatible(parent, source, target);

  return GrtListDiff().diff(source, target, omf, sqlDefinitionCmp);
}


DiffChange *GrtDiff::on_dict(DiffChange *parent, const DictRef &source, const DictRef &target, const TSlotNormilizerSlot sqlDefinitionCmp)
{
  ChangeSet changes;

  for (internal::Dict::const_iterator iter= source.begin(); iter != source.end(); ++iter)
  {
    std::string key= iter->first;
    ValueRef source_item(iter->second);

    if (!target.has_key(key))
      changes.append(ChangeFactory::create_dict_item_removed_change(parent, source, target, key));
    else
      changes.append(ChangeFactory::create_dict_item_modified_change(parent, source, target, key, on_value(NULL, source_item, target.get(key), sqlDefinitionCmp)));
  }

  for (internal::Dict::const_iterator iter= target.begin(); iter != target.end(); ++iter)
  {
    std::string key= iter->first;
    ValueRef target_item(iter->second);

    if (!source.has_key(key))
      changes.append(ChangeFactory::create_dict_item_added_change(parent, source, target, key, target_item));
  }

  return ChangeFactory::create_dict_change(parent, source, target, changes);
}

DiffChange *GrtDiff::on_uncompatible(DiffChange *parent, const ValueRef &source, const ValueRef &target)
{
  return ChangeFactory::create_value_added_change(parent, source, target);
}

}
