/*
 * (C) Copyright 1996-2016 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/*! \file HiLo.cc
    \brief Implementation of the Template class HiLo.
    
    Magics Team - ECMWF 2004
    
    Started: Tue 22-Jun-2004
    
    Changes:
    
*/

#include "HiLo.h"
#include "Filter.h"
#include "MatrixHandler.h"
#include "Transformation.h"


using namespace magics;


HiLo::HiLo()
{
}


HiLo::~HiLo()
{
}

/*!
 Class information are given to the output-stream.
*/	

void HiLo::print(ostream& out)  const
{
   out << "HiLo";
}



void HiLo::set(const map<string, string>& map)
{
   HiLoAttributes::set(map);
}


void HiLo::operator()(const PaperPoint& point)
{
    double value = point.value();
    if ( value > this->hilo_max_value_ ) return;
    if ( value < this->hilo_min_value_ ) return;
    if ( point.high() ) {
         if ( value > this->hi_max_value_ ) return;
         if ( value < this->hi_min_value_ ) return;    
    }
    if ( point.low() ) {
         if ( value > this->lo_max_value_ ) return;
         if ( value < this->lo_min_value_ ) return;
    }

    (*this->type_)(point, *this);
    (*this->marker_)(point, *this);
    //(*this->position_write_)(point);
}


struct SortHiLo 
{
    SortHiLo() {}
    ~SortHiLo() {}
    bool operator()(const UserPoint& first, const UserPoint& second)
    {   
        if (first.high() && second.low()) return true;
        if (first.low() && second.high()) return false;
        if (first.low() && second.low())  return first.value() < second.value(); 
        if (first.high() && second.high()) return first.value() > second.value(); 
        return false;
     }
};


struct Radius 
{
    Radius(double radius, const UserPoint& reference): radius_(radius), reference_(reference) {}
    ~Radius() {}
    bool operator()(const UserPoint& point)
    {   
      if (!same(point)) return false; 
      double dist = distance(point, reference_);
      if ( zero(dist) ) return false;
      return  dist  < radius_ * radius_;
    }
    
    bool same(const UserPoint& point) {
        if ( reference_.high() ) return point.high();
        if ( reference_.low() ) return point.low();
        return false;
    }

    double distance(const UserPoint& p1, const UserPoint& p2) {
        return ((p1.x() -p2.x())*(p1.x() -p2.x())) + ((p1.y() -p2.y())*(p1.y() -p2.y()));
    }
    
    double radius_;
    const UserPoint& reference_;
};



void HiLo::operator()(MatrixHandler& data, BasicGraphicsObjectContainer& parent)
{
    double radius;
    double area_width, area_height, area_max;
    const Transformation& transformation = parent.transformation();

    MinMaxFilter filter(data, this->window_size_, this->window_size_);
    filter.Process();

    std::sort(filter.begin(), filter.end(), SortHiLo());

    // compute a sensible radius within which we will not generate two Hi/Li close to each other
    area_height = data.row    (data.rows()    -1, 0) - data.row    (0, 0);
    area_width  = data.column (0, data.columns() -1) - data.column (0, 0);

    area_max = max (area_height, area_width);
    if (area_max > 180) area_max = 150;  // assumption: a width > 180 means polar-stereo - so adjust.

    radius = (area_max / 10.0);

    // printf ("area_height: %f, area_width: %f, area_max: %f, radius: %f\n", area_height, area_width, area_max, radius);

    for (vector<UserPoint>::const_iterator point = filter.begin(); point != filter.end(); ++point) {
        vector<UserPoint>::iterator last = std::remove_if(filter.begin(), filter.end(), Radius(radius, *point));
        filter.erase(last, filter.end());
    }

    clear();

    for (MinMaxFilter::const_iterator point = filter.begin(); point != filter.end(); ++point) {
    	PaperPoint xy = transformation(*point);
    	xy.low(point->low());
    	xy.high(point->high());
    	if ( transformation.in(xy) ) 
        	(*this)(xy);
    }
   
    // Now we feed the task
    for ( vector<BasicGraphicsObject*>::const_iterator object = begin(); object != end(); ++object)
    	parent.push_back(*object);
}
    


