/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  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.1 of the License, or (at your option) any later version.
**
**  This file 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2017-06-28
**  Copyright: 2017-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>

#include "CurveSplitter.h"

namespace QGpCoreMath {

  /*!
    \class CurveSplitter CurveSplitter.h
    \brief Brief description of class still missing

    Store relations between indexes (for instance of a QVector). A value is assigned
    to all added pairs of indexes. If some values are similar, indexes are considered
    as friends. Clusters are built with reciprocical friends.

    This object was originaly designed to clusterize a Curve made of randomly sorted
    samples distributed on several sub-curves (\sa Curve::split()).
  */

  CurveSplitter::Cluster::Cluster()
  {
    TRACE;
  }

  void CurveSplitter::Cluster::add(Item * item)
  {
    TRACE;
    QString s;
    for(QVector<Item *>::iterator it=_items.begin(); it!=_items.end(); it++) {
      s+=" ";
      s+=QString::number((*it)->_index);
    }
    _items.append(item);
    item->_cluster=this;
    App::log(2, tr("Add item index %1 to this cluster containing: %2\n").arg(item->_index).arg(s));
  }

  CurveSplitter::Item::Item()
  {
    TRACE;
    _index=-1;
    _cluster=0;
  }

  void CurveSplitter::Item::sortFriends(double maxErr)
  {
    TRACE;
    if(_friends.isEmpty()) {
      return;
    }
    QVector<ItemFriend> newFriends;
    QVector<ItemFriend> collectedFriends;
    int n=_friends.count();
    for(int i=1; i<n; i++) {
      const ItemFriend& f1=_friends.at(i);
      collectedFriends.append(f1);
      for(int j=i+1; j<n; j++) {
        const ItemFriend& f2=_friends.at(j);
        if(fabs(1.0-(_y+f1._slope*f2._dx)/f2._y)<maxErr) {
          collectedFriends.append(f2);
        }
      }
      if(collectedFriends.count()>newFriends.count()) {
        newFriends=collectedFriends;
      }
      collectedFriends.clear();
    }
    if(newFriends.count()>1) {
      _friends=newFriends;
    } else {
      _friends.clear();
    }
    App::log(2, tr("Set friends for item index %1\n").arg(_index));
    for(QVector<ItemFriend>::const_iterator it=_friends.begin(); it!=_friends.end(); it++) {
      App::log(2, tr("  index %1\n").arg(it->_index));
    }
  }

  void CurveSplitter::Item::addToCluster(Cluster * cluster, CurveSplitter * parent)
  {
    TRACE;
    if(!_cluster) {
      cluster->add(this);
      _cluster=cluster;
      for(int i=_friends.count()-1; i>=0; i--) {
        int f=parent->indexOf(_friends.at(i)._index);
        if(f>=0) {
          Item * item=parent->item(f);
          if(!item->_cluster && item->hasFriend(_index)) { // not yet part of a cluster
                                                           // and reciprocity of friendship
            item->addToCluster(cluster, parent);
          }
        }
      }
    }
  }

  bool CurveSplitter::Item::hasFriend(int index) const
  {
    TRACE;
    QVector<ItemFriend>::const_iterator it;
    for(it=_friends.begin(); it!=_friends.end(); it++) {
      if(it->_index==index) {
        return true;
      }
    }
    return false;
  }

  /*!
    Description of constructor still missing
  */
  CurveSplitter::CurveSplitter()
  {
    TRACE;
  }

  /*!
    Description of destructor still missing
  */
  CurveSplitter::~CurveSplitter()
  {
    TRACE;
    qDeleteAll(_clusters);
  }

  int CurveSplitter::indexOf(int index) const
  {
    TRACE;
    for(int i=_items.count()-1; i>=0; i--) {
      if(index==_items.at(i)._index) {
        return i;
      }
    }
    return -1;
  }

  /*!

  */
  void CurveSplitter::addPair(int i1, int i2, double y0, double dx, double y1)
  {
    TRACE;
    int i=indexOf(i1);
    if(i<0) {
      i=_items.count();
      _items.resize(i+1);
      _items[i]._index=i1;
      _items[i]._y=y0;
      App::log(2, tr("Add new item at %1 with value %2\n").arg(i1).arg(y0));
    }
    double slope=(y1-y0)/dx;
    _items[i]._friends.append(ItemFriend(i2, slope, dx, y1));
    App::log(2, tr("Add pair between %1 and %2 with slope %3 dx %4 value %5\n")
                        .arg(i1).arg(i2).arg(slope).arg(dx).arg(y1));
  }

  /*!
    \a maxErr is a relative error.
  */
  void CurveSplitter::clusterize(double maxErr)
  {
    TRACE;
    for(int i=_items.count()-1; i>=0; i--) {
      _items[i].sortFriends(maxErr);
    }
    qDeleteAll(_clusters);
    _clusters.clear();
    for(int i=_items.count()-1; i>=0; i--) {
      Cluster * cluster=new Cluster;
      _items[i].addToCluster(cluster, this);
      if(cluster->isEmpty()) {
        delete cluster;
      } else {
        _clusters.append(cluster);
      }
    }
  }

} // namespace QGpCoreMath

