/**
 * \file Polygon.cpp
 * \brief a polygon class
 * \author Gapchich Vladislav, Sven Sickert
 * \date 23/10/2011 (07/10/2015)
 */

#include "vislearning/cbaselib/Polygon.h"

using namespace OBJREC;
using namespace std;
using namespace NICE;

//! A constructor
Polygon::Polygon()
{
	points_.clear();
    id_ = -1;
    unique_id_ = -1;
}

// A copy-constructor
Polygon::Polygon(const Polygon &copy)
{
	points_ = PointsList(*(copy.points()));
	id_ = copy.id();
    unique_id_ = copy.unique_id_;
}

//! A desctructor
Polygon::~Polygon()
{
	
}

//! appends aPoint coordinate to the end of the point list 
void 
Polygon::push(const CoordT< int > &aPoint)
{
	if (aPoint.x < 0 || aPoint.y < 0) {
		return;
		/* NOTREACHED */
	}
	
	points_.push_back(aPoint);
}

//! overloaded
/*!
 * \see push(const CoordT< int > &aPoint)
 */
void
Polygon::push(const int &x, const int &y)
{
	if (x < 0 || y < 0) {
		return;
		/* NOTREACHED */
	}

	CoordT< int > point;
	point.x = x;
	point.y = y;
	points_.push_back(point);
}

//! Sets a category ID(label ID) for the polygon
/*!
 * /param[in] anID should not be less than zero 
 */
void 
Polygon::setID(const int &anID)
{
	if (anID < 0) {
		return;
		/* NOTREACHED */
	}
    this->id_ = anID;
}

//! returns a constant pointer to the list of polygon points(coordinates)
const PointsList * 
Polygon::points() const
{
	return &points_;	
}

//! deletes last added point of the polygon and returns it
CoordT< int > 
Polygon::pop()
{
	CoordT< int > ret = points_.back();
	points_.pop_back();
	return ret;
}

//! returns a category ID of the polygon
int 
Polygon::id() const
{
	return id_;
}
	
// check whether point is inside polygon or not
bool
Polygon::handleEdge ( const int px, const int py,
                      const int x1, const int y1,
                      const int x2, const int y2,
                      int & lastdir, int & c )
{
    if (py == y1)
    {
        if (px == x1) return true;

        if (y1 > y2)
        {
            if (lastdir == -1) // decreasing (cont.)
                if (x1 < px) c++;
        }

        if (y1 < y2)
        {
            if (lastdir == 1) // increasing (cont.)
                if (x1 < px) c++;
        }

        if (y1 == y2)
        {
            if ((x1 <= px) && (x2 >= px)) return true;
        }
    }

    if ( (y1 > py && y2 < py) || (y1 < py && y2 > py) )
    {
        int xz = (int)( (py - y1) * (x2 - x1) / (y2 - y1) + x1 );

        /* is point laying on the polygon curve? */
        if (xz == px) return true;

        /* does the scanning line cut the polygon curve left of the point? */
        if (xz < px) c++;
    }

    if (y2 > y1) lastdir = 1;

    if (y2 < y1) lastdir = -1;

    return false;
}


bool
Polygon::insidePolygon ( const int &px, const int &py )
{
    int i, j, c = 0;
    int lastdir = 0;

    if ( points_.size() < 2 )
    {
        cerr << "Polygon::insidePolygon: Not a valid Polygon curve" << endl;
        return false;
    }

    for ( PointsList::const_iterator i = points_.begin();
          i != points_.end(); ++i )
    {
        PointsList::const_iterator j = i;
        j++;

        if ( j == points_.end() ) j = points_.begin();

        CoordT<int> pi = *i;
        CoordT<int> pj = *j;

        if (pj.y > pi.y) lastdir = 1;

        if (pj.y < pi.y) lastdir = -1;
    }

    if (lastdir == 0)
    {
        cerr << "Polygon::insidePolygon: Polygon is degenerated" << endl;
        return false;
    }

    for ( PointsList::const_iterator i = points_.begin();
          i != points_.end(); ++i )
    {
        PointsList::const_iterator j = i;
        j++;

        if ( j == points_.end() ) j = points_.begin();

        CoordT<int> pi = *i;
        CoordT<int> pj = *j;

        if ( handleEdge( px, py, pi.x, pi.y, pj.x, pj.y, lastdir, c ) )
            return false;
      }

    if (c & 1) return true;

    return false;
}

bool
Polygon::insidePolygon ( const CoordT< int > &aPoint )
{
    if (aPoint.x < 0 || aPoint.y < 0) {
        cerr << "Polygon::insidePolygon(): point does not have valid coordinates"
             << endl;
        return false;
    }

    return insidePolygon ( aPoint.x, aPoint.y );
}
