#include <limits.h>
#include "BlobOperators.h"

/***************************************************************************
  Implementation of the helper classes to perform operations on blobs
***************************************************************************/

/**
- FUNCTION: moment
- FUNCTIONALITY: Calculates the pq moment of the blob
- PARAMETERS:
- RESULT:
    - returns the pq moment or 0 if the moment it is not implemented
- RESTRICTIONS:
    - Currently, implemented moments up to 3
- AUTHOR: Ricard Borràs
- CREATION DATE: 20-07-2004.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetMoment::operator()(Blob& blob)
{
    return blob.moment(m_p, m_q);
}

/**
- FUNCTION: BlobGetHullPerimeter
- FUNCTIONALITY: Calculates the convex hull perimeter of the blob
- PARAMETERS:
- RESULT:
    - returns the convex hull perimeter of the blob or the perimeter if the
    blob edges could not be retrieved
- RESTRICTIONS:
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetHullPerimeter::operator()(Blob& blob)
{
    Contours convexHull;
    blob.getConvexHull(convexHull);
    double perimeter;

    if (convexHull.size() != 0) {
        perimeter = fabs(arcLength(convexHull[0], true));
    }
    else {
        return 0;
    }
    return perimeter;
}

double BlobGetHullArea::operator()(Blob& blob)
{
    Contours convexHull;
    blob.getConvexHull(convexHull);
    double area;

    if (convexHull.size() == 0) {
        area = fabs(contourArea(convexHull[0], true));
    }
    else {
        return 0;
    }
    return area;
}

/**
- FUNCTION: BlobGetMinXatMinY
- FUNCTIONALITY: Calculates the minimum X on the minimum Y
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetMinXatMinY::operator()(Blob& blob)
{
    double result = static_cast<double>(LONG_MAX);

    //CvSeqReader reader;
    //CvPoint actualPoint;
    PointList externalContour;

    externalContour = blob.getExternalContour()->getContourPoints();
    if (externalContour.size()==0) return result;
    PointList::iterator it = externalContour.begin(), en = externalContour.end();
    for (; it != en; ++it) {
        cv::Point &actualPoint = *it;

        if ((actualPoint.y == blob.minY()) && (actualPoint.x < result)) {
            result = actualPoint.x;
        }
    }

    return result;
}

/**
- FUNCTION: BlobGetMinXatMinY
- FUNCTIONALITY: Calculates the minimum Y on the maximum X
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetMinYatMaxX::operator()(Blob& blob)
{
    double result = static_cast<double>(LONG_MAX);

    //CvSeqReader reader;
    //CvPoint actualPoint;
    PointList externalContour;

    externalContour = blob.getExternalContour()->getContourPoints();
    if (externalContour.size() == 0) { return result; }

    PointList::iterator it = externalContour.begin(), en = externalContour.end();
    for (; it != en; ++it) {
        cv::Point actualPoint = *it;

        if ((actualPoint.x == blob.maxX()) && (actualPoint.y < result)) {
            result = actualPoint.y;
        }
    }

    return result;
}

/**
- FUNCTION: BlobGetMaxXatMaxY
- FUNCTIONALITY: Calculates the maximum X on the maximum Y
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetMaxXatMaxY::operator()(Blob& blob)
{
    double result = LONG_MIN;

    //CvSeqReader reader;
    //CvPoint actualPoint;
    PointList externalContour;

    externalContour = blob.getExternalContour()->getContourPoints();
    if (externalContour.size() == 0) { return result; }

    PointList::iterator it = externalContour.begin(), en = externalContour.end();
    for (; it != en; ++it) {
        cv::Point &actualPoint = *it;

        if ((actualPoint.y == blob.maxY()) && (actualPoint.x > result)) {
            result = actualPoint.x;
        }
    }
    return result;
}

/**
- FUNCTION: BlobGetMaxYatMinX
- FUNCTIONALITY: Calculates the maximum Y on the minimum X
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetMaxYatMinX::operator()(Blob& blob)
{
    double result = LONG_MIN;

    //CvSeqReader reader;
    //CvPoint actualPoint;
    PointList externalContour;

    externalContour = blob.getExternalContour()->getContourPoints();
    if (externalContour.size() == 0) { return result; }


    PointList::iterator it = externalContour.begin(), en = externalContour.end();
    for (; it != en; ++it) {
        cv::Point &actualPoint = *it;

        if ((actualPoint.x == blob.minX()) && (actualPoint.y > result)) {
            result = actualPoint.y;
        }
    }

    return result;
}

/**
- FUNCTION: BlobGetElongation
- FUNCTIONALITY: Calculates the elongation of the blob (length/breadth)
- PARAMETERS:
- RESULT:
- RESTRICTIONS: - See below to see how the lenght and the breadth are aproximated
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetElongation::operator()(Blob& blob)
{
    double widthC, lengthC, width, length;

    double tmp;

    tmp = blob.perimeter()*blob.perimeter() - 16 * blob.area();

    if (tmp > 0.0) {
        widthC = (double) (blob.perimeter() + sqrt(tmp)) / 4;
        // error intrínsec en els càlculs de l'àrea i el perímetre
    }
    else {
        widthC = (double) (blob.perimeter()) / 4;
    }
    if (widthC <= 0.0) { return 0; }
    lengthC = (double) blob.area() / widthC;

    length = MAX(lengthC, widthC);
    width = MIN(lengthC, widthC);

    return (double) length/width;
}

/**
- FUNCTION: BlobGetCompactness
- FUNCTIONALITY: Calculates the compactness of the blob 
                (maximum for circle shaped blobs, minimum for the rest)
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetCompactness::operator()(Blob& blob)
{
    if (blob.area() != 0.0) {
        return (double) pow(blob.perimeter(), 2)/(4 * CV_PI * blob.area());
    }
    else {
        return 0.0;
    }
}

/**
- FUNCTION: BlobGetRoughness
- FUNCTIONALITY: Calculates the roughness of the blob 
                (ratio between perimeter and convex hull perimeter)
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetRoughness::operator()(Blob& blob)
{
    BlobGetHullPerimeter getHullPerimeter = BlobGetHullPerimeter();

    double hullPerimeter = getHullPerimeter(blob);

    if (hullPerimeter != 0.0) {
        return blob.perimeter() / hullPerimeter;//HullPerimeter();
    }
    return 0.0;
}

/**
- FUNCTION: BlobGetLength
- FUNCTIONALITY: Calculates the lenght of the blob (the biggest axis of the blob)
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
    - The lenght is an aproximation to the real lenght
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetLength::operator()(Blob& blob)
{
    double widthC, lengthC;
    double tmp;

    tmp = blob.perimeter() * blob.perimeter() - 16 * blob.area();

    if (tmp > 0.0) {
        widthC = (double) (blob.perimeter() + sqrt(tmp)) / 4;
        // error intrínsec en els càlculs de l'àrea i el perímetre
    }
    else {
        widthC = (double) (blob.perimeter()) / 4;
    }
    if (widthC <= 0.0) { return 0; }
    lengthC = (double) blob.area() / widthC;

    return MAX(lengthC , widthC);
}

/**
- FUNCTION: BlobGetBreadth
- FUNCTIONALITY: Calculates the breadth of the blob (the smallest axis of the blob)
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
    - The breadth is an aproximation to the real breadth
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetBreadth::operator()(Blob& blob)
{
    double widthC, lengthC;
    double tmp;

    tmp = blob.perimeter() * blob.perimeter() - 16 * blob.area();

    if (tmp > 0.0) {
        widthC = (double) (blob.perimeter() + sqrt(tmp)) / 4;
        // error intrínsec en els càlculs de l'àrea i el perímetre
    }
    else {
        widthC = (double) (blob.perimeter()) / 4;
    }
    if (widthC <= 0.0) { return 0; }
    lengthC = (double) blob.area() / widthC;

    return MIN(lengthC , widthC);
}

/**
- FUNCTION: BlobGetDistanceFromPoint
- FUNCTIONALITY: Calculates the euclidean distance between the blob center and 
                 the point specified in the constructor
- PARAMETERS:
- RESULT:
- RESTRICTIONS:
- AUTHOR: Ricard Borràs
- CREATION DATE: 25-05-2005.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetDistanceFromPoint::operator()(Blob& blob)
{
    BlobGetXCenter getXCenter;
    BlobGetYCenter getYCenter;

    double deltaX = m_x - getXCenter(blob);
    double deltaY = m_y - getYCenter(blob);

    return sqrt((deltaX * deltaX) + (deltaY * deltaY));
}

/**
- FUNCTION: BlobGetXYInside
- FUNCTIONALITY: Calculates whether a point is inside the
    rectangular bounding box of a blob
- PARAMETERS:
- RESULT:
    - returns 1 if it is inside; o if not
- RESTRICTIONS:
- AUTHOR: Francesc Pinyol Margalef
- CREATION DATE: 16-01-2006.
- MODIFICATION: Date. Author. Description.
*/
double BlobGetXYInside::operator()(Blob& blob)
{
    const PointList &contourPoints = blob.getExternalContour()->getContourPoints();
    if (contourPoints.size() == 0) {
        return pointPolygonTest(contourPoints, m_p, false) >= 0;
    }

    return 0;
}
#ifdef BLOB_OBJECT_FACTORY

/**
- FUNCTION: RegisterAll Operators
- FUNCTIONALITY: Register all operators defined in blob.h
- PARAMETERS:
- fabricaOperadorsBlob: factory where the operators will be registered
- RESULT:
- Modifies the manufacturesOperatorsBlob object
- RESTRICTIONS:
- Only blob.h operators will be registered. If you want to add them, you need to add them with
the Register method of the factory.
- AUTHOR: trees
- DATE OF CREATION: 2006/05/18
- MODIFICATION: Date. Author. Description.
*/
void RegistraTotsOperadors(t_OperadorBlobFactory &fabricaOperadorsBlob)
{
    // blob shape
    fabricaOperadorsBlob.Register(BlobGetArea().name(), Type2Type<BlobGetArea>());
    fabricaOperadorsBlob.Register(BlobGetBreadth().name(), Type2Type<BlobGetBreadth>());
    fabricaOperadorsBlob.Register(BlobGetCompactness().name(), Type2Type<BlobGetCompactness>());
    fabricaOperadorsBlob.Register(BlobGetElongation().name(), Type2Type<BlobGetElongation>());
    fabricaOperadorsBlob.Register(BlobGetExterior().name(), Type2Type<BlobGetExterior>());
    fabricaOperadorsBlob.Register(BlobGetLength().name(), Type2Type<BlobGetLength>());
    fabricaOperadorsBlob.Register(BlobGetPerimeter().name(), Type2Type<BlobGetPerimeter>());
    fabricaOperadorsBlob.Register(BlobGetRoughness().name(), Type2Type<BlobGetRoughness>());

    // blob color
    fabricaOperadorsBlob.Register(BlobGetMean(NULL).name(), Type2Type<BlobGetMean>());
    fabricaOperadorsBlob.Register(BlobGetStdDev(NULL).name(), Type2Type<BlobGetStdDev>());

    // external pixels
    fabricaOperadorsBlob.Register(BlobGetExternalPerimeterRatio().name(), Type2Type<BlobGetExternalPerimeterRatio>());
    fabricaOperadorsBlob.Register(BlobGetExternalHullPerimeterRatio().name(), Type2Type<BlobGetExternalHullPerimeterRatio>());
    fabricaOperadorsBlob.Register(BlobGetExternalPerimeter().name(), Type2Type<BlobGetExternalPerimeter>());


    // hull
    fabricaOperadorsBlob.Register(BlobGetHullPerimeter().name(), Type2Type<BlobGetHullPerimeter>());
    fabricaOperadorsBlob.Register(BlobGetHullArea().name(), Type2Type<BlobGetHullArea>());


    // elipse info
    fabricaOperadorsBlob.Register(BlobGetMajorAxisLength().name(), Type2Type<BlobGetMajorAxisLength>());
    fabricaOperadorsBlob.Register(BlobGetMinorAxisLength().name(), Type2Type<BlobGetMinorAxisLength>());
    fabricaOperadorsBlob.Register(BlobGetAxisRatio().name(), Type2Type<BlobGetAxisRatio>());
    fabricaOperadorsBlob.Register(BlobGetOrientation().name(), Type2Type<BlobGetOrientation>());
    fabricaOperadorsBlob.Register(BlobGetOrientationCos().name(), Type2Type<BlobGetOrientationCos>());
    fabricaOperadorsBlob.Register(BlobGetAreaElipseRatio().name(), Type2Type<BlobGetAreaElipseRatio>());

    // min an max
    fabricaOperadorsBlob.Register(BlobGetMaxX().name(), Type2Type<BlobGetMaxX>());
    fabricaOperadorsBlob.Register(BlobGetMaxY().name(), Type2Type<BlobGetMaxY>());
    fabricaOperadorsBlob.Register(BlobGetMinX().name(), Type2Type<BlobGetMinX>());
    fabricaOperadorsBlob.Register(BlobGetMinY().name(), Type2Type<BlobGetMinY>());

    fabricaOperadorsBlob.Register(BlobGetMaxXatMaxY().name(), Type2Type<BlobGetMaxXatMaxY>());
    fabricaOperadorsBlob.Register(BlobGetMaxYatMinX().name(), Type2Type<BlobGetMaxYatMinX>());
    fabricaOperadorsBlob.Register(BlobGetMinXatMinY().name(), Type2Type<BlobGetMinXatMinY>());
    fabricaOperadorsBlob.Register(BlobGetMinYatMaxX().name(), Type2Type<BlobGetMinYatMaxX>());

    // coordinate info
    fabricaOperadorsBlob.Register(BlobGetXYInside().name(), Type2Type<BlobGetXYInside>());
    fabricaOperadorsBlob.Register(BlobGetDiffY().name(), Type2Type<BlobGetDiffY>());
    fabricaOperadorsBlob.Register(CBlobGetDiffX().name(), Type2Type<CBlobGetDiffX>());
    fabricaOperadorsBlob.Register(BlobGetXCenter().name(), Type2Type<BlobGetXCenter>());
    fabricaOperadorsBlob.Register(BlobGetYCenter().name(), Type2Type<BlobGetYCenter>());
    fabricaOperadorsBlob.Register(BlobGetDistanceFromPoint().name(), Type2Type<BlobGetDistanceFromPoint>());

    // moments
    fabricaOperadorsBlob.Register(BlobGetMoment().name(), Type2Type<BlobGetMoment>());

}

#endif	//BLOB_OBJECT_FACTORY