/**
 * @file
 * $Revision: 1.0 $ 
 * $Date: 2014/02/27 18:49:25 $ 
 *
 *   Unless noted otherwise, the portions of Isis written by the USGS are public
 *   domain. See individual third-party library and package descriptions for
 *   intellectual property information,user agreements, and related information.
 *
 *   Although Isis has been used by the USGS, no warranty, expressed or implied,
 *   is made by the USGS as to the accuracy and functioning of such software
 *   and related material nor shall the fact of distribution constitute any such
 *   warranty, and no responsibility is assumed by the USGS in connection
 *   therewith.
 *
 *   For additional information, launch
 *   $ISISROOT/doc//documents/Disclaimers/Disclaimers.html in a browser or see
 *   the Privacy &amp; Disclaimers page on the Isis website,
 *   http://isis.astrogeology.usgs.gov, and the USGS privacy and disclaimers on
 *   http://www.usgs.gov/privacy.html.
 */

#include "ControlPointMerger.h"

#include <boost/foreach.hpp>

namespace Isis {

  /**
    * Constructs a default ControlPointMerger.
    * The tolerance defaults to DBL_MAX.
    */
  ControlPointMerger::ControlPointMerger() : m_image_tolerance(DBL_MAX), m_merged() { }


  /**
    * Constructs a ControlPointMerger with a given tolerance.
    * 
    * @param image_tolerance The tolerance used to determine if points will be merged.
    */
  ControlPointMerger::ControlPointMerger(const double image_tolerance) :
                      m_image_tolerance(image_tolerance), m_merged() { }


  /**
    * Destroys a ControlPointMerger.
    */
  ControlPointMerger::~ControlPointMerger() { }


  /**
    * Returns the number of points that have been merged.
    * 
    * @return @b int The number of points that have been merged.
    */
  int ControlPointMerger::size() const {
    return ( m_merged.size() );
  }


  /**
    * Clears the list of merged points.
    */
  void ControlPointMerger::clear() {
    m_merged.clear();
    return;
  }


  /**
  * @brief Merges control points that satisfy image coordinate constraints
  * 
  * Evaluates a list of candidate points to determine if they should be merged
  * into a source point.  For each candidate point, the image space distance
  * between its measures and the source point's measures on shared images are
  * averaged.  If this average is less than the ControlPointMerger's tolerance
  * the candidate point is merged into the source point.
  * 
  * @author 2016-10-25 Kris Becker
  * 
  * @param point       Source point to merge candidates into
  * @param candidates  List of point candidates
  * 
  * @return @b int     Number of measures merged
  */
  int ControlPointMerger::apply(ControlPoint *point, QList<MeasurePoint> &candidates)  {

    QList<ControlMeasure *> measures = point->getMeasures( true );
    QList<ControlPoint *> v_processed;
    clear();

    int nMerged(0);
    BOOST_FOREACH ( MeasurePoint &measure, candidates ) {
      ControlPoint *v_p = measure.getPoint();
      if ( point != v_p) {  // Don't process the same point!
        if (measure.isValid() && (!v_processed.contains(v_p))) {
          v_processed.append(v_p);

          // Compute distance statistics of common measures in two points
          Statistics stats; 
          BOOST_FOREACH ( ControlMeasure *m, measures ) {
            if ( v_p->HasSerialNumber( m->GetCubeSerialNumber() ) ) {
              ControlMeasure *c = v_p->GetMeasure( m->GetCubeSerialNumber() );
              if ( isValid(*c) ) {
                stats.AddData( image_distance(*m, *c) );
              }
            }
          }

          // Test for conditions of a merger. If there are common image measures,
          // use the statistics.
          if ( stats.ValidPixels() > 0 ) {
            if (stats.Average() <= m_image_tolerance) {
              nMerged += merge(point, v_p, stats);
              measure.disable();
              m_merged.append(measure);
            }
          }
        }
      }
    }
    return (nMerged);
  }


  /**
    * @brief Merges measures from one control point into another.
    * 
    * Merges measures on shared images from a candidate point into a source
    * point.  If a reference measure is merged, then its residual and goodness
    * of fit are set to the average and standard deviation of the distance
    * between the source and candidate points' measures.
    * 
    * @param source     The source point that measueres are merged into.
    * @param candidate  The candidate point that measures are merged from.
    * @param stats      Statistics object containing the distances between
    *                   measures in the source and candidate points on
    *                   common images.
    * 
    * @return @b int    The number of measures merged from the candidate point
    *                   into the source point.
    * 
    * @see ControlPointMerger::apply
    */
  int ControlPointMerger::merge(ControlPoint *source, ControlPoint *candidate, 
                                const Statistics &stats) {
    int nMerged(0);
    bool setCandidateType = true;

    // Gut check to ensure we don't merge into ourself
    if ( source != candidate ) {

      // Set up for merging a reference measure. Expectation is the tolerance
      // is <= 1, but don't assume that.
      double residual = stats.Average();
      double goodnessoffit = qMin(qMax(0.0, stats.StandardDeviation()), 1.0);
      ControlMeasureLogData data(ControlMeasureLogData::GoodnessOfFit,
                                  goodnessoffit);

      // Consider only valid measures                  
      QList<ControlMeasure*> measures = candidate->getMeasures(true); 

      //Check to see if reference measures for both source/candidate are the same.
      if (source->GetRefMeasure() == candidate->GetRefMeasure()) {


        setCandidateType = false;

      }


      BOOST_FOREACH ( ControlMeasure *m, measures ) {
        if ( !source->HasSerialNumber(m->GetCubeSerialNumber()) ) {
          QScopedPointer<ControlMeasure> p_m(new ControlMeasure(*m));

          // Handle the transfer of a reference measure from the candidate
          if ( candidate->GetRefMeasure() == m ) {
            p_m->SetResidual(residual, residual);
            p_m->SetLogData(data);


          }
          if (setCandidateType) {
            p_m->SetType(ControlMeasure::Candidate);
          }
          source->Add( p_m.take() ); 
          nMerged++;
        }
      }
      // Essentially disables this point meaning this point is already merged 
      // with another
      candidate->SetIgnored(true);
    }

    return ( nMerged );
  }


  /**
    * Determines if a measure is valid.
    * 
    * @param m The measure to check.
    * 
    * @return @b bool If the measure is ignored or rejected.
    */
  bool ControlPointMerger::isValid(const ControlMeasure &m) const {
      return ( !( m.IsIgnored() || m.IsRejected() ) );
  }

  /**
    * Computes the distance, in image, between two measures.
    * Note that this returns the squared distance because that
    * is what the nanoflann library expects.
    * 
    * @param source      A measure from the source point.
    * @param candidate   A measure from the candidate point.
    * 
    * @return @b double  The squared distance between the measures.
    */
  inline double ControlPointMerger::image_distance(const ControlMeasure &source, 
                                                   const ControlMeasure &candidate) const {
    double dx = source.GetSample() - candidate.GetSample();
    double dy = source.GetLine() - candidate.GetLine();
    return ( std::sqrt( dx*dx + dy*dy ) );
  }

}  // namespace Isis
