/*
 *   Copyright (C) 2014  Alfons Wirtz
 *   website www.freerouting.net
 *
 *   Copyright (C) 2017 Michael Hoffer <info@michaelhoffer.de>
 *   Website www.freerouting.mihosoft.eu
*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License at <http://www.gnu.org/licenses/> 
 *   for more details.
 *
 * RationalPoint.java
 *
 * Created on 1. Februar 2003, 13:12
 */

package eu.mihosoft.freerouting.geometry.planar;
import java.math.BigInteger;

import eu.mihosoft.freerouting.datastructures.BigIntAux;

/**
 *
 * Implementation of points in the projective plane represented by
 * 3 coordinates x, y, z, which are infinite precision integers.
 * Two projective points (x1, y1, z1) and (x2, y2 z2) are equal,
 * if they are located on the same line through the zero point,
 * that means, there exist a number r with x2 = r*x1,
 * y2 = r*y1 and z2 = r*z1.
 * The affine Point with rational coordinates  represented by
 * the projective Point  (x, y, z) is (x/z, y/z).
 * The projective plane with integer coordinates contains in
 * addition to the affine plane with rational coordinates the
 * so-called line at infinity, which consist of
 * all projective points (x, y, z) with z = 0.
 *
 * @author Alfons Wirtz
 */

public class RationalPoint extends Point implements java.io.Serializable
{
    
    /**
     * approximates the coordinates of this point by float coordinates
     */
    public FloatPoint to_float()
    {
        double xd = x.doubleValue();
        double yd = y.doubleValue();
        double zd = z.doubleValue();
        if (zd == 0)
        {
            xd = Float.MAX_VALUE;
            yd = Float.MAX_VALUE;
        }
        else
        {
            xd /= zd;
            yd /= zd;
        }
        
        return new FloatPoint( xd, yd);
    }
    
    /**
     * returns true, if this RationalPoint is equal to p_ob
     */
    public final boolean equals( Object p_ob )
    {
        if ( this == p_ob )
        {
            return true;
        }
        if ( p_ob == null )
        {
            return false;
        }
        if ( getClass() != p_ob.getClass() )
        {
            return false ;
        }
        RationalPoint other = (RationalPoint)p_ob;
        BigInteger det = BigIntAux.determinant(x, other.x, z, other.z);
        if (det.signum() != 0)
        {
            return false;
        }
        det = BigIntAux.determinant(y, other.y, z, other.z);
        
        return (det.signum() == 0);
    }
    
    public boolean is_infinite()
    {
        return z.signum() == 0;
    }
    
    public IntBox surrounding_box()
    {
        FloatPoint fp = to_float();
        int llx = (int) Math.floor(fp.x);
        int lly = (int) Math.floor(fp.y);
        int urx = (int) Math.ceil(fp.x);
        int ury = (int) Math.ceil(fp.y);
        return new IntBox(llx, lly, urx, ury);
    }
    
    public IntOctagon surrounding_octagon()
    {
        FloatPoint fp = to_float();
        int lx = (int) Math.floor(fp.x);
        int ly = (int) Math.floor(fp.y);
        int rx = (int) Math.ceil(fp.x);
        int uy = (int) Math.ceil(fp.y);
        
        double tmp = fp.x - fp.y;
        int ulx = (int) Math.floor(tmp);
        int lrx = (int) Math.ceil(tmp);
        
        tmp = fp.x + fp.y;
        int llx = (int) Math.floor(tmp);
        int urx = (int) Math.ceil(tmp);
        return new IntOctagon(lx, ly, rx, uy, ulx, lrx, llx, urx);
    }
    
    public boolean is_contained_in(IntBox p_box)
    {
        BigInteger tmp = BigInteger.valueOf(p_box.ll.x).multiply(z);
        if (x.compareTo(tmp) < 0)
        {
            return false;
        }
        tmp = BigInteger.valueOf(p_box.ll.y).multiply(z);
        if (y.compareTo(tmp) < 0)
        {
            return false;
        }
        tmp = BigInteger.valueOf(p_box.ur.x).multiply(z);
        if (x.compareTo(tmp) > 0)
        {
            return false;
        }
        tmp = BigInteger.valueOf(p_box.ur.y).multiply(z);
        if (y.compareTo(tmp) > 0)
        {
            return false;
        }
        return true;
    }
    
    /**
     * returns the translation of this point by p_vector
     */
    public Point translate_by(Vector p_vector)
    {
        if (p_vector.equals(Vector.ZERO))
        {
            return this;
        }
        return p_vector.add_to(this) ;
    }
    
    Point translate_by(IntVector p_vector)
    {
        RationalVector vector = new RationalVector(p_vector);
        return  translate_by(vector);
    }
    
    Point translate_by(RationalVector p_vector)
    {
        BigInteger v1[] = new BigInteger[3];
        v1[0] = x;
        v1[1] = y;
        v1[2] = z;
        
        BigInteger v2[] = new BigInteger[3];
        v2[0] = p_vector.x;
        v2[1] = p_vector.y;
        v2[2] = p_vector.z;
        BigInteger[] result = BigIntAux.add_rational_coordinates(v1, v2);
        return new RationalPoint(result[0], result[1], result[2]);
    }
    
    /**
     * returns the difference vector of this point and p_other
     */
    public Vector difference_by(Point p_other)
    {
        Vector tmp =  p_other.difference_by(this);
        return tmp.negate();
    }
    
    Vector difference_by(IntPoint p_other)
    {
        RationalPoint other = new RationalPoint(p_other);
        return difference_by(other);
    }
    
    Vector difference_by(RationalPoint p_other)
    {
        BigInteger v1[] = new BigInteger[3];
        v1[0] = x;
        v1[1] = y;
        v1[2] = z;
        
        BigInteger v2[] = new BigInteger[3];
        v2[0] = p_other.x.negate();
        v2[1] = p_other.y.negate();
        v2[2] = p_other.z;
        BigInteger[] result = BigIntAux.add_rational_coordinates(v1, v2);
        return new RationalVector(result[0], result[1], result[2]);
    }
    
    /**
     * The function returns
     *         Side.ON_THE_LEFT, if this Point is on the left
     *                            of the line from p_1 to p_2;
     *         Side.ON_THE_RIGHT, if this Point is on the right
     *                            f the line from p_1 to p_2;
     *     and Side.COLLINEAR, if this Point is collinear with p_1 and p_2.
     */
    public Side side_of(Point p_1, Point p_2)
    {
        Vector v1 = difference_by(p_1);
        Vector v2 = p_2.difference_by(p_1);
        return v1.side_of(v2);
    }
    
    public Side side_of(Line p_line)
    {
        return side_of(p_line.a, p_line.b);
    }
    
    public Point perpendicular_projection(Line p_line)
    {
        // this function is at the moment only implemented for lines
        // consisting of IntPoints.
        // The general implementation is still missing.
        IntVector v = (IntVector)p_line.b.difference_by(p_line.a);
        BigInteger vxvx = BigInteger.valueOf((long)v.x * v.x);
        BigInteger vyvy = BigInteger.valueOf((long)v.y * v.y);
        BigInteger vxvy = BigInteger.valueOf((long) v.x * v.y);
        BigInteger denominator = vxvx.add(vyvy);
        BigInteger det =
                BigInteger.valueOf(((IntPoint)p_line.a).determinant((IntPoint)p_line.b));
        
        BigInteger tmp1 = vxvx.multiply(x);
        BigInteger tmp2 = vxvy.multiply(y);
        tmp1 = tmp1.add(tmp2);
        tmp2 = det.multiply(BigInteger.valueOf(v.y));
        tmp2 = tmp2.multiply(z);
        BigInteger proj_x = tmp1.add(tmp2);
        
        tmp1 = vxvy.multiply(x);
        tmp2 = vyvy.multiply(y);
        tmp1 = tmp1.add(tmp2);
        tmp2 = det.multiply(BigInteger.valueOf(v.x));
        tmp2 = tmp2.multiply(z);
        BigInteger proj_y = tmp1.add(tmp2);
        
        int signum = denominator.signum();
        if (signum != 0)
        {
            if (signum < 0)
            {
                denominator = denominator.negate();
                proj_x = proj_x.negate();
                proj_y = proj_y.negate();
            }
            if ((proj_x.mod(denominator)).signum() == 0 &&
                    (proj_y.mod(denominator)).signum() == 0)
            {
                proj_x = proj_x.divide(denominator);
                proj_y = proj_y.divide(denominator);
                if (proj_x.abs().compareTo(Limits.CRIT_INT_BIG) <= 0
                        && proj_y.abs().compareTo(Limits.CRIT_INT_BIG) <= 0)
                {
                    return new IntPoint(proj_x.intValue(), proj_y.intValue());
                }
                denominator = BigInteger.ONE;
            }
        }
        return new RationalPoint(proj_x, proj_y, denominator);
    }
    
    public int compare_x(Point p_other)
    {
        return -p_other.compare_x(this);
    }
    
    
    public int compare_y(Point p_other)
    {
        return -p_other.compare_y(this);
    }
    
    int compare_x(RationalPoint p_other)
    {
        BigInteger tmp1 = this.x.multiply(p_other.z);
        BigInteger tmp2 = p_other.x.multiply(this.z);
        return tmp1.compareTo(tmp2);
    }
    
    int compare_y(RationalPoint p_other)
    {
        BigInteger tmp1 = this.y.multiply(p_other.z);
        BigInteger tmp2 = p_other.y.multiply(this.z);
        return tmp1.compareTo(tmp2);
    }
    
    int compare_x(IntPoint p_other)
    {
        BigInteger tmp1 = this.z.multiply(BigInteger.valueOf(p_other.x));
        return this.x.compareTo(tmp1);
    }
    
    int compare_y(IntPoint p_other)
    {
        BigInteger tmp1 = this.z.multiply(BigInteger.valueOf(p_other.y));
        return this.y.compareTo(tmp1);
        
    }
    
    /**
     *  creates a RetionalPoint from 3 BigIntegers p_x, p_y and p_z.
     *  They represent the 2-dimensinal point with the
     *  rational number Tuple ( p_x / p_z , p_y / p_z).
     *  Throws IllegalArgumentException if denominator p_z is <= 0
     */
    RationalPoint(BigInteger p_x, BigInteger p_y, BigInteger p_z)
    {
        x = p_x;
        y = p_y;
        z = p_z;
        if (p_z.signum() < 0)
        {
            throw new IllegalArgumentException
                    ("RationalPoint: p_z is expected to be >= 0");
        }
    }
    
    /**
     * creates a RetionalPoint from an IntPoint
     */
    RationalPoint(IntPoint p_point)
    {
        x = BigInteger.valueOf(p_point.x);
        y = BigInteger.valueOf(p_point.y);
        z = BigInteger.ONE;
    }
    
    
    
    final BigInteger x;
    final BigInteger y;
    final BigInteger z;
    
    
}