/**
 * @file trigo.cpp
 * @brief Trigonometric functions.
 */

#include "fctsys.h"
#include "macros.h"
#include "trigo.h"


bool TestSegmentHit( wxPoint aRefPoint, wxPoint aStart, wxPoint aEnd, int aDist )
{
    // make coordinates relatives to aStart:
    aEnd -= aStart;
    aRefPoint -= aStart;
    return DistanceTest( aDist, aEnd.x, aEnd.y, aRefPoint.x, aRefPoint.y );
}


bool DistanceTest( int seuil, int dx, int dy, int spot_cX, int spot_cY )
{
    /*  We can have 4 cases::
     *      horizontal segment
     *      vertical segment
     *      45 degrees segment
     *      other slopes
     */
    int cXrot, cYrot, segX, segY;
    int pointX, pointY;

    segX   = dx;
    segY   = dy;
    pointX = spot_cX;
    pointY = spot_cY;

    /* Recalculating coord for the segment is in 1st quadrant (coord >= 0) */
    if( segX < 0 )   /* set > 0 by symmetry about the Y axis */
    {
        segX   = -segX;
        pointX = -pointX;
    }

    if( segY < 0 )   /* set > 0 by symmetry about the X axis */
    {
        segY   = -segY;
        pointY = -pointY;
    }


    if( segY == 0 ) /* horizontal */
    {
        if( abs( pointY ) <= seuil )
        {
            if( ( pointX >= 0 ) && ( pointX <= segX ) )
                return 1;

            if( ( pointX < 0 ) && ( pointX >= -seuil ) )
            {
                if( ( ( pointX * pointX ) + ( pointY * pointY ) ) <= ( seuil * seuil ) )
                    return true;
            }
            if( ( pointX > segX ) && ( pointX <= ( segX + seuil ) ) )
            {
                if( ( ( ( pointX - segX ) * ( pointX - segX ) )
                     + ( pointY * pointY ) ) <= ( seuil * seuil ) )
                    return true;
            }
        }
    }
    else if( segX == 0 ) /* vertical */
    {
        if( abs( pointX ) <= seuil )
        {
            if( ( pointY >= 0 ) && ( pointY <= segY ) )
                return true;

            if( ( pointY < 0 ) && ( pointY >= -seuil ) )
            {
                if( ( ( pointY * pointY ) + ( pointX * pointX ) ) <= ( seuil * seuil ) )
                    return true;
            }

            if( ( pointY > segY ) && ( pointY <= ( segY + seuil ) ) )
            {
                if( ( ( ( pointY - segY ) * ( pointY - segY ) )
                     + ( pointX * pointX ) ) <= ( seuil * seuil ) )
                    return true;
            }
        }
    }
    else if( segX == segY )    /* 45 degrees */
    {
        /* Rotate axes of 45 degrees. mouse was then
         * Coord: x1 = x * y * cos45 + sin45
         * y1 = y * cos45 - sin45 x *
         * And the segment of track is horizontal.
         * Coord recalculation of the mouse (sin45 = cos45 = .707 = 7 / 10
         * Note: sin or cos45 = .707, and when recalculating coord
         * dx45 and dy45, lect coeff .707 is neglected, dx and dy are
         * actually 0707 times
         * Too big. (security hole too small)
         * Spot_cX, Y * must be by .707 * .707 = 0.5
         */

        cXrot = (pointX + pointY) >> 1;
        cYrot = (pointY - pointX) >> 1;

        /* Recalculating coord of segment extremity, which will be vertical
         * following the orientation of axes on the screen: dx45 = pointx
         * (or pointy) and 1.414 is actually greater, and dy45 = 0
         */

        // * Threshold should be .707 to reflect the change in coeff dx, dy
        seuil *= 7;
        seuil /= 10;

        if( abs( cYrot ) <= seuil ) /* ok on vertical axis */
        {
            if( ( cXrot >= 0 ) && ( cXrot <= segX ) )
                return true;

            /* Check extremes using the radius of a circle. */
            if( ( cXrot < 0 ) && ( cXrot >= -seuil ) )
            {
                if( ( ( cXrot * cXrot ) + ( cYrot * cYrot ) ) <= ( seuil * seuil ) )
                    return true;
            }
            if( ( cXrot > segX ) && ( cXrot <= ( segX + seuil ) ) )
            {
                if( ( ( ( cXrot - segX ) * ( cXrot - segX ) )
                     + ( cYrot * cYrot ) ) <= ( seuil * seuil ) )
                    return true;
            }
        }
    }
    else    /* any orientation */
    {
        /* There is a change of axis (rotation), so that the segment
         * track is horizontal in the new reference
         */
        int angle;

        angle = wxRound( ( atan2( (double) segY, (double) segX ) * 1800.0 / M_PI ) );
        cXrot = pointX;
        cYrot = pointY;

        RotatePoint( &cXrot, &cYrot, angle );   /* Rotate the point to be tested */
        RotatePoint( &segX, &segY, angle );     /* Rotate the segment */

        /* The track is horizontal, following the amendments to coordinate
         * axis and, therefore segX = length of segment
         */
        if( abs( cYrot ) <= seuil ) /* vertical axis */
        {
            if( ( cXrot >= 0 ) && ( cXrot <= segX ) )
                return true;

            if( ( cXrot < 0 ) && ( cXrot >= -seuil ) )
            {
                if( ( ( cXrot * cXrot ) + ( cYrot * cYrot ) ) <= ( seuil * seuil ) )
                    return true;
            }

            if( ( cXrot > segX ) && ( cXrot <= ( segX + seuil ) ) )
            {
                if( ( ( ( cXrot - segX ) * ( cXrot - segX ) )
                    + ( cYrot * cYrot ) ) <= ( seuil * seuil ) )
                    return true;
            }
        }
    }

    return false;
}


int ArcTangente( int dy, int dx )
{
    double fangle;

    if( dy == 0 )
    {
        if( dx >= 0 )
            return 0;
        else
            return -1800;
    }

    if( dx == 0 )
    {
        if( dy >= 0 )
            return 900;
        else
            return -900;
    }

    if( dx == dy )
    {
        if( dx >= 0 )
            return 450;
        else
            return -1800 + 450;
    }

    if( dx == -dy )
    {
        if( dx >= 0 )
            return -450;
        else
            return 1800 - 450;
    }

    fangle = atan2( (double) dy, (double) dx ) / M_PI * 1800;
    return wxRound( fangle );
}


void RotatePoint( int* pX, int* pY, int angle )
{
    int tmp;

    while( angle < 0 )
        angle += 3600;

    while( angle >= 3600 )
        angle -= 3600;

    // Cheap and dirty optimizations for 0, 90, 180, and 270 degrees.
    if( angle == 0 )
        return;

    if( angle == 900 )          /* sin = 1, cos = 0 */
    {
        tmp = *pX;
        *pX = *pY;
        *pY = -tmp;
    }
    else if( angle == 1800 )    /* sin = 0, cos = -1 */
    {
        *pX = -*pX;
        *pY = -*pY;
    }
    else if( angle == 2700 )    /* sin = -1, cos = 0 */
    {
        tmp = *pX;
        *pX = -*pY;
        *pY = tmp;
    }
    else
    {
        double fangle = DEG2RAD( (double) angle / 10.0 );
        double sinus = sin( fangle );
        double cosinus = cos( fangle );
        double fpx = (*pY * sinus ) + (*pX * cosinus );
        double fpy = (*pY * cosinus ) - (*pX * sinus );
        *pX = wxRound( fpx );
        *pY = wxRound( fpy );
    }
}


void RotatePoint( int* pX, int* pY, int cx, int cy, int angle )
{
    int ox, oy;

    ox = *pX - cx;
    oy = *pY - cy;

    RotatePoint( &ox, &oy, angle );

    *pX = ox + cx;
    *pY = oy + cy;
}


void RotatePoint( wxPoint* point, int angle )
{
    int ox, oy;

    ox = point->x;
    oy = point->y;

    RotatePoint( &ox, &oy, angle );
    point->x = ox;
    point->y = oy;
}


void RotatePoint( wxPoint* point, const wxPoint& centre, int angle )
{
    int ox, oy;

    ox = point->x - centre.x;
    oy = point->y - centre.y;

    RotatePoint( &ox, &oy, angle );
    point->x = ox + centre.x;
    point->y = oy + centre.y;
}


void RotatePoint( double* pX, double* pY, double cx, double cy, int angle )
{
    double ox, oy;

    ox = *pX - cx;
    oy = *pY - cy;

    RotatePoint( &ox, &oy, angle );

    *pX = ox + cx;
    *pY = oy + cy;
}


void RotatePoint( double* pX, double* pY, int angle )
{
    double tmp;

    while( angle < 0 )
        angle += 3600;

    while( angle >= 3600 )
        angle -= 3600;

    // Cheap and dirty optimizations for 0, 90, 180, and 270 degrees.
    if( angle == 0 )
        return;

    if( angle == 900 )          /* sin = 1, cos = 0 */
    {
        tmp = *pX;
        *pX = *pY;
        *pY = -tmp;
    }
    else if( angle == 1800 )    /* sin = 0, cos = -1 */
    {
        *pX = -*pX;
        *pY = -*pY;
    }
    else if( angle == 2700 )    /* sin = -1, cos = 0 */
    {
        tmp = *pX;
        *pX = -*pY;
        *pY = tmp;
    }
    else
    {
        double fangle = DEG2RAD( (double) angle / 10.0 );
        double sinus = sin( fangle );
        double cosinus = cos( fangle );

        double fpx = (*pY * sinus ) + (*pX * cosinus );
        double fpy = (*pY * cosinus ) - (*pX * sinus );
        *pX = fpx;
        *pY = fpy;
    }
}


double EuclideanNorm( wxPoint vector )
{
    return sqrt( (double) vector.x * (double) vector.x + (double) vector.y * (double) vector.y );
}


wxPoint TwoPointVector( wxPoint startPoint, wxPoint endPoint )
{
    return endPoint - startPoint;
}


double DistanceLinePoint( wxPoint linePointA, wxPoint linePointB, wxPoint referencePoint )
{
    return fabs( (double) ( (linePointB.x - linePointA.x) * (linePointA.y - referencePoint.y) -
                 (linePointA.x - referencePoint.x ) * (linePointB.y - linePointA.y) )
                  / EuclideanNorm( TwoPointVector( linePointA, linePointB ) ) );
}


bool HitTestPoints( wxPoint pointA, wxPoint pointB, double threshold )
{
    wxPoint vectorAB = TwoPointVector( pointA, pointB );
    double  distance = EuclideanNorm( vectorAB );

    return distance < threshold;
}


int CrossProduct( wxPoint vectorA, wxPoint vectorB )
{
    return vectorA.x * vectorB.y - vectorA.y * vectorB.x;
}


double GetLineLength( const wxPoint& aPointA, const wxPoint& aPointB )
{
    return sqrt( pow( (double) aPointA.x - (double) aPointB.x, 2 ) +
                 pow( (double) aPointA.y - (double) aPointB.y, 2 ) );
}
