/*
 *   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.
 *
 * IntVector.java
 *
 * Created on 1. Februar 2003, 14:47
 */

package eu.mihosoft.freerouting.geometry.planar;
import eu.mihosoft.freerouting.datastructures.BigIntAux;
import eu.mihosoft.freerouting.datastructures.Signum;

/**
 *
 * Implementation of the interface Vector via a tuple of integers
 *
 * @author Alfons Wirtz
 */

public class IntVector extends Vector implements java.io.Serializable
{
    
    /**
     * creates an  IntVector from two integer coordinates
     */
    public IntVector(int p_x, int p_y)
    {
        // range check ommitet for performance reasons
        x = p_x;
        y = p_y;
    }
    
    /**
     * returns true, if this IntVector 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 ;
        }
        IntVector other = (IntVector)p_ob ;
        return ( x == other.x && y == other.y ) ;
    }
    
    /**
     * returns true, if both coordinates of this vector are 0
     */
    public final boolean is_zero()
    {
        return x == 0 && y == 0;
    }
    
    /**
     * returns the Vector such that this plus this.minus() is zero
     */
    public Vector negate()
    {
        return new IntVector(-x, -y);
    }
    
    public boolean is_orthogonal()
    {
        return ( x == 0 || y == 0 ) ;
    }
    
    public boolean is_diagonal()
    {
        return ( Math.abs(x) == Math.abs(y) ) ;
    }
    
    /**
     * Calculates the determinant of the matrix consisting of this
     * Vector and p_other.
     */
    public final long determinant(IntVector p_other)
    {
        return (long)x * p_other.y - (long)y * p_other.x;
    }
    
    public Vector turn_90_degree(int p_factor)
    {
        int n = p_factor;
        while (n < 0)
        {
            n += 4;
        }
        while (n >= 4)
        {
            n -= 4;
        }
        int new_x ;
        int new_y ;
        switch (n)
        {
            case 0: // 0 degree
                new_x = x;
                new_y = y;
                break;
            case 1: // 90 degree
                new_x = -y ;
                new_y = x ;
                break;
            case 2: // 180 degree
                new_x = -x ;
                new_y = -y ;
                break;
            case 3: // 270 degree
                new_x = y ;
                new_y = -x ;
                break;
            default:
                new_x = 0 ;
                new_y = 0 ;
        }
        return new IntVector(new_x, new_y) ;
    }
    
    
    public Vector mirror_at_y_axis()
    {
        return new IntVector(-this.x, this.y);
    }
    
    public Vector mirror_at_x_axis()
    {
        return new IntVector(this.x, -this.y);
    }
    
    /**
     * adds p_other to this vector
     */
    public final Vector add( Vector p_other)
    {
        return p_other.add(this);
    }
    
    final Vector add( IntVector p_other)
    {
        return new IntVector(x + p_other.x, y + p_other.y);
    }
    
    final Vector add( RationalVector p_other)
    {
        return p_other.add(this);
    }
    
    /**
     * returns the Point, which results from adding this vector to p_point
     */
    final Point add_to(IntPoint p_point)
    {
        return new IntPoint(p_point.x + x, p_point.y + y);
    }
    
    final Point add_to(RationalPoint p_point)
    {
        return p_point.translate_by(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();
    }
    
    Side side_of(IntVector p_other)
    {
        double determinant = (double) p_other.x * y - (double) p_other.y * x;
        return Side.of(determinant);
    }
    
    Side side_of(RationalVector p_other)
    {
        Side tmp = p_other.side_of(this);
        return tmp.negate();
    }
    
    /**
     * The function returns
     *   Signum.POSITIVE, if the scalar product of this vector and p_other {@literal >} 0,
     *   Signum.NEGATIVE, if the scalar product Vector is {@literal <} 0,
     *   and Signum.ZERO, if the scalar product is equal 0.
     */
    public Signum projection(Vector p_other)
    {
        return p_other.projection(this);
    }
    
    public double scalar_product(Vector p_other)
    {
        return p_other.scalar_product(this);
    }
    
    
    
    /**
     * converts this vector to a PointFloat.
     */
    public FloatPoint to_float()
    {
        return new FloatPoint(x, y);
    }
    
    public Vector change_length_approx(double p_length)
    {
        FloatPoint new_point = this.to_float().change_size(p_length);
        return new_point.round().difference_by(Point.ZERO);
    }
    
    Direction to_normalized_direction()
    {
        int dx = x;
        int dy = y;
        
        int gcd = BigIntAux.binaryGcd(Math.abs(dx), Math.abs(dy));
        if (gcd > 1)
        {
            dx /= gcd;
            dy /= gcd;
        }
        return new IntDirection(dx, dy);
    }
    
    
    /**
     * The function returns
     *   Signum.POSITIVE, if the scalar product of this vector and p_other > 0,
     *   Signum.NEGATIVE, if the scalar product Vector is < 0,
     *   and Signum.ZERO, if the scalar product is equal 0.
     */
    Signum projection(IntVector p_other)
    {
        double tmp = (double) x * p_other.x + (double) y * p_other.y;
        return Signum.of(tmp);
    }
    
    double scalar_product(IntVector p_other)
    {
        return (double) x * p_other.x + (double) y * p_other.y;
    }
    
    double scalar_product(RationalVector p_other)
    {
        return p_other.scalar_product(this);
    }
    
    
    Signum projection(RationalVector p_other)
    {
        return p_other.projection(this);
    }
    
    
    /**
     * the x coordinate of this vector
     */
    public final int x;
    
    /**
     * the y coordinate of this vector
     */
    public final int y;
}