/* 
 * Copyright (c) 2007, 2013, 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
 */

#ifndef _GRTPP_UTIL_H_
#define _GRTPP_UTIL_H_
//#ifdef max
//#undef max
//#endif
#include <boost/function.hpp>
#include "grtpp.h"

#include <set>

#include <glib.h>

#define GRTLIST_FOREACH(type, list, iter) \
  for (grt::ListRef<type>::const_iterator iter##end= list.end(), iter= list.begin(); iter != iter##end; ++iter)

#define GRTLIST_REVERSE_FOREACH(type, list, iter) \
  for (grt::ListRef<type>::const_reverse_iterator iter##end= list.rend(), iter= list.rbegin(); iter != iter##end; ++iter)


namespace grt
{
  std::string MYSQLGRT_PUBLIC type_to_str(Type type);
  Type MYSQLGRT_PUBLIC str_to_type(const std::string &str);

  std::string MYSQLGRT_PUBLIC fmt_simple_type_spec(const SimpleTypeSpec &type);
  std::string MYSQLGRT_PUBLIC fmt_type_spec(const TypeSpec &type);
  std::string MYSQLGRT_PUBLIC fmt_arg_spec_list(const ArgSpecList &args);
  
  ValueRef MYSQLGRT_PUBLIC get_value_by_path(const ValueRef &root, const std::string &path);
  bool MYSQLGRT_PUBLIC set_value_by_path(const ValueRef &value, const std::string &path, const ValueRef &new_value);

  inline bool is_container_type(Type type)
  {
    if (type == ListType
        || type == DictType
        || type == ObjectType)
      return true;
    return false;
  }
    
  inline bool is_simple_type(Type type)
  {
    if (type == IntegerType
        || type == DoubleType
        || type == StringType)
      return true;
    return false;
  }

  std::string MYSQLGRT_PUBLIC get_guid();
  
  inline std::string path_base(const std::string &path)
  {
    std::string::size_type p= path.rfind('/');
    if (p != std::string::npos)
      return path.substr(0, p);
    return "";
  }


  inline std::string path_last(const std::string &path)
  {
    std::string::size_type p= path.rfind('/');
    if (p != std::string::npos)
      return path.substr(p);
    return "";
  }

/*
  inline std::string format_message(const MYX_GRT_MSG &msg, bool withtype= false)
  {
    std::string text;
    
    if (withtype)
    {
      switch (msg.msg_type)
      {
      case MYX_MSG_INFO:  text= "Info: "; break;
      case MYX_MSG_WARNING: text= "Warning: "; break;
      case MYX_MSG_ERROR: text= "Error: "; break;
      }
    }
    
    text+= msg.msg;
    
    if (msg.msg_detail && msg.msg_detail->strings_num > 0 && *msg.msg_detail->strings[0])
    {
      text+= " ("+std::string(msg.msg_detail->strings[0])+")";
    }
    
    return text;
  }
*/

  template<class O>
    inline Ref<O> find_named_object_in_list(const ListRef<O> &list, 
                                            const std::string &value,
                                            bool case_sensitive= true,
                                            const std::string &name= "name")
    {
      size_t i, c= list.count();
      
      if (case_sensitive)
      {
        for (i= 0; i < c; i++)
        {
          Ref<O> tmp= list[i];
          
          if (tmp.is_valid() && tmp->get_string_member(name) == value)
            return tmp;
        }
      }
      else
      {
        for (i= 0; i < c; i++)
        {
          Ref<O> tmp= list[i];
          
          if (tmp.is_valid() && g_ascii_strcasecmp(tmp->get_string_member(name).c_str(), value.c_str())==0)
            return tmp;
        }
      }
      return Ref<O>();
    }
  

  template<class O>
    inline Ref<O> find_object_in_list(const ListRef<O> &list, 
                                 const std::string &id)
    {
      size_t i, c= list.count();
      for (i= 0; i < c; i++)
      {
        Ref<O> value= list[i];
        
        if (value.is_valid() && value->id() == id)
          return value;
      }
      return Ref<O>();
    }
   
   
    template<class O>
    inline size_t find_object_index_in_list(ListRef<O> list,
      const std::string &id)
    {
      size_t i, c= list.count();
      for (i= 0; i < c; i++)
      {
        Ref<O> value= list.get(i);

        if (value.is_valid() && value.id() == id)
          return i;
      }
      return -1;
    }

    template<typename TPredicate>
    std::string get_name_suggestion(TPredicate duplicate_found_pred,const std::string &prefix, const bool serial)
    {
      char buffer[30] = "";
      int x= 1;
      std::string name;

      if (serial)
        g_snprintf(buffer, sizeof(buffer), "%i", x);
      name= prefix+buffer;
      while (duplicate_found_pred(name))
      {
        g_snprintf(buffer, sizeof(buffer), "%i", x++);
        name= prefix+buffer;
      } 
      return name;
    }

  MYSQLGRT_PUBLIC std::string get_name_suggestion_for_list_object(const BaseListRef &objlist, const std::string &prefix, bool serial= true);
  
  
  MYSQLGRT_PUBLIC ObjectRef find_child_object(const DictRef &dict, const std::string &id, bool recursive= true);
  MYSQLGRT_PUBLIC ObjectRef find_child_object(const BaseListRef &list, const std::string &id, bool recursive= true);
  MYSQLGRT_PUBLIC ObjectRef find_child_object(const ObjectRef &object, const std::string &id, bool recursive= true);

  MYSQLGRT_PUBLIC void update_ids(ObjectRef object, const std::set<std::string>& skip_members= std::set<std::string>());
  // the following merge functions are not recursive
  MYSQLGRT_PUBLIC void append_contents(BaseListRef target, BaseListRef source);
  MYSQLGRT_PUBLIC void replace_contents(BaseListRef target, BaseListRef source);
  MYSQLGRT_PUBLIC void merge_contents_by_name(ObjectListRef target,
                                              ObjectListRef source,
                                              bool replace_matching);
  MYSQLGRT_PUBLIC void merge_contents_by_id(ObjectListRef target,
                                            ObjectListRef source,
                                            bool replace_matching);

  MYSQLGRT_PUBLIC void replace_contents(DictRef target, DictRef source);
  MYSQLGRT_PUBLIC void merge_contents(DictRef target, DictRef source, bool overwrite);
  MYSQLGRT_PUBLIC void merge_contents(ObjectRef target, ObjectRef source);
  
  MYSQLGRT_PUBLIC bool compare_list_contents(const ObjectListRef &list1, const ObjectListRef &list2);

  MYSQLGRT_PUBLIC std::string join_string_list(const StringListRef &list, const std::string &separator);
  
  MYSQLGRT_PUBLIC void remove_list_items_matching(ObjectListRef list, const boost::function<bool (grt::ObjectRef)> &matcher);
  
  //XXX don't use this for objects, use CopyContext::copy() instead
  MYSQLGRT_PUBLIC ValueRef copy_value(ValueRef value, bool deep);

  struct MYSQLGRT_PUBLIC CopyContext
  {
    GRT *grt;
    std::map<std::string, ValueRef> object_copies;
    std::list<ObjectRef> copies;
    
    CopyContext(GRT *agrt) : grt(agrt) {}
        
    ObjectRef copy(const ObjectRef &object, std::set<std::string> skip_members= std::set<std::string>());
    ObjectRef shallow_copy(const ObjectRef &object);
    void finish() { update_references(); }
    void update_references();
    
    ValueRef copy_for_object(ValueRef object);
    
  private:
    ObjectRef duplicate_object(ObjectRef object, std::set<std::string> skip_members, bool dontfollow);
    void copy_list(BaseListRef &list, const BaseListRef &source, bool dontfollow);
    void copy_dict(DictRef &dict, const DictRef &source, bool dontfollow);
  };

  template<typename OType>
  OType copy_object(const OType& object, std::set<std::string> skip_members = std::set<std::string>())
  {
      CopyContext copier(object.get_grt());
      OType copy;

      copy= OType::cast_from(copier.copy(object, skip_members));
      copier.finish();

      return copy;
  }

  template<typename OType>
  OType shallow_copy_object(const OType& object)
  {
      CopyContext copier(object.get_grt());
      OType copy;

      copy= OType::cast_from(copier.shallow_copy(object));
      //this is a shallow copy so changing the references in it's sub-values would mean modifying
      //their originals
      //copier.finish();

      return copy;
  }

  MYSQLGRT_PUBLIC void dump_value(const grt::ValueRef &value);
 
  // temporary code
  MYSQLGRT_PUBLIC bool init_python_support(grt::GRT *grt, const std::string &python_module_path);
  MYSQLGRT_PUBLIC void add_python_module_dir(grt::GRT *grt, const std::string &python_module_path);
  
  // diffing
  
  class DiffChange;
  typedef boost::function<bool (ValueRef, ValueRef,std::string)> TSlotNormalizerSlot;

  struct MYSQLGRT_PUBLIC Omf
  {
    TSlotNormalizerSlot normalizer;
    bool case_sensitive;
    //some structure members needs to be skipped only when diffing model vs db but not when diffing model
    //_dontdiff_mask will hold mask to allow selective bypass of ceratin fields
    //1 always diff, 2 diff only vs db, 4 diff only vs live object
    unsigned int dontdiff_mask;
    Omf(): case_sensitive(true), dontdiff_mask(1) {};
    virtual ~Omf() {};
    virtual bool less(const ValueRef& , const ValueRef&) const= 0;
    virtual bool equal(const ValueRef& , const ValueRef&) const= 0;
  };

  struct default_omf : public Omf
{
    bool peq(const ValueRef &l, const ValueRef &r)const
    {
        if ((l.type() == r.type() && l.type() == ObjectType)
            && ObjectRef::can_wrap(l) && ObjectRef::can_wrap(r))
        {
            ObjectRef left = ObjectRef::cast_from(l);
            ObjectRef right = ObjectRef::cast_from(r);
            if(left->has_member("name"))
                return left->get_string_member("name") == right->get_string_member("name");
        }
        return l == r;
    }

    bool pless(const ValueRef &l, const ValueRef &r)const
    {
        if ((l.type() == r.type() && l.type() == ObjectType)
            && ObjectRef::can_wrap(l) && ObjectRef::can_wrap(r))
        {
            ObjectRef left = ObjectRef::cast_from(l);
            ObjectRef right = ObjectRef::cast_from(r);
            if(left->has_member("name"))
                return left->get_string_member("name") < right->get_string_member("name");
        }
        return l < r;
    }


  virtual bool less(const ValueRef& l, const ValueRef& r)const {return pless(l,r);};
  virtual bool equal(const ValueRef& l, const ValueRef& r)const  {return peq(l,r);};
};


  MYSQLGRT_PUBLIC
    boost::shared_ptr<DiffChange> diff_make(const ValueRef &source, const ValueRef &target, const Omf* omf, bool dont_clone_values = false);
  
};

#endif
