/*
 *   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.
 *
 * RationalVector.java
 *
 * Created on 1. Februar 2003, 09:16
 */

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

import eu.mihosoft.freerouting.datastructures.BigIntAux;
import eu.mihosoft.freerouting.datastructures.Signum;
import eu.mihosoft.freerouting.logger.FRLogger;

/**
 *
 * Analog RationalPoint, but implementing the functionality
 * of a Vector instead of the functionality of a Point.
 *
 * @author Alfons Wirtz
 */

public class RationalVector extends Vector implements java.io.Serializable
{
    /**
     *  creates a RetionalVector from 3 BigIntegers p_x, p_y and p_z.
     *  They represent the 2-dimensional Vector with the
     *  rational number Tuple ( p_x / p_z , p_y / p_z).
     */
    public RationalVector(BigInteger p_x, BigInteger p_y, BigInteger p_z)
    {
        if (p_z.signum() >= 0)
        {
            x = p_x;
            y = p_y;
            z = p_z;
            
        }
        else
        {
            x = p_x.negate();
            y = p_y.negate();
            z = p_z.negate();
        }        
    }
    
    /**
     * creates a RetionalVector from an IntVector
     */
    RationalVector(IntVector p_vector)
    {
        x = BigInteger.valueOf(p_vector.x);
        y = BigInteger.valueOf(p_vector.y);
        z = BigInteger.ONE;
    }
    
    /**
     * returns true, if the x and y coordinates of this vector are 0
     */
    public final boolean is_zero()
    {
        return x.signum() == 0 && y.signum() == 0;
    }
    
    /**
     * returns true, if this RationalVector 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);
    }
    
    /**
     * returns the Vector such that this plus this.minus() is zero
     */
    public Vector negate()
    {
        return new RationalVector(x.negate(), y.negate(), z);
    }
    
    /**
     * adds p_other to this vector
     */
    public final Vector add( Vector p_other)
    {
        return p_other.add(this);
    }
    
    /**
     * Let L be the line from the Zero Vector to p_other.
     * The function returns
     *         Side.ON_THE_LEFT, if this Vector is on the left of L
     *         Side.ON_THE_RIGHT, if this Vector is on the right of L
     *     and Side.COLLINEAR, if this Vector is collinear with L.
     */
    public Side side_of(Vector p_other)
    {
        Side tmp = p_other.side_of(this);
        return tmp.negate();
    }
    
    public boolean is_orthogonal()
    {
        return (x.signum() == 0 || y.signum() == 0);
    }
    
    public boolean is_diagonal()
    {
        return x.abs().equals(y.abs());
    }
    
    
    /**
     * The function returns
     *   Signum.POSITIVE, if the scalar product of this vector and p_other {@literal >} 0,
     *   Signum.NEGATIVE, if the scalar product is {@literal <} 0,
     *   and Signum.ZERO, if the scalar product is equal 0.
     */
    public Signum projection(Vector p_other)
    {
        return p_other.projection(this);
    }
    
    /**
     * calculates  the scalar product of this vector and p_other
     */
    public double scalar_product(Vector p_other)
    {
        return p_other.scalar_product(this);
    }
    
    /**
     * approximates the coordinates of this vector by float coordinates
     */
    public FloatPoint to_float()
    {
        double xd = x.doubleValue();
        double yd = y.doubleValue();
        double zd = z.doubleValue();
        return new FloatPoint( xd / zd, yd / zd);
    }
    
    public Vector change_length_approx(double p_lenght)
    {
        FRLogger.warn("RationalVector: change_length_approx not yet implemented");
        return this;
    }
    
    public Vector turn_90_degree(int p_factor)
    {
        int n = p_factor;
        while (n < 0)
        {
            n += 4;
        }
        while (n >= 4)
        {
            n -= 4;
        }
        BigInteger new_x ;
        BigInteger new_y ;
        switch (n)
        {
            case 0: // 0 degree
                new_x = x;
                new_y = y;
                break;
            case 1: // 90 degree
                new_x = y.negate();
                new_y = x ;
                break;
            case 2: // 180 degree
                new_x = x.negate() ;
                new_y = y.negate() ;
                break;
            case 3: // 270 degree
                new_x = y ;
                new_y = x.negate() ;
                break;
            default:
                return this;
        }
        return new RationalVector(new_x, new_y, this.z);
    }
    
    public Vector mirror_at_y_axis()
    {
        return new RationalVector(this.x.negate(), this.y, this.z);
    }
    
    public Vector mirror_at_x_axis()
    {
        return new RationalVector(this.x, this.y.negate(), this.z);
    }
    
    Direction to_normalized_direction()
    {
        BigInteger dx = x;
        BigInteger dy = y;
        BigInteger gcd = dx.gcd(y);
        dx = dx.divide(gcd);
        dy = dy.divide(gcd);
        if ( (dx.abs()).compareTo(Limits.CRIT_INT_BIG) <= 0 &&
                (dy.abs()).compareTo(Limits.CRIT_INT_BIG) <= 0 )
        {
            return new IntDirection(dx.intValue(), dy.intValue());
        }
        return new BigIntDirection(dx, dy);
    }
    
    double scalar_product(IntVector p_other)
    {
        Vector other = new RationalVector(p_other);
        return other.scalar_product(this);
    }
    
    double scalar_product(RationalVector p_other)
    {
        FloatPoint v1 = to_float();
        FloatPoint v2 = p_other.to_float();
        return v1.x * v2.x + v1.y * v2.y;
    }
    
    Signum projection(IntVector p_other)
    {
        Vector other = new RationalVector(p_other);
        return other.projection(this);
    }
    
    Signum projection(RationalVector p_other)
    {
        BigInteger tmp1 = x.multiply(p_other.x);
        BigInteger tmp2 = y.multiply(p_other.y);
        BigInteger tmp3 = tmp1.add(tmp2);
        int result = tmp3.signum();
        return Signum.of(result);
    }
    
    final Vector add(IntVector p_other)
    {
        RationalVector other = new RationalVector(p_other);
        return add(other);
    }
    
    final Vector add(RationalVector 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;
        v2[1] = p_other.y;
        v2[2] = p_other.z;
        BigInteger[] result = BigIntAux.add_rational_coordinates(v1, v2);
        return new RationalVector(result[0], result[1], result[2]);
    }
    
    Point add_to(IntPoint p_point)
    {
        BigInteger new_x = z.multiply(BigInteger.valueOf(p_point.x));
        new_x = new_x.add(x);
        BigInteger new_y  = z.multiply(BigInteger.valueOf(p_point.y));
        new_y = new_y.add(y);
        return new RationalPoint(new_x, new_y, z);
    }
    
    Point add_to(RationalPoint p_point)
    {
        BigInteger v1[] = new BigInteger[3];
        v1[0] = x;
        v1[1] = y;
        v1[2] = z;
        
        BigInteger v2[] = new BigInteger[3];
        v2[0] = p_point.x;
        v2[1] = p_point.y;
        v2[2] = p_point.z;
        
        BigInteger[] result = BigIntAux.add_rational_coordinates(v1, v2);
        return new RationalPoint(result[0], result[1], result[2]);
    }
    
    Side side_of(IntVector p_other)
    {
        RationalVector other = new RationalVector(p_other);
        return side_of(other);
    }
    
    Side side_of(RationalVector p_other)
    {
        BigInteger tmp_1 = y.multiply(p_other.x);
        BigInteger tmp_2 = x.multiply(p_other.y);
        BigInteger determinant = tmp_1.subtract(tmp_2);
        int signum = determinant.signum();
        return Side.of(signum);
    }
    
    
    public final BigInteger x;
    public final BigInteger y;
    public final BigInteger z;
}