package org.cocos2d.support;

import org.cocos2d.cocoa.CCGeometry.CCPoint;
import org.cocos2d.cocoa.CCGeometry.CCSize;
import org.cocos2d.include.ccMacros;

/**
 * @addtogroup data_structures
 * @{
 */

public class CCPointExtension {

	/** Helper macro that creates a CCPoint
	 @return CCPoint
	 @since v0.7.2
	 */
	public static CCPoint Make(float x, float y) {
		return new CCPoint(x, y);
	}

	// TODO legacy -->

    private static final float kCCPointEpsilon = 0.00000012f; 

    /**
     * Helper macro that creates a CCPoint
     *
     * @return CCPoint
     */
    public static CCPoint ccp(float x, float y) {
        return new CCPoint(x, y);
    }
    
    /**
     * Returns opposite of point.
     *
     * @return CCPoint
     */
    public static CCPoint ccpNeg(final CCPoint v) {
        return ccp(-v.x, -v.y);
    }

    /**
     * Calculates sum of two points.
     *
     * @return CCPoint
     */
    public static CCPoint ccpAdd(final CCPoint v1, final CCPoint v2) {
        return ccp(v1.x + v2.x, v1.y + v2.y);
    }

    /**
     * Calculates difference of two points.
     *
     * @return CCPoint
     */
    public static CCPoint ccpSub(final CCPoint v1, final CCPoint v2) {
        return ccp(v1.x - v2.x, v1.y - v2.y);
    }

    /**
     * Returns point multiplied by given factor.
     *
     * @return CCPoint
     */
    public static CCPoint ccpMult(final CCPoint v, final float s) {
        return ccp(v.x * s, v.y * s);
    }

    /**
     * Calculates midpoint between two points.
     *
     * @return CCPoint
     */
    public static CCPoint ccpMidpoint(final CCPoint v1, final CCPoint v2) {
        return ccpMult(ccpAdd(v1, v2), 0.5f);
    }

    /**
     * Calculates dot product of two points.
     *
     * @return float
     */
    public static float ccpDot(final CCPoint v1, final CCPoint v2) {
        return v1.x * v2.x + v1.y * v2.y;
    }

    /**
     * Calculates cross product of two points.
     *
     * @return float
     */
    public static float ccpCross(final CCPoint v1, final CCPoint v2) {
        return v1.x * v2.y - v1.y * v2.x;
    }

    /**
     * Calculates perpendicular of v, rotated 90 degrees counter-clockwise -- cross(v, perp(v)) >= 0
     *
     * @return CCPoint
     */
    public static CCPoint ccpPerp(final CCPoint v) {
        return ccp(-v.y, v.x);
    }

    /**
     * Calculates perpendicular of v, rotated 90 degrees clockwise -- cross(v, rperp(v)) <= 0
     *
     * @return CCPoint
     */
    public static CCPoint ccpRPerp(final CCPoint v) {
        return ccp(v.y, -v.x);
    }

    /**
     * Calculates the projection of v1 over v2.
     *
     * @return CCPoint
     */
    public static CCPoint ccpProject(final CCPoint v1, final CCPoint v2) {
        return ccpMult(v2, ccpDot(v1, v2) / ccpDot(v2, v2));
    }

    /**
     * Rotates two points.
     *
     * @return CCPoint
     */
    public static CCPoint ccpRotate(final CCPoint v1, final CCPoint v2) {
        return ccp(v1.x * v2.x - v1.y * v2.y, v1.x * v2.y + v1.y * v2.x);
    }

    /**
     * Unrotates two points.
     *
     * @return CCPoint
     */
    public static CCPoint ccpUnrotate(final CCPoint v1, final CCPoint v2) {
        return ccp(v1.x * v2.x + v1.y * v2.y, v1.y * v2.x - v1.x * v2.y);
    }

    /**
     * Calculates the square length of a CCPoint (not calling sqrt() )
     *
     * @return float
     */
    public static float ccpLengthSQ(final CCPoint v) {
        return ccpDot(v, v);
    }

    /** Calculates distance between point and origin
     @return CGFloat
     @since v0.7.2
     */
    public static float ccpLength(final CCPoint v) {
        return (float)Math.sqrt(ccpLengthSQ(v));
    }

    /**
     * Calculates the distance between two points
     *
     * @return float
     */
    public static float ccpDistance(final CCPoint v1, final CCPoint v2) {
        return ccpLength(ccpSub(v1, v2));
    }

    /**
     * Returns point multiplied to a length of 1.
     *
     * @return CCPoint
     */
    public static CCPoint ccpNormalize(final CCPoint v) {
        return ccpMult(v, 1.0f / ccpLength(v));
    }

    /**
     * Converts radians to a normalized vector.
     *
     * @return CCPoint
     */
    public static CCPoint ccpForAngle(final float a) {
        return ccp((float)Math.cos(a), (float)Math.sin(a));
    }

    /**
     * Converts a vector to radians.
     *
     * @return float
     */
    public static float ccpToAngle(final CCPoint v) {
        return (float) Math.atan2(v.y, v.x);
    }

    /**
     *  Caculate the rotation(in degrees) between two points,
     *      so that when we move from one point to the other,
     *      we can set the correct rotation to head to that point.
     *
     * @param from
     * @param to
     * @return the rotation in degrees
     */
    public static float ccpCalcRotate(final CCPoint from, final CCPoint to) {
        float o = to.x - from.x;
        float a = to.y - from.y;
        float at = ccMacros.CC_RADIANS_TO_DEGREES((float) Math.atan(o / a));

        if (a < 0) {
            if (o < 0)
                at = 180 + Math.abs(at);
            else
                at = 180 - Math.abs(at);
        }

        return at;
    }


    /** @returns the angle in radians between two vector directions
      @since v0.99.1
      */
    public static float ccpAngle(CCPoint a, CCPoint b) {
        float angle = (float)Math.acos(ccpDot(ccpNormalize(a), ccpNormalize(b)));
        if( Math.abs(angle) < kCCPointEpsilon ) return 0.f;
        return angle;
    }

    /** Linear Interpolation between two points a and b
     @returns
        alpha == 0 ? a
        alpha == 1 ? b
        otherwise a value between a..b
     @since v0.99.1
     */
    public static CCPoint ccpLerp(CCPoint a, CCPoint b, float alpha) {
        return ccpAdd(ccpMult(a, 1.f - alpha), ccpMult(b, alpha));
    }

    /** Clamp a value between from and to.
      @since v0.99.1
      */
    public static float clampf(float value, float min_inclusive, float max_inclusive) {
        if (min_inclusive > max_inclusive) {
            float tmp = min_inclusive;
            min_inclusive = max_inclusive;
            max_inclusive = tmp;
        }

        return value < min_inclusive ? min_inclusive : value < max_inclusive? value : max_inclusive;
    }

    /** Clamp a point between from and to.
      @since v0.99.1
      */
    public static CCPoint ccpClamp(CCPoint p, CCPoint min_inclusive, CCPoint max_inclusive) {
        return ccp(clampf(p.x,min_inclusive.x,max_inclusive.x), 
                    clampf(p.y, min_inclusive.y, max_inclusive.y));
    }

    /** Quickly convert CGSize to a CCPoint
      @since v0.99.1
      */
    public static CCPoint ccpFromSize(CCSize s) {
        return ccp(s.width, s.height);
    }

    /** @returns if points have fuzzy equality which means equal with some degree of variance.
      @since v0.99.1
      */
    public static boolean ccpFuzzyEqual(CCPoint a, CCPoint b, float var) {
        if(a.x - var <= b.x && b.x <= a.x + var)
            if(a.y - var <= b.y && b.y <= a.y + var)
                return true;
        return false;
    }

    /** Multiplies a nd b components, a.x*b.x, a.y*b.y
      @returns a component-wise multiplication
      @since v0.99.1
      */
    public static CCPoint ccpCompMult(CCPoint a, CCPoint b) {
        return ccp(a.x * b.x, a.y * b.y);
    }

    /** @returns the signed angle in radians between two vector directions
      @since v0.99.1
      */
    public static float ccpAngleSigned(CCPoint a, CCPoint b) {
        CCPoint a2 = ccpNormalize(a);
        CCPoint b2 = ccpNormalize(b);
        float angle = (float)Math.atan2(a2.x * b2.y - a2.y * b2.x, ccpDot(a2, b2));
        if( Math.abs(angle) < kCCPointEpsilon ) return 0.f;
        return angle;
    }

    /** Rotates a point counter clockwise by the angle around a pivot
      @param v is the point to rotate
      @param pivot is the pivot, naturally
      @param angle is the angle of rotation cw in radians
      @returns the rotated point
      @since v0.99.1
      */
    public static CCPoint ccpRotateByAngle(CCPoint v, CCPoint pivot, float angle) {
        CCPoint r = ccpSub(v, pivot);
        float t = r.x;
        float cosa = (float)Math.cos(angle);
        float sina = (float)Math.sin(angle);
        r.x = t*cosa - r.y*sina;
        r.y = t*sina + r.y*cosa;
        r = ccpAdd(r, pivot);
        return r;
    }

    /** A general line-line intersection test
      @param p1 
      is the startpoint for the first line P1 = (p1 - p2)
      @param p2 
      is the endpoint for the first line P1 = (p1 - p2)
      @param p3 
      is the startpoint for the second line P2 = (p3 - p4)
      @param p4 
      is the endpoint for the second line P2 = (p3 - p4)
      @param s 
      is the range for a hitpoint in P1 (pa = p1 + s*(p2 - p1))
      @param t
      is the range for a hitpoint in P3 (pa = p2 + t*(p4 - p3))
      @return bool 
      indicating successful intersection of a line
      note that to truly test intersection for segments we have to make 
      sure that s & t lie within [0..1] and for rays, make sure s & t > 0
      the hit point is		p3 + t * (p4 - p3);
      the hit point also is	p1 + s * (p2 - p1);
      @since v0.99.1
      */
    public static boolean ccpLineIntersect(CCPoint p1, CCPoint p2, 
            CCPoint p3, CCPoint p4, CCPoint ret){
        CCPoint p13, p43, p21;
        float d1343, d4321, d1321, d4343, d2121;
        float numer, denom;

        p13 = ccpSub(p1, p3);

        p43 = ccpSub(p4, p3);

        //Roughly equal to zero but with an epsilon deviation for float 
        //correction
        if (ccpFuzzyEqual(p43, CCPoint.Zero, kCCPointEpsilon))
            return false;

        p21 = ccpSub(p2, p1);

        //Roughly equal to zero
        if (ccpFuzzyEqual(p21,CCPoint.Zero, kCCPointEpsilon))
            return false;

        d1343 = ccpDot(p13, p43);
        d4321 = ccpDot(p43, p21);
        d1321 = ccpDot(p13, p21);
        d4343 = ccpDot(p43, p43);
        d2121 = ccpDot(p21, p21);

        denom = d2121 * d4343 - d4321 * d4321;
        if (Math.abs(denom) < kCCPointEpsilon)
            return false;
        numer = d1343 * d4321 - d1321 * d4343;

        ret.x = numer / denom;
        ret.y = (d1343 + d4321 * ret.x) / d4343;

        return true;
    }

}

// end of data_structures group
/// @}