/******************************** LICENSE ********************************

 Copyright 2007 European Centre for Medium-Range Weather Forecasts (ECMWF)

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at 

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 ******************************** LICENSE ********************************/

/*! \file IsoPlot.cc  
 \brief Implementation of the Template class IsoPlot.
 
 Magics Team - ECMWF 2004 
 
 Started: Wed 3-Mar-2004
  
 Changes:
 */
 
 
#include "IsoPlot.h"
#include "Factory.h"
#include "MatrixHandler.h"
#include "Timer.h"
#include "GeoPoint.h"
#include "IsoHighlight.h"
#include "Colour.h"
#include "AutoLock.h"
#include "ThreadControler.h"
#include "LegendVisitor.h"
#include "Histogram.h"



namespace magics {
  
  
static MutexCond producerMutex_;

template<class P> IsoPlot<P>::IsoPlot() {
    setTag("isoline");
}

template<class P> IsoPlot<P>::~IsoPlot() {
}

/*!
 Class information are given to the output-stream.
 */
template<class P> void IsoPlot<P>::print(ostream& out) const {
    out << "IsoPlot<P>[";
    IsoPlotAttributes<P>::print(out);
    out << "]";
}



/*! \brief Helper class for Countouring
 * 
 */
template <class P> 
class CellBox : public VectorOfPointers<vector<CellBox<P>* > >
{
public:
    CellBox(const CellArray<P>* parent, int row1, int row2, int column1, int column2) :
             parent_(parent), row1_(row1), row2_(row2), column1_(column1), column2_(column2) {
        shape_ = new Shape();
        shape_->reference_ = 1;
    }

    CellBox(const CellArray<P>* parent) :
            parent_(parent), row1_(0), row2_(parent->rows_-1), column1_(0), column2_(parent->columns_-1) {
        shape_ = new Shape();
    }
    
    CellBox() :
            parent_(0), row1_(0), row2_(0), column1_(0), column2_(0) {shape_ = new Shape();
    }

    virtual ~CellBox() {
        delete shape_;
    }
   
    virtual double value () {
        return (*parent_)(row1_, column1_)->value(0);
    }
    
    virtual RangeType range()
    {
        // First try to finfd iif the cell is outOfRange...
        bool out = true;
        for (int row = row1_; row <= row2_; row++) {
            for (int column = column1_; column <= column2_; column++) {
                if ((*parent_)(row, column)->range() != outOfRange)
                {
                    out = false;
                    break;
                }
            }
        }
        if ( out )
            return outOfRange;

        int min = INT_MAX;
        int max = INT_MIN;
        for (int row = row1_; row <= row2_; row++)
        {
           for (int column = column1_; column <= column2_; column++)
           {
             Cell<P>* cell = (*parent_)(row, column);
             RangeType range   = cell->range();
             if ( range == outOfRange)
                continue;

             if (range == multipleRange)
                return multipleRange;

             min = ( min < cell->min_ ) ? min : cell->min_;
             max = ( max > cell->max_ ) ? max : cell->max_;
             if (max-min > 0 ) 
                return multipleRange; 
           }
        }
        return  ( max-min == 0 ) ?  singleRange : multipleRange;
    }
    
    void reduce(Shape* shape)
    {
        static int level = 0;
        level++;

        Shape::iterator first = shape->begin();
        Shape::iterator second = shape->begin();
        while(++second != shape->end())
        {              
           if ( *first == *second)
           {
             shape->erase(first);                
             shape->erase(second);                
             reduce(shape);
             return;
           }
           ++first;      
        }
    }

    bool reshape(Shape* shape1, Shape* shape2)
    {
        if( !shape1->intersect(*shape2) ) 
            return false;
        Shape::iterator where;
        Shape::iterator from;
        for ( Shape::iterator line1= shape1->begin(); line1 != shape1->end(); ++line1) {
             for ( Shape::iterator line2= shape2->begin(); line2 != shape2->end(); ++line2) {
                 
                 if ( *line2 == *line1 ) {
                     where = line1;
                     from = line2;
                     ++from;
                     std::rotate(shape2->begin(), from, shape2->end());
                     shape1->splice(where, *shape2);
                     
                        // Remove common lines!
                      reduce(shape1);    
                      if ( shape1->minx_ > shape2->minx_ ) shape1->minx_ = shape2->minx_;
                      if ( shape1->maxx_ < shape2->maxx_ ) shape1->maxx_ = shape2->maxx_;
                      if ( shape1->miny_ > shape2->miny_ ) shape1->miny_ = shape2->miny_;
                      if ( shape1->maxy_ < shape2->maxy_ ) shape1->maxy_ = shape2->maxy_;
                      // here normally we have copied shape 2 into shape1 we can release shape2
                      if ( shape2->reference_ == 0 ) delete shape2;
                      return true;
                 }
             }
        }
        return false;
    }
    
    void reduce(std::list<Shape*>& list) 
    {
        static int level = 0;
        level++;
        
        for (std::list<Shape*>::iterator entry1 = list.begin(); entry1 != list.end(); ++entry1 ) {
            for (std::list<Shape*>::iterator entry2 = list.begin(); entry2 != list.end(); ++entry2) {
               if ( entry1 == entry2 ) continue; 
               if ( reshape(*entry1, *entry2) ) {                 
                    list.erase(entry2);
                    reduce(list);
                    level--;
                    return;
                }
            }
        }
    }

    void reshape(Shape* cell)
    {
        Colour colour = cell->colour_;
        if ( colour.name() == "none" ) return;
        map<Colour, list<Shape*> >::iterator entry = this->helper_.find(colour);
        if (entry == helper_.end() ) {
            helper_.insert(make_pair(colour, list<Shape*>()));
            entry = helper_.find(colour);
            entry->second.push_back(cell);
            return;
        }

        list<Shape*>::iterator last = entry->second.end();
        
        for (list<Shape*>::iterator shape = entry->second.begin(); shape != last; ++shape) {
            if (reshape(*shape, cell) )
                return;
        }
        entry->second.push_back(cell);
    }

    virtual void reshape(CellBox<P>* parent)
    {
        if ( parent == this) return;
        for (map<Colour, list<Shape*> >::iterator entry = helper_.begin(); entry != helper_.end(); ++entry) {                    
                    for (list<Shape*>::iterator shape = entry->second.begin(); shape != entry->second.end(); ++shape) {                    
                            parent->reshape(*shape);                    
                }
        }
        helper_.clear();
    }

    virtual void split();
    
    virtual Shape* shape(const Colour& colour)
    {
        if (shape_->empty())
        {
            Cell<P>* cell;
            
            // bottom 
            for (int column = column1_; column <= column2_; column++) {
                cell = (*parent_)(row1_, column);
                shape_->push_back(cell->column(0), cell->row(0));
            }

            // right
            for (int row = row1_; row <= row2_; row++) {
                cell = (*parent_)(row, column2_);
                shape_->push_back(cell->column(1), cell->row(1));
            }

            for (int column = column2_; column >= column1_; column--) {
                cell = (*parent_)(row2_, column);
                shape_->push_back(cell->column(2), cell->row(2));
            }

            for (int row = row2_; row >= row1_; row--) {
                cell = (*parent_)(row, column1_);    
                shape_->push_back(cell->column(3), cell->row(3));
            }
        }
        shape_->colour_= colour;
        return shape_;
    }
    
    void shade(const IsoPlot<P>& owner);
    
    void shade(const IsoPlot<P>& owner, CellBox* parent) {
        RangeType def = this->range();
        switch (def)
        {
          case outOfRange : 
                break;

          case singleRange: 
                this->shape_->value_ = this->value();
                reshape(this->shape(owner.colour(this->value())));        
                reshape(parent);
                break;

          default:
                split();        
                if (this->empty()) { 
                    assert( row1_ == row2_);
                    assert( column1_ == column2_);
                    owner.isoline(*(*parent_)(row1_, column1_), this);
                }
                else {
                    for (typename CellBox<P>::iterator cell = this->begin(); cell != this->end(); ++cell) 
                        (*cell)->shade(owner, this);
                }
                reshape(parent);
        }
    }
    
    void contour(const IsoPlot<P>& owner)
    {
           for (int row = row1_; row <= row2_; row++) {
                        for (int column = column1_; column <= column2_; column++) {                                                    
                                    owner.isoline(*(*parent_)(row, column));
                        }
            }            
    }

    void feed(IsoPlot<P>& owner, BasicGraphicsObjectContainer& out)
    {
        PolylineSet* lines = new PolylineSet();
        lines->name("PolylineSet");
        out.push_back(lines);
        
        for ( map<Colour, list<Shape*> >::iterator colour = helper_.begin(); colour!= helper_.end(); ++colour) {

            reduce(colour->second);
            
            for (list<Shape*>::iterator cells = colour->second.begin(); cells != colour->second.end(); ++cells) {
                    if ( (*cells)->size() < 3 ) {
                        (*cells)->clear();
                        continue;
                    }
                   
                    vector<PaperPoint> helper1;
                    stack<PaperPoint> helper;
                    double value = (*cells)->value_;

                   PaperPoint p1, p2, p3;
                   Shape::const_iterator cell = (*cells)->begin();
                   helper1.push_back(PaperPoint(cell->p1x_, cell->p1y_, value));
                   while ( cell != (*cells)->end() ) {
                       p3 = PaperPoint(cell->p1x_, cell->p1y_, value);
                       if ( !(p3 == helper1.back() ) ) {
                           helper1.push_back(p3);
                       }
                       cell++;
                   }

                   vector<PaperPoint>::const_iterator point =  helper1.begin();
                   for (point = helper1.begin();  point != helper1.end(); ++point) {


                       if ( helper.empty() ) {
                          helper.push(*point);

                          continue;
                      }

                       p2 =  helper.top();
                       helper.pop();

                       if ( helper.empty() ) {
                           helper.push(p2);
                           if ( !( p2 == *point) ) {
                               helper.push(*point);
                           }
                       }
                       else if ( !( *point == helper.top()) ) {
                          helper.push(p2);
                          helper.push(*point);
                       }


                   }

                    
                    if ( helper.size() < 3 ) {
                        (*cells)->clear();

                        continue;
                    }
                    Polyline* poly = new Polyline();

                    poly->setFilled(true); 
                    poly->setStroke(false);
                    poly->setColour((*cells)->colour_);
                    poly->setFillColour((*cells)->colour_);
                    
                    
                    while ( !helper.empty() ) {
                        PaperPoint& point = helper.top();
                        point.value_ = value;
                        
                        poly->push_back(helper.top());
                        helper.pop();
                    }
                
                    
                    if ( !poly->empty() ) {
                        owner.getShading()(poly);
                        
                                                  
                          lines->push_back(poly);
                          
                    }
                    else
                        delete poly;

                    (*cells)->clear();

            }

        }
    }
    
    Shape* shape_;
    const CellArray<P>* parent_;
    int row1_;
    int row2_;
    int column1_;
    int column2_;
    map<Colour, list<Shape*> > helper_;
};


}  // end magics namespace


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template <class P>
void CellBox<P>::shade(const IsoPlot<P>& owner) {
        shade(owner, this);                
}


template <class P>
void CellBox<P>::split()
{
    if ( row1_ == row2_ && column1_ ==  column2_ ) 
            return;

    const int row    = (row2_   + row1_) /2;
    const int column = (column2_+ column1_)/2;

    if (row2_- row1_ > 1&& column2_- column1_ > 1)
    {
       //try first 2 split in columns ...
       CellBox<P>* cell = new CellBox(parent_, row1_, row2_, column1_, column);
       RangeType def = cell->range();
            
       if ( def != multipleRange ) {
             push_back(cell);
       }
       else {
             delete cell;
             push_back(new CellBox(parent_, row1_, row, column1_, column));
             push_back(new CellBox(parent_, row+1, row2_, column1_, column));
       }
       cell = new CellBox(parent_, row1_, row2_, column+1, column2_);
       def = cell->range();

       if ( def != multipleRange ) {
             push_back(cell);
       }
       else {
             delete cell;
             push_back(new CellBox(parent_, row1_, row, column+1, column2_));
             push_back(new CellBox(parent_, row+1, row2_, column+1, column2_));
       }
       return;
    }

    if (row2_- row1_ > 0) {
       push_back(new CellBox(parent_, row1_, row, column1_, column2_));
       push_back(new CellBox(parent_, row+1, row2_, column1_, column2_));
       return;
    }

    if (column2_- column1_ > 0) {
       push_back(new CellBox(parent_, row1_, row2_, column1_, column));
       push_back(new CellBox(parent_, row1_, row2_, column+1, column2_));
       return;
    }
}




template <class P>
struct IsoProducerData {
public:
        IsoProducerData(bool shading, IsoPlot<P>& parent, CellBox<P>& cell):
              shading_(shading), parent_(parent), cell_(cell) {
           more_ = true;
        }  
        bool shading_;
        IsoPlot<P>& parent_;
        CellBox<P>& cell_;   
        bool more_;
        MutexCond cond_;
};

template <class P>
class IsoProducer: public Thread {

public:    
    IsoProducer(int n, IsoProducerData<P>& data) : n_(n), objects_(data) {}
    void run()
    {  Timer timer("cell", "shading");
        ( objects_.shading_ ) ? objects_.cell_.shade(objects_.parent_) : objects_.cell_.contour(objects_.parent_);
    }
    virtual ~IsoProducer() {}
   
protected:
     //! Method to print string about this class on to a stream of type ostream (virtual).
    virtual void print(ostream&) const {}
    int n_;
    IsoProducerData<P>&  objects_;

private:
    //! Copy constructor - No copy allowed
    IsoProducer(const IsoProducer&);
    //! Overloaded << operator to copy - No copy allowed
    IsoProducer& operator=(const IsoProducer&);

    // -- Friends
    //! Overloaded << operator to call print().
    friend ostream& operator<<(ostream& s,const IsoProducer& p)
        { p.print(s); return s; }
};



template<class P> 
void IsoPlot<P>::isoline(Cell<P>& cell, CellBox<P>* box) const
{
   static int cases[3][3][3] = { { { 0, 1, 2 },   { 3, 4, 5 },    { 6, 7, 8 } }, 
                               { { 9, 10, 11 }, { 12, 13, 14 }, { 15, 16, 17 } }, 
                               { { 18, 19, 20 },{ 21, 22, 23 }, { 24, 25, 0 } } };
   int p1, p2, p3;
   int current;
   double x1=0, x2=0, y1=0, y2=0;

   RangeType def = cell.range();

   if ( def == outOfRange )
        return;
   if ( def == singleRange )
   {
        if ( !box  ) // NO Shading == Nothing to do! 
            return;
        else {
            const double contour = levels_[cell.min_];
            Shape* x = new Shape();
            for (int i = 0; i < 4; i++) {
                    x->push_back(cell.column(i), cell.row(i));            
            }
            x->value_  = (cell.value(0) > contour) ? this->shading_->rightRange(contour)  : this->shading_->leftRange(contour);
            x->colour_ = (cell.value(0) > contour) ? this->shading_->rightColour(contour) : this->shading_->leftColour(contour);
            box->reshape(x);
            return;
        }
   }

   for (int p=0; p<2; p++)
   {
        p1 = p;
        p2 = p1+1;
        p3 = 3;

        //int count = 0;
        if (cell.missing(p1)) continue;
        if (cell.missing(p2)) continue;
        if (cell.missing(p3)) continue;

        vector<pair<Shape*, Shape*> > shapes;
        
        // First, build the list of isolines in this triangle...
        vector<int> levels;        

        for (int l = cell.min_; l != cell.max_; ++l) {
            const double contour = levels_[l];
            const int    out     = cases[cell.coef(p1, contour)][cell.coef(p2, contour)][cell.coef(p3, contour)];
            if ( out != 0 ) {
                levels.push_back(l);
            }
        }
        
        if ( levels.empty() && box ) {
            Shape* shape = new Shape(this->shading_->colour(cell.value(p1)), cell.value(p1));
            shape->push_back(cell.column(p1), cell.row(p1));
            shape->push_back(cell.column(p2), cell.row(p2));
            shape->push_back(cell.column(p3), cell.row(p3));
            
            box->reshape(shape);
    }

    bool complex = ( levels.size() > 1);
    for (vector<int>::const_iterator l = levels.begin(); l != levels.end(); ++l)
    {
        int level = *l;                
        // First make a quich check to see if there is at l
        
        const double contour=levels_[level];
        Colour leftcolour  = ( box ) ? this->shading_->leftColour(contour)  : Colour("none");
        Colour rightcolour = ( box ) ? this->shading_->rightColour(contour) : Colour("none");

        current = cases[cell.coef(p1, contour)][cell.coef(p2, contour)][cell.coef(p3, contour)];

        int add = 2;
        Shape* leftcell  = (box) ? new Shape(leftcolour, this->shading_->leftRange(contour))  :0;
        Shape* rightcell = (box) ? new Shape(rightcolour,this->shading_->rightRange(contour)) :0;
        

        switch (current)
        {
        //-------------------------------------------------------------------------------------------------------
        //     Case 0 -point out!
        //-------------------------------------------------------------------------------------------------------
        case 0:
            add = 0;
            break;
            if ( !box ) break;
            
            if (cell.height(p1, contour) < 0) {
                leftcell->push_back(cell.column(p1), cell.row(p1));
                leftcell->push_back(cell.column(p2), cell.row(p2));
                leftcell->push_back(cell.column(p3), cell.row(p3));
            }
            else {
                rightcell->push_back(cell.column(p1), cell.row(p1));
                rightcell->push_back(cell.column(p2), cell.row(p2));
                rightcell->push_back(cell.column(p3), cell.row(p3));
            }
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 1 -Single point 3
        //-------------------------------------------------------------------------------------------------------
        case 1:
            x1=cell.column(p3);
            y1=cell.row(p3);
            add = 1;
            if ( !box) break;
            if (cell.height(p1, contour) < 0) {
                leftcell->push_back(cell.column(p1), cell.row(p1));
                leftcell->push_back(cell.column(p2), cell.row(p2));
                leftcell->push_back(cell.column(p3), cell.row(p3));
            }
            else {
                rightcell->push_back(cell.column(p1), cell.row(p1));
                rightcell->push_back(cell.column(p2), cell.row(p2));
                rightcell->push_back(cell.column(p3), cell.row(p3));
            }
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 2 - Line between sides 2-3 and 3-1
        //-------------------------------------------------------------------------------------------------------
        case 2:
            cell.xysect(p2,p3, contour, x1, y1);
            cell.xysect(p3,p1, contour, x2, y2);
        
            if ( !box ) break;
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(cell.column(p2), cell.row(p2));
            
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p3), cell.row(p3));
            rightcell->push_back(x2, y2);
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 3 -Single point 2
        //-------------------------------------------------------------------------------------------------------
        case 3:
            x1=cell.column(p2);
            y1=cell.row(p2);
            add = 1;
            if ( !box) break;
            
            if (cell.height(p1, contour) < 0) {
                  leftcell->push_back(cell.column(p1), cell.row(p1));
                  leftcell->push_back(cell.column(p2), cell.row(p2));
                  leftcell->push_back(cell.column(p3), cell.row(p3));
            }
            else {
                  rightcell->push_back(cell.column(p1), cell.row(p1));
                  rightcell->push_back(cell.column(p2), cell.row(p2));
                  rightcell->push_back(cell.column(p3), cell.row(p3));
            }
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 4 - Line between vertex 2 and vertex 3
        //-------------------------------------------------------------------------------------------------------
        case 4:
            
            x1=cell.column(p2);
            y1=cell.row(p2);
            x2=cell.column(p3);
            y2=cell.row(p3);
            if ( !box ) break;
            
            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(cell.column(p2), cell.row(p2));
            leftcell->push_back(cell.column(p3), cell.row(p3));
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 5 - Line between vertex 2 and side 3-1
        //-------------------------------------------------------------------------------------------------------
        case 5:            
            x1=cell.column(p2);
            y1=cell.row(p2);
            
            cell.xysect(p3,p1, contour,x2, y2);
            if ( !box ) break;
            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p3), cell.row(p3));
            rightcell->push_back(x2, y2);   
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 6 - Line between side 1-2  and side 2.3
        //-------------------------------------------------------------------------------------------------------
        case 6:
            
            cell.xysect(p1,p2, contour, x1, y1);
            cell.xysect(p2,p3, contour, x2, y2);
    
            if ( !box ) break;
            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            leftcell->push_back(cell.column(p3), cell.row(p3));
            
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p2), cell.row(p2));
            rightcell->push_back(x2, y2);
            
            break;
        //-------------------------------------------------------------------------------------------------------
        //     Case 7 - Line between sides 1-2 aanve vertex 3
        //-------------------------------------------------------------------------------------------------------
        case 7:
            cell.xysect(p1,p2, contour, x1, y1);
            x2=cell.column(p3);
            y2=cell.row(p3);
            
            if ( !box ) break;
            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);

            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p2), cell.row(p2));
            rightcell->push_back(x2, y2);
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 8 - Line between sides 1-2 and 3-1
        //-------------------------------------------------------------------------------------------------------
        case 8:            
            cell.xysect(p1,p2, contour, x1, y1);
            cell.xysect(p3,p1, contour, x2, y2);
            
            if ( !box ) break;
            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);

            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p2), cell.row(p2));
            rightcell->push_back(cell.column(p3), cell.row(p3));
            rightcell->push_back(x2, y2);            
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 9 -single point 1
        //-------------------------------------------------------------------------------------------------------
        case 9:            
            x1=cell.column(p1);
            y1=cell.row(p1);
            add = 1;
            if ( !box) break;
            if (cell.height(p2, contour) < 0) {
                   leftcell->push_back(cell.column(p1), cell.row(p1));
                   leftcell->push_back(cell.column(p2), cell.row(p2));
                   leftcell->push_back(cell.column(p3), cell.row(p3));
            }
            else {
                   rightcell->push_back(cell.column(p1), cell.row(p1));
                   rightcell->push_back(cell.column(p2), cell.row(p2));
                   rightcell->push_back(cell.column(p3), cell.row(p3));
            }
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 10 - Line between vertex 3 and vertex 1
        //-------------------------------------------------------------------------------------------------------
        case 10:
            
            x1=cell.column(p3);
            y1=cell.row(p3);
            x2=cell.column(p1);
            y2=cell.row(p1);
            if ( !box) break;

            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(cell.column(p2), cell.row(p2));
            leftcell->push_back(cell.column(p3), cell.row(p3));
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 11 - Line between side 3-2 and vertex 1
        //-------------------------------------------------------------------------------------------------------
        case 11:
            cell.xysect(p3,p2, contour, x1, y1);
            x2=cell.column(p1);
            y2=cell.row(p1);
            if ( !box ) break;
            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(cell.column(p2), cell.row(p2));
            leftcell->push_back(x1, y1);
            
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p3), cell.row(p3));
            rightcell->push_back(x2, y2);
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 12 - Line between vertex 1 and vertex 2
        //-------------------------------------------------------------------------------------------------------
        case 12:
            x1=cell.column(p1);
            y1=cell.row(p1);
            x2=cell.column(p2);
            y2=cell.row(p2);
            if ( !box ) break;
        
            leftcell->push_back(cell.column(p1), cell.row(p1));
            leftcell->push_back(cell.column(p2), cell.row(p2));
            leftcell->push_back(cell.column(p3), cell.row(p3));
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 13 - Flat Area all vertex have the isoline value!
        //-------------------------------------------------------------------------------------------------------
        case 13:
            add = 0;
            if ( !box ) break;
            if (cell.height(p1, contour) < 0) {
                   leftcell->push_back(cell.column(p1), cell.row(p1));
                   leftcell->push_back(cell.column(p2), cell.row(p2));
                   leftcell->push_back(cell.column(p3), cell.row(p3));
            }
            else {
                   rightcell->push_back(cell.column(p1), cell.row(p1));
                   rightcell->push_back(cell.column(p2), cell.row(p2));
                   rightcell->push_back(cell.column(p3), cell.row(p3));
            }
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 14 - Line between vertex 2 and vertex 1
        //-------------------------------------------------------------------------------------------------------
        case 14:
            x1=cell.column(p2);
            y1=cell.row(p2);
            x2=cell.column(p1);
            y2=cell.row(p1);
            if ( !box ) break;
            
            rightcell->push_back(cell.column(p1), cell.row(p1));
            rightcell->push_back(cell.column(p2), cell.row(p2));
            rightcell->push_back(cell.column(p3), cell.row(p3));
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 15 - Line between vertex 1 and side3-2
        //-------------------------------------------------------------------------------------------------------
        case 15:
            x1=cell.column(p1);
            y1=cell.row(p1);
            cell.xysect(p3,p2, contour, x2, y2);
            if ( !box ) break;

            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            leftcell->push_back(cell.column(p3), cell.row(p3));
            
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p2), cell.row(p2));
            rightcell->push_back(x2, y2);
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 16 - Line between vertex 1 and vertex 3
        //-------------------------------------------------------------------------------------------------------
        case 16:
            x1=cell.column(p1);
            y1=cell.row(p1);
            x2=cell.column(p3);
            y2=cell.row(p3);
            if ( !box ) break;
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p2), cell.row(p2));
            rightcell->push_back(x2, y2);
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 17 - single point 1
        //-------------------------------------------------------------------------------------------------------
        case 17:
            x1=cell.column(p1);
            y1=cell.row(p1);
            add = 1;
            if ( !box ) break;
            if (cell.height(p2, contour) < 0) {
                      leftcell->push_back(cell.column(p1), cell.row(p1));
                      leftcell->push_back(cell.column(p2), cell.row(p2));
                      leftcell->push_back(cell.column(p3), cell.row(p3));
            }
            else {
                      rightcell->push_back(cell.column(p1), cell.row(p1));
                      rightcell->push_back(cell.column(p2), cell.row(p2));
                      rightcell->push_back(cell.column(p3), cell.row(p3));
            }
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 18 - Line between side3-1 and side1-2
        //-------------------------------------------------------------------------------------------------------
        case 18:
            cell.xysect(p3,p1, contour, x1, y1);
            cell.xysect(p1,p2, contour, x2, y2);
            
            if ( !box ) break;
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            leftcell->push_back(cell.column(p2), cell.row(p2));
            leftcell->push_back(cell.column(p3), cell.row(p3));
        
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p1), cell.row(p1));
            rightcell->push_back(x2, y2);
        
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 19 - Line between vertex 3 and side1-2
        //-------------------------------------------------------------------------------------------------------
        case 19:
            x1=cell.column(p3);
            y1=cell.row(p3);
        
            cell.xysect(p1,p2, contour, x2, y2);
            
            if ( !box ) break;
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            leftcell->push_back(cell.column(p2), cell.row(p2));
        
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p1), cell.row(p1));
            rightcell->push_back(x2, y2);
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 20 - Line between side3-2 and side1-2
        //-------------------------------------------------------------------------------------------------------
        case 20:
            cell.xysect(p3,p2, contour, x1, y1);
            cell.xysect(p1,p2, contour, x2, y2);
            
            if ( !box ) break;
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            leftcell->push_back(cell.column(p2), cell.row(p2));
        
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p3), cell.row(p3));
            rightcell->push_back(cell.column(p1), cell.row(p1));
            rightcell->push_back(x2, y2);
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 21 - Line between side 3-1 and vertex 2 
        //-------------------------------------------------------------------------------------------------------
        case 21:
            cell.xysect(p3,p1, contour, x1, y1);
            
            x2=cell.column(p2);
            y2=cell.row(p2);
            if ( !box ) break;
            
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            leftcell->push_back(cell.column(p3), cell.row(p3));
                
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p1), cell.row(p1));
            rightcell->push_back(cell.column(p2), cell.row(p2));
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 22 - Line between vertex 3 and vertex 2
        //-------------------------------------------------------------------------------------------------------
        case 22:
            x1=cell.column(p3);
            y1=cell.row(p3);
            x2=cell.column(p2);
            y2=cell.row(p2);
            if ( !box) break;
            rightcell->push_back(cell.column(p1), cell.row(p1));
            rightcell->push_back(cell.column(p2), cell.row(p2));
            rightcell->push_back(cell.column(p3), cell.row(p3));
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 23 - single point 2
        //-------------------------------------------------------------------------------------------------------
        case 23:
            x1=cell.column(p2);
            y1=cell.row(p2);
            add = 1;
            if ( !box) break;
            if (cell.height(p1, contour) < 0) {
                     leftcell->push_back(cell.column(p1), cell.row(p1));
                     leftcell->push_back(cell.column(p2), cell.row(p2));
                     leftcell->push_back(cell.column(p3), cell.row(p3));
            }
            else {
                     rightcell->push_back(cell.column(p1), cell.row(p1));
                     rightcell->push_back(cell.column(p2), cell.row(p2));
                     rightcell->push_back(cell.column(p3), cell.row(p3));
            }
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 24 - Line between side1-3  and side3-2
        //-------------------------------------------------------------------------------------------------------
        case 24:
            cell.xysect(p1,p3, contour, x1, y1);
            cell.xysect(p3,p2, contour, x2, y2);
            if ( !box) break;
            leftcell->push_back(x1, y1);
            leftcell->push_back(x2, y2);
            leftcell->push_back(cell.column(p3), cell.row(p3));
            
            rightcell->push_back(x1, y1);
            rightcell->push_back(cell.column(p1), cell.row(p1));
            rightcell->push_back(cell.column(p2), cell.row(p2));
            rightcell->push_back(x2, y2);
            break;

        //-------------------------------------------------------------------------------------------------------
        //     Case 25 - single point C
        //-------------------------------------------------------------------------------------------------------
        case 25:
            x1=cell.column(p3);
            y1=cell.row(p3);
            add = 1;
            if ( !box) break;
            if (cell.height(p1, contour) < 0) {
                     leftcell->push_back(cell.column(p1), cell.row(p1));
                     leftcell->push_back(cell.column(p2), cell.row(p2));
                     leftcell->push_back(cell.column(p3), cell.row(p3));
            }
            else {
                     rightcell->push_back(cell.column(p1), cell.row(p1));
                     rightcell->push_back(cell.column(p2), cell.row(p2));
                     rightcell->push_back(cell.column(p3), cell.row(p3));
            }
            break;
            
        default:
            break;
        }
        
        if (add == 2 && needIsolines() )
        {
            // here we have 2 Points to add! 
            // We send it to a thread! 
            const int t = (*l) % threads_;
            {     
              AutoLock<MutexCond> lockproducer(producerMutex_);
              {
                 AutoLock<MutexCond> lock(segments_[t]->cond_);
                 segments_[t]->segments_.push_back(make_pair(levels_[*l], 
                                                   make_pair(make_pair(x1, y1), make_pair(x2, y2))));
                 if ( segments_[t]->segments_.size() >= 2000 ) 
                       segments_[t]->cond_.signal();
                 }
                 producerMutex_.signal();
              }
            }

            if ( !box) {
                continue;
            }
            
            if ( !complex ) {
                box->reshape(leftcell);
                box->reshape(rightcell);
            }
            else {
                shapes.push_back(make_pair(leftcell, rightcell));
            }
        } // end of levels...
        
        if ( shapes.empty() ) continue;
        
        // Now we reduce the shape! 
        vector<pair<Shape*, Shape*> >::iterator current = shapes.begin();    
        vector<pair<Shape*, Shape*> >::iterator next = shapes.begin();
        box->reshape(current->first);
        next++;
        while ( next != shapes.end() ) {
            current->second->intersection(*next->second);
            box->reshape(current->second);
            current++;
            next++ ;
        }
        box->reshape(current->second);
    } // step to next triangle
}



template<class P> 
void IsoPlot<P>::isoline(MatrixHandler<P>& data, BasicGraphicsObjectContainer& parent)
{
    const Transformation& transformation = parent.transformation();
    levels_.clear();

    // Find the used levels!
    const vector<double>::const_iterator end = (*this->levelSelection_).end();
    vector<double>::const_iterator previous = (*this->levelSelection_).end();
    vector<double>::const_iterator last = (*this->levelSelection_).end();
        
    if ( (*this->levelSelection_).front()  > data.min() )
            levels_.push_back(data.min());
        
    for (vector<double>::const_iterator l = (*this->levelSelection_).begin(); l != end; ++l)
    {
       if ( (*l <= data.min()) ) {
           previous = l;
           continue;
       }
       if ( (*l >= data.max()) ) {
           last = l;
           continue;
       }
       if ( previous != (*this->levelSelection_).end() ) {
           levels_.push_back(*previous);
           previous = (*this->levelSelection_).end();
       }
       levels_.push_back(*l);
    }
    
    if ( last != (*this->levelSelection_).end())
       levels_.push_back(*last);
    else 
       levels_.push_back(data.max());

    missing_ = data.missing();


    IntervalMap<int> range;
    int r= 1;
    for (vector<double>::const_iterator level = levels_.begin(); level != levels_.end(); ++level) {

       if (level+1!= levels_.end() )
          range.insert(make_pair(Interval(*level, *(level+1)), r++));
       }

       CellArray<P> array(data, range, transformation, parent.widthResolution(), parent.heightResolution(), 
	   		this->resolution_, this->technique_);
       CellBox<P> view(&array);

       threads_ = (needIsolines())  ? 4: 0;

       vector<IsoHelper*> consumers_;
       vector<IsoProducer<P>* >  producers_;

       {
        VectorOfPointers<vector<ThreadControler *>  > consumers;
        VectorOfPointers<vector<ThreadControler *>  > producers;
        segments_.clear();
        colourShapes_.clear();
        lines_.clear();

        for (int c = 0; c < threads_; c++) {
            vector<Polyline* >* lines = new vector<Polyline*>();
            lines_.push_back(lines);    
            segments_.push_back(new IsoData()); 
            consumers_.push_back(new IsoHelper(c, *lines,*(segments_.back())));
            consumers.push_back(new ThreadControler(consumers_.back(), false));
            consumers.back()->start();
        }
        
        view.split();

        // let's start 4 producers...
        int c = 0;
        for (typename CellBox<P>::iterator cell = view.begin(); cell != view.end(); ++cell) 
        {
           IsoProducerData<P>* data = new IsoProducerData<P>(this->shading_->shadingMode(), *this, **cell);
           producers_.push_back(new IsoProducer<P>(c, *data));
           producers.push_back(new ThreadControler(producers_.back(), false));
           producers.back()->start();
           c++;
        }

        for (vector<ThreadControler *>::iterator producer = producers.begin(); 
           producer != producers.end(); ++producer) {
           (*producer)->wait();
        }

        // No more
        { 
           for (int i = 0; i < threads_; i++) {
                 AutoLock<MutexCond> lock(segments_[i]->cond_);
                 segments_[i]->more_ = false;
                 segments_[i]->cond_.signal();
           }     
        }

        for (vector<ThreadControler *>::iterator consumer = consumers.begin(); consumer != consumers.end(); ++consumer) {
             (*consumer)->wait();
        }
       }

       for (typename CellBox<P>::iterator cell = view.begin(); cell != view.end(); ++cell)
           (*cell)->feed(*this,parent);        

}


template<class P> bool IsoPlot<P>::prepare(MatrixHandler<P>& data)
{
    (*this->levelSelection_).clear();
    (*this->levelSelection_).calculate(data.min() , data.max() , this->shading_->shadingMode());
    (*this->label_).prepare(*this->levelSelection_, (*this->colour_).name());
    return (*this->shading_)(*this->levelSelection_);
}


/*!
 * Get the triangles list ...
 * Create the isolines...
 */
template<class P> void IsoPlot<P>::operator()(MatrixHandler<P>& data, BasicGraphicsObjectContainer& parent)
{
    prepare(data);
    if ( this->legend_only_ ) return;
    
    {
        Timer timer("contouring", "Time spent in contouring");
        isoline(data, parent);
    }

#ifdef ISOPLOT_DEBUG
    vector<Colour> colours;    
    colours.push_back(Colour("red"));
    colours.push_back(Colour("green"));
    colours.push_back(Colour("blue"));
    colours.push_back(Colour("orange"));
    vector<Colour>::iterator colour = colours.begin();
#endif

    (*this->shading_)(data, parent);
    (*this->highlight_).prepare(*this->levelSelection_);

    // Now we feed the task...   
    for (typename vector<vector<Polyline* >* >::const_iterator lines = this->lines_.begin(); lines != this->lines_.end(); ++lines)
    {
      for (typename vector<Polyline* >::const_iterator poly = (*lines)->begin(); poly != (*lines)->end(); ++poly)
      {
        if ( (*poly)->empty() ) continue;
        (*poly)->setLineStyle(this->style_);
        (*poly)->setThickness(this->thickness_);
        (*poly)->setColour(*this->colour_);
#ifdef ISOPLOT_DEBUG
        (*poly)->setColour(*colour);
        colour++;
        if ( colour == colours.end()) 
            colour = colours.begin();
#endif
        (*this->highlight_)(*(*poly));
        (*this->label_)(**poly, (*poly)->front().value());

        parent.push_back(*poly);
      }
    }
    this->lines_.clear();
}


template<class P> void NoIsoPlot<P>::operator()(MatrixHandler<P>& data, BasicGraphicsObjectContainer& parent)
{
    // Create the isolines...        
    if ( !prepare(data) ) {    
        if ( this->legend_only_ ) return;
        (*this->shading_)(data, parent);
        // do not send the isolines...  
        return;
    }
    if ( this->legend_only_ ) return;
    // The shading needs the isolines..
    // WE will calculate them but will not send them to the printer     
    {
        Timer timer("contouring", "Time spent in contouring");
        isoline(data, parent);
    }

    (*this->shading_)(data, parent);

    // Now we feed the task...
        for (typename vector<vector<Polyline* >* >::const_iterator lines = this->lines_.begin(); lines != this->lines_.end(); ++lines)
        {
          for (typename vector<Polyline* >::const_iterator poly = (*lines)->begin(); poly != (*lines)->end(); ++poly)
          {
            if ( (*poly)->empty() ) continue;
            (*poly)->setThickness(0);
            (*this->label_)(**poly, (*poly)->front().value());
            parent.push_back(*poly);
          }
        }
        this->lines_.clear();
}


template<class P>
void IsoPlot<P>::visit(Data<P>& data, LegendVisitor& legend) {
	switch (legend.legendType())  {
		case LegendMethod::CONTINUOUS :
		case LegendMethod::DISJOINT: {
			(*this->shading_).visit(legend);
			if (this->shading_->shadingMode() ) return;

			Polyline* line1 = new Polyline();
			Polyline* line2 = 0;
			line1->setColour(*this->colour_);
			line1->setLineStyle(this->style_);
			line1->setThickness(this->thickness_);
    
			this->highlight_->visit(line2);
			legend.add(new DoubleLineEntry(this->legend_text_, line1, line2));
			// Should do something for the wrep legend!!!
			if ( legend.size() < 3 && legend.getWrep() ) {
				legend.add(new EmptyEntry());
			}
			break;
		}
		case LegendMethod::HISTOGRAM: {
			legend.newLegend();
			IntervalMap<Colour> beans;
			vector<double>::iterator from = this->levelSelection_->begin();
			vector<double>::iterator to = this->levelSelection_->begin();
			++to;
			for (;  to != this->levelSelection_->end(); ++to){
				Colour colour = *this->colour_;
				this->shading_->colour(*from, colour);
				beans.insert(make_pair(Interval(*from, *to), colour ));
				++from;
			}
			Histogram<P> helper;
			IntervalMap<int>& histogram = helper.histogram(beans, data.points());
			int total = 0;
			for (IntervalMap<int>::const_iterator  interval = histogram.begin(); interval != histogram.end(); ++interval){
				total+=interval->second;
			}
			bool first = true;
			for ( IntervalMap<Colour>::const_iterator interval = beans.begin(); interval != beans.end(); ++interval) {
				   Polyline* box = new Polyline();

				   double min =interval->first.min_;
				   double max = interval->first.max_;

				   box->setShading(new FillShadingProperties());
				   box->setFillColour(interval->second);
				   box->setFilled(true);
				   BoxEntry* entry = new BoxEntry(min, max, box);
				   int count = histogram.find(min, 0);
				   entry->population(count);
				   entry->totalPopulation(total);
				   if (first) {
					   entry->first();
					   first = false;
				   }


				   legend.add(entry);

			}

			legend.last();
		}
	}
    
}


template<class P> void NoIsoPlot<P>::visit(Data<P>& data, LegendVisitor& legend) {

    switch (legend.legendType())  {
    		case LegendMethod::CONTINUOUS :
    		case LegendMethod::DISJOINT: {
    			(*this->shading_).visit(legend);
    			break;
    		}
    		case LegendMethod::HISTOGRAM: {
    			legend.newLegend();
    			IntervalMap<Colour> beans;
    			vector<double>::iterator from = this->levelSelection_->begin();
    			vector<double>::iterator to = this->levelSelection_->begin();
    			++to;
    			for (;  to != this->levelSelection_->end(); ++to){
    				Colour colour = *this->colour_;
    				this->shading_->colour(*from, colour);
    				beans.insert(make_pair(Interval(*from, *to), colour ));
    				++from;
    			}
    			Histogram<P> helper;
    			IntervalMap<int>& histogram = helper.histogram(beans, data.points());
    			int total = 0;
    			for (IntervalMap<int>::const_iterator  interval = histogram.begin(); interval != histogram.end(); ++interval){
    				total+=interval->second;
    			}
    			bool first = true;
    			for ( IntervalMap<Colour>::const_iterator interval = beans.begin(); interval != beans.end(); ++interval) {
    				   Polyline* box = new Polyline();

    				   double min =interval->first.min_;
    				   double max = interval->first.max_;

    				   box->setShading(new FillShadingProperties());
    				   box->setFillColour(interval->second);
    				   box->setFilled(true);
    				   BoxEntry* entry = new BoxEntry(min, max, box);
    				   int count = histogram.find(min, 0);
    				   entry->population(count);
    				   entry->totalPopulation(total);
    				   if (first) {
    					   entry->first();
    					   first = false;
    				   }


    				   legend.add(entry);

    			}

    			legend.last();
    		}
    	}
}


template<class P>
void IsoPlot<P>::visit(Data<P>& data, PointsHandler<P>& points, HistoVisitor& visitor)
{
    IntervalMap<Colour> beans;
    if ( !visitor.basic() ) {

        vector<double>::iterator from = this->levelSelection_->begin();
        vector<double>::iterator to = this->levelSelection_->begin();
        ++to;
        for (;  to != this->levelSelection_->end(); ++to){
            Colour colour = *this->colour_;
            this->shading_->colour(*from, colour);
            beans.insert(make_pair(Interval(*from, *to), colour ));
            ++from;
        }
    }
    Histogram<P> helper;
    helper.visit(beans, data, points, visitor);
}
