/*
 *  Copyright (C) 2014  Alfons Wirtz  
 *   website www.freerouting.net
 *
 *   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 net.freerouting.freeroute.geometry.planar;

import java.math.BigInteger;
import java.util.Objects;
import net.freerouting.freeroute.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
 */
@SuppressWarnings("serial")
public class RationalPoint extends Point {

    final BigInteger x;
    final BigInteger y;
    final BigInteger z;

    /**
     * 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
     */
    private RationalPoint(IntPoint p_point) {
        x = BigInteger.valueOf(p_point.x);
        y = BigInteger.valueOf(p_point.y);
        z = BigInteger.ONE;
    }

    /**
     * approximates the coordinates of this point by float coordinates
     */
    @Override
    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
     */
    @Override
    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);
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 29 * hash + Objects.hashCode(this.x);
        hash = 29 * hash + Objects.hashCode(this.y);
        hash = 29 * hash + Objects.hashCode(this.z);
        return hash;
    }

    @Override
    public boolean is_infinite() {
        return z.signum() == 0;
    }

    @Override
    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);
    }

    @Override
    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);
    }

    @Override
    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);
        return y.compareTo(tmp) <= 0;
    }

    /**
     * returns the translation of this point by p_vector
     */
    @Override
    public Point translate_by(Vector p_vector) {
        if (p_vector.equals(VectorUtils.ZERO)) {
            return this;
        }
        return p_vector.add_to(this);
    }

    @Override
    Point translate_by(IntVector p_vector) {
        RationalVector vector = new RationalVector(p_vector);
        return translate_by(vector);
    }

    @Override
    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
     */
    @Override
    public Vector difference_by(Point p_other) {
        Vector tmp = p_other.difference_by(this);
        return tmp.negate();
    }

    @Override
    Vector difference_by(IntPoint p_other) {
        RationalPoint other = new RationalPoint(p_other);
        return difference_by(other);
    }

    @Override
    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.
     */
    @Override
    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);
    }

    @Override
    public Side side_of(Line p_line) {
        return side_of(p_line.a, p_line.b);
    }

    @Override
    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);
    }

    @Override
    public int compare_x(Point p_other) {
        return -p_other.compare_x(this);
    }

    @Override
    public int compare_y(Point p_other) {
        return -p_other.compare_y(this);
    }

    @Override
    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);
    }

    @Override
    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);
    }

    @Override
    int compare_x(IntPoint p_other) {
        BigInteger tmp1 = this.z.multiply(BigInteger.valueOf(p_other.x));
        return this.x.compareTo(tmp1);
    }

    @Override
    int compare_y(IntPoint p_other) {
        BigInteger tmp1 = this.z.multiply(BigInteger.valueOf(p_other.y));
        return this.y.compareTo(tmp1);

    }

}
