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

namespace grt
{
DEFAULT_LOG_DOMAIN("Diff module")

time_t timestamp()
{
#if defined(__WIN__) || defined(_WIN32) || defined(_WIN64)
  return GetTickCount();
#else
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}

//#define LOG_DIFF_TIME
boost::shared_ptr<DiffChange> diff_make(const ValueRef &source, const ValueRef &target, const Omf *omf)
{
#ifdef LOG_DIFF_TIME
  time_t start = timestamp();
#endif
  boost::shared_ptr<DiffChange> result = GrtDiff(omf).diff(source, target, omf);
#ifdef LOG_DIFF_TIME
  log_debug2("Diff took %li ticks\n", timestamp() - start);
  printf("Diff took %li ticks\n", timestamp() - start);
#endif
  return result;
}

bool is_any(const ValueRef &v)
{
  return !v.is_valid() || 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));
}

boost::shared_ptr<DiffChange> GrtDiff::diff(const ValueRef &source, const ValueRef &target, const Omf* omf)
{
  return on_value(boost::shared_ptr<DiffChange>(), source, target);
}

boost::shared_ptr<DiffChange>  GrtDiff::on_value( boost::shared_ptr<DiffChange> parent, const ValueRef &source, const ValueRef &target)
{
  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));
    break;
  case DictType:
    return on_dict(parent, DictRef::cast_from(source), DictRef::cast_from(target));
    break;
  case ObjectType:
    return on_object(parent, ObjectRef::cast_from(source), ObjectRef::cast_from(target));
    break;
  default:
    break;
  }
  assert(0);
  return boost::shared_ptr<DiffChange>();
}

boost::shared_ptr<DiffChange> GrtDiff::on_object(boost::shared_ptr<DiffChange> parent, const ObjectRef &source, const ObjectRef &target)
{
    ChangeSet changes;
    MetaClass *meta= source.get_metaclass();

    if (meta->has_member("isStub"))
    {
        ValueRef v1= source.get_member("isStub");
        ValueRef v2= target.get_member("isStub");
        if ((1 == IntegerRef::cast_from(v1)) || (1 == IntegerRef::cast_from(v2)))
            return boost::shared_ptr<DiffChange>();
    }
    if (meta->has_member("modelOnly"))
    {
        ValueRef v1= source.get_member("modelOnly");
        ValueRef v2= target.get_member("modelOnly");
        if ((1 == IntegerRef::cast_from(v1)) || (1 == IntegerRef::cast_from(v2)))
            return boost::shared_ptr<DiffChange>();
    }

    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 name= iter->second.name;
            std::string attr= meta->get_member_attribute(name, "dontdiff");
            int dontdiff= attr.size() && (atoi(attr.c_str()) & omf->dontdiff_mask);

            if (dontdiff)
                continue;

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

            if (!v1.is_valid() && !v2.is_valid())
                continue;

            if (omf->normalizer && omf->normalizer(source,target,name))
                continue;

            //        if (name == "sqlDefinition") continue;
            //v2 is our model
            boost::shared_ptr<DiffChange> change;
            const bool dontfollow= !iter->second.owned_object && (name != "flags") && (name != "columns");
            if(dontfollow && GrtObjectRef::can_wrap(v1) && GrtObjectRef::can_wrap(v2))
            {
                if (omf->normalizer && omf->normalizer(GrtObjectRef::cast_from(v1),GrtObjectRef::cast_from(v2),"name"))
                    continue;
                StringRef n1(v1.is_valid() ? GrtObjectRef::cast_from(v1)->name() : "");
                StringRef n2(v2.is_valid() ? GrtObjectRef::cast_from(v2)->name() : "");
                if(n1 == n2)
                    continue;
            }

            change = ChangeFactory::create_object_attr_modified_change(
                parent, source, target, name, dontfollow?
                ChangeFactory::create_simple_value_change(parent, v1, v2):
            on_value(boost::shared_ptr<DiffChange>(), v1, v2));

            changes.append(change);
#ifdef _DEBUG
#if 0
            //         if (name == "indices")
            //            continue;
            if (name == "formattedRawType")
            {
                //          std::cout<<"v1("<<GrtObjectRef::cast_from(target)->name().c_str()<<") v2("<<GrtObjectRef::cast_from(source)->name().c_str()<<")";
                //          std::cout<<name<<" v1("<<StringRef::cast_from(v1).c_str()<<") v2("<<StringRef::cast_from(v2).c_str()<<")"<<std::endl;        
            }

            //        continue;

            //        if (name != "columns") continue;
            if (change)
                //if (name == "referencedColumns")
            {
                std::cout<<name;
                //change->dump_log(0);
                if (dontfollow)
                {
                    std::cout<<" Simple ";
                    if (v1.is_valid() && v2.is_valid())
                        if (v1.type() == internal::Object::static_type())
                            std::cout<<"v1("<<GrtObjectRef::cast_from(v1)->name().c_str()<<") "<<std::endl<<"v2("<<GrtObjectRef::cast_from(v2)->name().c_str()<<")";
                        else if(v1.type() == internal::String::static_type())
                            std::cout<<"v1("<<StringRef::cast_from(v1).c_str()<<") "<<std::endl<<"v2("<<StringRef::cast_from(v2).c_str()<<")"<<std::endl;
                }
                else
                {
                    if (v1.type() == internal::String::static_type())
                        std::cout<<" v1("<<StringRef::cast_from(v1).c_str()<<") "<<std::endl<<"v2("<<StringRef::cast_from(v2).c_str()<<")"<<std::endl;
                    //              ChangeFactory::create_object_attr_modified_change(
                    //                parent, source, target, name, on_value(NULL, v1, v2,sqlDefinitionCmp));
                }
                std::cout<<std::endl;
            };
#endif
#endif
        }

        meta= meta->parent();
    }
    while (meta != 0);
    return ChangeFactory::create_object_modified_change(parent, source, target, changes);
}
  
  
boost::shared_ptr<DiffChange> GrtDiff::on_list(boost::shared_ptr<DiffChange> parent, const BaseListRef &source, const BaseListRef &target)
{
  Type type;

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

  return GrtListDiff::diff(source, target, omf);
}


boost::shared_ptr<DiffChange> GrtDiff::on_dict(boost::shared_ptr<DiffChange> parent, const DictRef &source, const DictRef &target)
{
  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(boost::shared_ptr<DiffChange>(), source_item, target.get(key))));
  }

  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);
}

boost::shared_ptr<DiffChange> GrtDiff::on_uncompatible(boost::shared_ptr<DiffChange> parent, const ValueRef &source, const ValueRef &target)
{
  return ChangeFactory::create_value_added_change(parent, source, target);
}

}
