/*
 * Copyright (c) 2009, 2014, 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 <memory>
#include "grtlistdiff.h"
#include "grtpp.h"
#include "changefactory.h"
#include "changelistobjects.h"
#include "grtdiff.h"
#include "base/log.h"

#include <memory>
#include <algorithm>

namespace grt
{
//typedef ListDifference<ValueRef, internal::List::raw_iterator, internal::List::raw_iterator> GrtListDifference;

//TODO: check if this is till used
bool pless_struct::operator()(const ValueRef &_Left, const ValueRef &_Right) const
{ // apply operator< to operands
  return _Left < _Right;
}


struct omf_eq
{
  const Omf *omf;
  omf_eq(const Omf *omf) : omf(omf) {};
  bool operator ()(const ValueRef &l, const ValueRef &r)
  {
    return omf->equal(l, r);
  }
};

struct omf_lt
{
  const Omf *omf;
  omf_lt(const Omf *omf) : omf(omf) {};

  bool operator ()(const ValueRef &l, const ValueRef &r)
  {
    return omf->less(l, r);
  }
};


struct OmfEqPred : public std::binary_function<ValueRef, ValueRef, bool>
{ // functor for operator<
  const Omf *_omf;
  OmfEqPred(const Omf* omf) : _omf(omf) {}
  bool operator() (const ValueRef& _Left, const ValueRef& _Right) const { return _omf->equal(_Left, _Right); }
};

/**
 * Find Longest Increasing Subsequence (LIS)
 *
 * This is used to find sequence elements that needs to be moved
 * in order to sort sequence with minimal amount of elment moves
 * LIS represents maximum amount of elements that are already on
 * in the correct positions (one relative to each other) rest of
 * elements needs to be moved
 *
 * Note that elements in output are in reversed ored. You may use std::reverse(res.begin(), res.end());
 * if you really need to reverse them othervise using .rbegin() and .rend() should be enough
 *
 * @param src[in] source sequence all elements must be unique
 * @param res[out] container to be filled with elements belonging to LIS must have push_back and resize
 */
template<typename InputContainerType, typename OutputContainerType>
void reversed_LIS(const InputContainerType& src, OutputContainerType& res)
{
  typedef typename InputContainerType::value_type value_type;
  typedef std::map<value_type, size_t> TTailsMap;
  std::vector<size_t> sequence_history(src.size(), std::string::npos);//traces back LIS including each element
  //longest_sequence_tails.size() is LIS size, last element represents last
  TTailsMap longest_sequence_tails;//holds <element, index> that may become LIS endings, but not sequnce itself
  for (size_t i = 0; i < src.size(); ++i)
  {
    // It_added_value will hold iterator for newly added value
    typename TTailsMap::iterator It_added_value = longest_sequence_tails.insert(typename TTailsMap::value_type(src[i], i)).first;
    if (It_added_value == longest_sequence_tails.begin())
      sequence_history[i] = std::string::npos;//minmal element starts new sequence, mark it
    else
    {
      //yet another sequence member so, mark previous(by value not by index!) item as part of sequence
      sequence_history[i] = (--It_added_value)->second;
      ++It_added_value;
    }
    //current element is less than next one, but has the same sized LIS
    //thus it may be included in any LIS instead next one and possibly
    //some other LISes so it replaces bigger one, or in case if new
    //element has maximum value it becomes "king of the hill" representing
    //end of longest sequence that was found so far
    if (++It_added_value != longest_sequence_tails.end())
      longest_sequence_tails.erase(It_added_value);
  }

  if (longest_sequence_tails.empty())//empty input
    return;
  //this is the last element of LIS
  typename TTailsMap::iterator It = longest_sequence_tails.end();
  --It;
  size_t j = It->second;
  res.reserve(longest_sequence_tails.size());//avoid unecessary reallocations
  //trace back LIS thru history array
  do
  {
    res.push_back(src[j]);
  } while ((j = sequence_history[j]) != std::string::npos);
  //history where iterated in backward direction reverse it
  //no need toreverse it here, using reverse iteratros would be much faster
  //  std::reverse(res.begin(), res.end());
}

bool diffPred(const boost::shared_ptr<ListItemChange>& a, const boost::shared_ptr<ListItemChange>& b)
{
  if (a->get_change_type() == grt::ListItemRemoved)
    if (b->get_change_type() == grt::ListItemRemoved)
      return a->get_index() > b->get_index();//removals should be processed right to left to keep still existing items at the same indexes
    else
      return false;
    else
      if (b->get_change_type() == grt::ListItemRemoved)
        return true;
      else
        return a->get_index() < b->get_index();
}

boost::shared_ptr<MultiChange> GrtListDiff::diff(const BaseListRef &source, const BaseListRef &target, const Omf *omf)
{
  typedef std::vector<size_t> TIndexContainer;
  default_omf def_omf;
  std::vector<boost::shared_ptr<ListItemChange> > changes;
  const Omf *comparer = omf?omf:&def_omf;
  ValueRef prev_value;
  //This is indexes of source's elements that exist in both target and source
  //in order of element appearance in target
  //We need to swap indexes(and eventually elements) so that source's elements order
  //will become the same as target's
  TIndexContainer source_indexes;//new indexes for already existing elements
  TIndexContainer ordered_indexes;//ordered indexes list for set_difference
  for (size_t target_idx = 0; target_idx < target.count(); ++target_idx)
  {//look for something that exists in target but not in source, it should be added
    const ValueRef v = target.get(target_idx);
    internal::List::raw_const_iterator It_Dup = find_if (target.content().raw_begin(), target.content().raw_begin() + target_idx, std::bind2nd(OmfEqPred(comparer), v));
    if (It_Dup != target.content().raw_begin() + target_idx)
      continue;
    internal::List::raw_const_iterator It = find_if (source.content().raw_begin(), source.content().raw_end(), std::bind2nd(OmfEqPred(comparer), v));
    if (It == source.content().raw_end())
      changes.push_back(boost::shared_ptr<ListItemChange> (new ListItemAddedChange(v, prev_value, target_idx)));
    else//item exists in both target and source, save indexes
      source_indexes.push_back(source.get_index(*It));
    prev_value = v;
  };

  for (size_t source_idx = 0; source_idx < source.count(); ++source_idx)
  {//look for something that exists in source but not in target, it should be removed
    const ValueRef v = source.get(source_idx);

    //This shouldn't happend actually, since lists are expected to be unique
    //But in case of caseless compare we may have non-unique lists
    //so just skip it
    internal::List::raw_const_iterator It_Dup = find_if (source.content().raw_begin(), source.content().raw_begin() + source_idx, std::bind2nd(OmfEqPred(comparer), v));
    if (It_Dup != source.content().raw_begin() + source_idx)
      continue;

    internal::List::raw_const_iterator It = find_if (target.content().raw_begin(), target.content().raw_end(), std::bind2nd(OmfEqPred(comparer), v));
    if (It == target.content().raw_end())
    {
  #ifdef DEBUG_DIFF
      log_info("Removing %s from list\n", grt::ObjectRef::cast_from(v)->get_string_member("name").c_str());
      if (grt::ObjectRef::cast_from(v)->get_string_member("name") == "fk_tblClientApp_base_tblClient_base1_idx")
        dump_value(target);
  #endif
      changes.push_back(boost::shared_ptr<ListItemChange> (new ListItemRemovedChange(v, source_idx)));
    }
    else
      ordered_indexes.push_back(source_idx);
  };

  //  return changes.empty()? NULL : new MultiChange(ListModified, changes);// No ListItemOrderChange

  TIndexContainer stable_elements;
  reversed_LIS(source_indexes, stable_elements);
  TIndexContainer moved_elements(source_indexes.size()-stable_elements.size());
  std::set_difference(ordered_indexes.begin(), ordered_indexes.end(), stable_elements.rbegin(), stable_elements.rend(), moved_elements.begin());
  for (TIndexContainer::iterator It = moved_elements.begin(); It != moved_elements.end(); ++It)
  {
    internal::List::raw_const_iterator It_target = find_if (target.content().raw_begin(), target.content().raw_end(), std::bind2nd(OmfEqPred(comparer), source.get(*It)));
    prev_value = It_target == target.content().raw_begin()?ValueRef():*(It_target-1);
    boost::shared_ptr<ListItemOrderChange> orderchange(new ListItemOrderChange(source.get(*It), *It_target, omf, prev_value, target.get_index(*It_target)));
    //    if (!orderchange->subchanges()->empty())
    changes.push_back(orderchange);
  }

  for (TIndexContainer::iterator It = stable_elements.begin(); It != stable_elements.end(); ++It)
  {
    internal::List::raw_const_iterator It_target = find_if (target.content().raw_begin(), target.content().raw_end(), std::bind2nd(OmfEqPred(comparer), source.get(*It)));
    if (It_target != target.content().raw_end())
    {
      boost::shared_ptr<ListItemChange> change = create_item_modified_change(source.get(*It), *It_target, omf, target.get_index(*It_target));
      if (change)
        changes.push_back(change);
    }
  }
  ChangeSet retval;
  std::sort(changes.begin(), changes.end(), diffPred);
  for(std::vector<boost::shared_ptr<ListItemChange> >::const_iterator It = changes.begin(); It != changes.end(); ++It)
    retval.append(*It);
  return retval.empty()? boost::shared_ptr<MultiChange>() : boost::shared_ptr<MultiChange>(new MultiChange(ListModified, retval));

}

////////////////////////////////////////////////////////////////////////////
boost::shared_ptr<ListItemModifiedChange> create_item_modified_change(
                                                                      const ValueRef &source, 
                                                                      const ValueRef &target, 
                                                                      const Omf* omf, 
                                                                      const size_t index)
{
  boost::shared_ptr<DiffChange> subchange= GrtDiff(omf).diff(source, target, omf);
  if (!subchange)
    return boost::shared_ptr<ListItemModifiedChange>();
  //    diff_make(source, target, omf, sqlDefinitionCmp);
  return boost::shared_ptr<ListItemModifiedChange> (new ListItemModifiedChange(source, target, subchange, index));
}
  
}
