/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2005/08/09, 11:19
!  AUTHOR(S): KOGA, Junichiro
!  File : VectorOperations.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */

package ciss.phase_viewer.common;

import org.apache.log4j.Logger;

/**
 * various static methods to perform vector operations.
 * 
 * @author KOGA, Junichiro
 */
public class VectorOperations {
    private static final Logger logger = Logger
            .getLogger(VectorOperations.class.getName());

    /**
     * calculates the distance between two vectors. if the dimension of the two
     * vectors are inconsistent, the smaller one is used.
     * 
     * @param firstVector
     *            the first vector
     * @param secondVector
     *            the second vector
     * @return the distance between vector one and two.
     */
    public static double calDist(double[] firstVector, double[] secondVector) {
        double dist = 0.d;
        int ndim;
        if (firstVector == null || secondVector == null) {
            logger.error("either first vector or second vector is null... ");
            return 0.d;
        }

        ndim = firstVector.length;

        if (ndim > secondVector.length) {
            ndim = secondVector.length;
        }

        if (ndim != secondVector.length) {
            logger.warn("the dimension of the two vectors are inconsistent... using the smaller one, "
                    + ndim);
        }

        for (int i = 0; i < ndim; i++) {
            dist += Math.pow((firstVector[i] - secondVector[i]), 2.d);
        }
        dist = Math.sqrt(dist);
        return dist;
    }

    public static float calDist(float[] firstVector, float[] secondVector) {
        double[] dfv = new double[firstVector.length];
        double[] dsv = new double[secondVector.length];
        for (int i = 0; i < dfv.length; i++) {
            dfv[i] = (double) firstVector[i];
        }
        for (int i = 0; i < dsv.length; i++) {
            dsv[i] = (double) secondVector[i];
        }
        float ret = (float) calDist(dfv, dsv);
        return ret;
    }

    /**
     * calculates the dot product between two vectors. if the dimension of the
     * two vectors are inconsistent, the smaller one is used.
     * 
     * @param v1
     *            the first vector
     * @param v2
     *            the second vector
     * @return the dot product between vector one and two.
     */
    public static double dotProduct(double[] v1, double[] v2) {
        int ndim;
        if (v1 == null || v2 == null) {
            logger.error("either first vector or second vector is null... ");
            return 0.d;
        }
        ndim = v1.length;

        if (ndim > v2.length) {
            ndim = v2.length;
        }

        if (v1.length != v2.length) {
            logger.warn("the dimension of the two vectors are inconsistent... using the smaller one, "
                    + ndim);
        }

        double dot = 0.d;
        for (int i = 0; i < ndim; i++) {
            dot += v1[i] * v2[i];
        }
        return dot;
    }

    public static float dotProduct(float[] v1, float[] v2) {
        double[] d1 = new double[v1.length];
        double[] d2 = new double[v2.length];
        for (int i = 0; i < d1.length; i++) {
            d1[i] = (double) v1[i];
        }
        for (int i = 0; i < d2.length; i++) {
            d2[i] = (double) v2[i];
        }
        float ret = (float) dotProduct(d1, d2);
        return ret;
    }

    /**
     * calculates the cross product between two vectors... will work only for
     * 3D!
     * 
     * @param v1
     *            the first vector
     * @param v2
     *            the second vector
     * @return the cross product
     */
    public static double[] crossProduct(double[] v1, double[] v2) {
        double[] ret = null;
        if (v1 != null && v2 != null) {
            ret = new double[3];
            ret[0] = v1[1] * v2[2] - v1[2] * v2[1];
            ret[1] = v1[2] * v2[0] - v1[0] * v2[2];
            ret[2] = v1[0] * v2[1] - v1[1] * v2[0];
        } else {
            logger.error("either v1 or v2 was null ...");
            logger.error("returning null.");
        }
        return ret;
    }

    public static float[] crossProduct(float[] v1, float[] v2) {
        double[] d1 = new double[v1.length];
        double[] d2 = new double[v2.length];
        for (int i = 0; i < d1.length; i++) {
            d1[i] = (double) v1[i];
        }
        for (int i = 0; i < d2.length; i++) {
            d2[i] = (double) v2[i];
        }
        double[] foo = crossProduct(d1, d2);
        float[] ret = new float[foo.length];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = (float) foo[i];
        }
        return ret;
    }

    /**
     * calculates the norm of a vector.
     * 
     * @param v
     *            the vector whose norm will be calculated.
     * @return the norm of v
     */
    public static double norm(double[] v) {
        double norm = 0.d;
        if (v != null) {
            for (int i = 0; i < v.length; i++) {
                norm += v[i] * v[i];
            }
        } else {
            logger.error("v was null ... ");
        }
        return Math.sqrt(norm);
    }

    public static float norm(float[] v) {
        double[] dv = new double[v.length];
        for (int i = 0; i < dv.length; i++) {
            dv[i] = (double) v[i];
        }
        float ret = (float) norm(dv);
        return ret;
    }

    /**
     * calculates the normal vector of a plane defined by two vectors. for the
     * time being, this method will work only for 3D, since the cross-product
     * method works only for 3D...
     * 
     * @param firstVector
     *            the first vector
     * @param secondVector
     *            the second vector
     * @return the calculated normal vector
     */
    public static double[] getNormalVector(double[] firstVector,
            double[] secondVector) {

        if (firstVector == null || secondVector == null) {
            logger.error("either vector 1 or vector 2 was null...");
            return null;
        }
        if (firstVector.length < 3 || secondVector.length < 3) {
            logger.error("the dimension of the vectors must at least be greater than 3.");
            return null;
        }
        if (firstVector.length != 3 || secondVector.length != 3) {
            logger.warn("the dimension of either vector 1 or vector 2 is greater than 3 ...");
            logger.warn("truncating higher dimensions...");
        }

        double[] n1 = crossProduct(firstVector, secondVector);
        double norm1 = norm(n1);

        for (int i = 0; i < 3; i++) {
            n1[i] = n1[i] / norm1;
        }

        return n1;
    }

    public static float[] getNormalVector(float[] fv, float[] sv) {
        double[] dfv = new double[fv.length];
        double[] dsv = new double[sv.length];
        for (int i = 0; i < dfv.length; i++) {
            dfv[i] = (double) fv[i];
            dsv[i] = (double) sv[i];
        }
        double[] ret = getNormalVector(dfv, dsv);
        float[] retu = new float[ret.length];
        for (int i = 0; i < retu.length; i++) {
            retu[i] = (float) ret[i];
        }
        return retu;
    }

    /**
     * calculates the distance from some point A to a plane defined by two
     * vectors. For the time being, will work only for 3D...
     * 
     * @param point
     *            the point from which the distance will be calculated
     * @param firstVector
     *            the first vector specifying the plane
     * @param secondVector
     *            the second vector specifying the plane
     * @return distance between the point and the plane specified by the two
     *         vectors.
     */
    public static double getDistanceFromPlane(double[] point,
            double[] firstVector, double[] secondVector, double[] origin) {
        return getDistanceFromPlane(point, firstVector, secondVector, origin,
                false);
    }

    /**
     * calculates the distance from some point A to a plane defined by two
     * vectors. For the time being, will work only for 3D...
     * 
     * @param point
     *            the point from which the distance will be calculated
     * @param firstVector
     *            the first vector specifying the plane
     * @param secondVector
     *            the second vector specifying the plane
     * @param origin
     *            origin of the plane
     * @param orientationToo
     *            if true, return the 'raw' value instead of the absolute value
     * @return distance between the point and the plane specified by the two
     *         vectors.
     */
    public static double getDistanceFromPlane(double[] point,
            double[] firstVector, double[] secondVector, double[] origin,
            boolean orientationToo) {

        if (firstVector == null || secondVector == null) {
            logger.error("either vector 1 or vector 2 was null...");
            return 0.d;
        }
        if (firstVector.length < 3 || secondVector.length < 3) {
            logger.error("the dimension of the vectors must at least be greater than 3.");
            return 0.d;
        }
        if (firstVector.length != 3 || secondVector.length != 3) {
            logger.warn("the dimension of either vector 1 or vector 2 is greater than 3 ...");
            logger.warn("truncating higher dimensions...");
        }

        double[] referanceVector = new double[3];
        for (int i = 0; i < 3; i++) {
            referanceVector[i] = point[i] - (firstVector[i] + origin[i]);
        }

        double[] normalVector = getNormalVector(firstVector, secondVector);

        if (!orientationToo) {
            return Math.abs(dotProduct(normalVector, referanceVector));
        } else {
            return dotProduct(normalVector, referanceVector);
        }

    }

    /**
     * calculates the distance from some point A to a plane defined by a normal
     * vector and its origin. For the time being, will work only for 3D...
     * 
     * @param point
     *            the point from which the distance will be calculated
     * @param normalVector
     *            normal vector of the plane
     * @param origin
     *            the origin of the plane
     * @return distance between the point and the plane.
     */
    public static double getDistanceFromPlane(double[] point,
            double[] normalVector, double[] origin) {
        return getDistanceFromPlane(point, normalVector, origin, false);
    }

    /**
     * calculates the distance from some point A to a plane defined by its
     * normal and origin. For the time being, will work only for 3D...
     * 
     * @param point
     *            the point from which the distance will be calculated
     * @param normalVector
     *            the first vector specifying the plane
     * @param origin
     *            origin of the plane
     * @param orientationToo
     *            if true, return the 'raw' value instead of the absolute value
     * @return distance between the point and the plane specified by the two
     *         vectors.
     */
    public static double getDistanceFromPlane(double[] point,
            double[] normalVector, double[] origin, boolean orientationToo) {
        if (normalVector == null) {
            logger.error("invalid normal...");
            return 0.d;
        }
        if (normalVector.length < 3) {
            logger.error("the dimension of the vectors must at least be greater than 3.");
            return 0.d;
        }
        if (normalVector.length != 3) {
            logger.warn("the dimension of either vector 1 or vector 2 is greater than 3 ...");
            logger.warn("truncating higher dimensions...");
        }

        double[] referenceVector = new double[3];
        for (int i = 0; i < 3; i++) {
            referenceVector[i] = point[i] - origin[i];
        }

        if (!orientationToo) {
            return Math.abs(dotProduct(normalVector, referenceVector));
        } else {
            return dotProduct(normalVector, referenceVector);
        }

    }

    /**
     * 二つのベクトルの間の角度を計算する. ラジアン単位. どちらかのノルムが0の場合, 0を返す.
     * 
     * @param vector1
     *            一つ目のベクトル
     * @param vector2
     *            二つ目のベクトル
     * @return vector1とvector2の間の角度(ラジアン単位)
     */
    public static float getAngleBetween(float[] vector1, float[] vector2) {
        float verySmall = 0.0001f;
        float norm1 = norm(vector1);
        float norm2 = norm(vector2);
        float[] vecbuff1 = vector1;
        float[] vecbuff2 = vector2;

        if (norm1 < verySmall || norm2 < verySmall) {
            return 0.f;
        }

        float cosine = VectorOperations.dotProduct(vecbuff1, vecbuff2)
                / (norm1 * norm2);
        if (cosine > 1) {
            cosine = 1;
        }
        if (cosine < -1) {
            cosine = -1;
        }
        return (float) Math.acos((double) cosine);
    }

}
