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

package eu.mihosoft.freerouting.geometry.planar;

import java.math.BigInteger;

import eu.mihosoft.freerouting.datastructures.Signum;

/**
 * Abstract class describing functionality of Vectors.
 * Vectors are used for translating Points in the plane.
 *
 * @author Alfons Wirtz
 */

public abstract class Vector implements java.io.Serializable
{
    
    /**
     * returns true, if this vector is equal to the zero vector.
     */
    public abstract boolean is_zero();
    
    /**
     * returns the Vector such that this plus this.negate() is zero
     */
    public abstract Vector negate();
    
    /**
     * adds p_other to this vector
     */
    public abstract Vector add( Vector p_other);
    
    /**
     * 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 abstract Side side_of(Vector p_other);
    
    /**
     * returns true, if the vector is horizontal or vertical
     */
    public abstract boolean is_orthogonal();
    
    /**
     * returns true, if the vector is diagonal
     */
    public abstract boolean is_diagonal();
    
    /**
     * Returns true, if the vector is orthogonal or diagonal
     */
    public boolean is_multiple_of_45_degree()
    {
        return is_orthogonal() || is_diagonal();
    }
    
    /**
     * 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 abstract Signum projection(Vector p_other);
    
    /**
     * Returns an approximation of the scalar product of this vector
     * with p_other by a double.
     */
    public abstract double scalar_product(Vector p_other);
    
    /**
     * approximates the coordinates of this vector by float coordinates
     */
    public abstract FloatPoint to_float();
    
    /**
     * Turns this vector by p_factor times 90 degree.
     */
    public abstract Vector turn_90_degree(int p_factor);
    
    /**
     * Mirrors this vector at the x axis.
     */
    public abstract Vector mirror_at_x_axis();
    
    /**
     * Mirrors this vector at the y axis.
     */
    public abstract Vector mirror_at_y_axis();
    
    /**
     * Standard implementation of the zero vector .
     */
    public static final IntVector ZERO = new IntVector(0, 0);
    
    /**
     * Creates a Vector (p_x, p_y) in the plane.
     */
    public static Vector get_instance(int p_x, int p_y)
    {
        IntVector result = new IntVector(p_x, p_y);
        if ( Math.abs(p_x) > Limits.CRIT_INT ||
                Math.abs(p_x) > Limits.CRIT_INT )
        {
            return new RationalVector(result);
        }
        return result;
    }
    
    /**
     * Creates a 2-dimensinal Vector from the 3 input values.
     * If p_z != 0 it correspondents to the Vector in the plane
     * with rational number coordinates (p_x / p_z, p_y / p_z).
     */
    public static Vector get_instance(BigInteger p_x, BigInteger p_y,
            BigInteger p_z)
    {
        if (p_z.signum() < 0)
        {
            // the dominator z of a RationalVector is expected to be positive
            p_x = p_x.negate();
            p_y = p_y.negate();
            p_z = p_z.negate();
            
        }
        if ((p_x.mod(p_z)).signum() == 0 && (p_x.mod(p_z)).signum() == 0)
        {
            // p_x and p_y can be divided by p_z
            p_x = p_x.divide(p_z);
            p_y = p_y.divide(p_z);
            p_z = BigInteger.ONE;
        }
        if (p_z.equals(BigInteger.ONE))
        {
            if ( (p_x.abs()).compareTo(Limits.CRIT_INT_BIG) <= 0 &&
                    (p_y.abs()).compareTo(Limits.CRIT_INT_BIG) <= 0 )
            {
                // the Point fits into an IntPoint
                return new IntVector(p_x.intValue(), p_y.intValue());
            }
        }
        return new RationalVector(p_x, p_y, p_z);
    }
    
    /**
     * returns an approximation of the euclidian length of this vector
     */
    public double length_approx()
    {
        return this.to_float().size();
    }
    
    
    /**
     * Returns an approximation of the cosinus of the angle
     * between this vector and  p_other by a double.
     */
    public double cos_angle(Vector p_other)
    {
        double result = this.scalar_product(p_other);
        result /= this.to_float().size() * p_other.to_float().size();
        return result;
    }
    
    /**
     * Returns an approximation of the signed angle between this vector and p_other.
     */
    public double angle_approx(Vector p_other)
    {
        double result = Math.acos(cos_angle(p_other));
        if (this.side_of(p_other) == Side.ON_THE_LEFT)
        {
            result = - result;
        }
        return result;
    }
    
    /**
     * Returns an approximation of the signed angle between this vector and the x axis.
     */
    public double angle_approx()
    {
        Vector other = new IntVector(1, 0);
        return other.angle_approx(this);
    }
    
    /**
     * Returns an approximation vector of this vector with the same direction and
     * length p_length.
     */
    public abstract Vector change_length_approx(double p_lenght);
    
    abstract Direction to_normalized_direction();
    
    
    
    // auxiliary functions needed because the virtual function mechanism
    // does not work in parameter position
    
    abstract Vector add( IntVector p_other);
    abstract Vector add( RationalVector p_other);
    
    abstract Point add_to(IntPoint p_point);
    abstract Point add_to(RationalPoint p_point);
    
    abstract Side side_of(IntVector p_other);
    abstract Side side_of(RationalVector p_other);
    
    abstract Signum projection(IntVector p_other);
    abstract Signum projection(RationalVector p_other);
    
    abstract double scalar_product(IntVector p_other);
    abstract double scalar_product(RationalVector p_other);
    
    
}