// PathVisio,
// a tool for data visualization and analysis using Biological Pathways
// Copyright 2006-2011 BiGCaT Bioinformatics
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package org.pathvisio.core.view;

import java.awt.geom.Point2D;

/**
 * Helper class for rotation calculations.
 *
 */
public class LinAlg
{

	/**
	 * Determines angle between two vectors defined by p1 and p2
	 * Both vectors start at 0.0
	 *
	 * @param p1 start point of vector
	 * @param p2 end point of vector
	 * @return angle in radians
	 */
	public static double angle(Point p1, Point p2)
	{
		//Angle:
		//					p1.p2
        //cos(angle) = --------------
        //          	||p1||*||p2||

		double cos = dot(p1,p2) / (p1.len() * p2.len());
		if(cos>1)
		{
			cos=1;
		}
		return direction(p1,p2) * Math.acos(cos);
	}

	/**
	 * negative: ccw positive: cw
	 */
	public static double direction(Point p1, Point p2)
	{

		return Math.signum(p1.x * p2.y - p1.y * p2.x);
	}

	private static double dot(Point v1, Point v2)
	{
		double[] d1 = v1.asArray();
		double[] d2 = v2.asArray();
		double sum = 0;
		for(int i = 0; i < Math.min(d1.length, d2.length); i++) sum += d1[i]*d2[i];
		return sum;
	}

	/**
	   Projection of point q on a line through p with direction vector v

	   If p is 0,0, it's the same as the two-argument function with the same name.
	 */
	public static Point project (Point p, Point q, Point v)
	{
		Point q2 = new Point (q.x - p.x, q.y - p.y);
		double vlen = dot (v, v);
		if (vlen == 0)
		{
			return p;
		}
		else
		{
			double c = dot (q2, v) / dot (v, v);
			return new Point (p.x + v.x * c, p.y + v.y * c);
		}
	}

	/**
	 * Convert a 2-D point to 1-D line coordinates (relative position on the line, range {0,1})
	 */
	public static double toLineCoordinates (Point start, Point end, Point p) {
		//Project v position on line and calculate relative position
		Point direction = start.subtract(end);
		Point projection = project(start, p, direction);
		double lineLength = distance(start, end);
		double anchorLength = distance(start, projection);
		double position = anchorLength / lineLength;

		double ldir = direction(start, end);
		double adir = direction(start, projection);
		if(adir != ldir) {
			position = 0;
		}
		if(position > 1) position = 1;
		if(position < 0) position = 0;
		if(Double.isNaN(position)) position = 0;
		return position;
	}
	/**
	  Projection of p1 on p2:

	   p1.p2
	   ----- . p2
	   p2.p2
	*/
	public static Point project(Point p1, Point p2)
	{
		double c = dot(p1, p2) / dot(p2, p2);
		return new Point(p2.x * c, p2.y * c);
	}

	public static double distance(Point p1, Point p2)
	{
		Point dp = p2.subtract(p1);
		return dp.len();
	}

	public static Point rotate(Point p, double angle)
	{
		Point pr = new Point(0,0);
		pr.x = p.x * Math.cos(angle) + p.y * Math.sin(angle);
		pr.y = -p.x * Math.sin(angle) + p.y * Math.cos(angle);
		return pr;
	}

	/**
	 * Point, pair of doubles. Supports adding, substracting,
	 * calculating the length of the vector, etc.
	 */
	public static class Point
	{
		public double x, y;

		public Point(double x, double y) { this.x = x; this.y = y;	}

		public Point(Point2D p2d) {
			this(p2d.getX(), p2d.getY());
		}

		public Point2D toPoint2D() {
			return new Point2D.Double(x, y);
		}

		public int[] asIntArray() { return new int[] { (int)x, (int)y }; }

		public double[] asArray() { return new double[] { x, y }; }

		public Point norm()
		{
			double l = len();
			return new Point(x / l, y / l);
		}
		public double len()
		{
			return Math.sqrt(dot(this, this));
		}

		public Point add(Point p) { return new Point(x + p.x, y + p.y); }
		public Point subtract(Point p) { return new Point(x - p.x, y - p.y); }
		public Point multiply(double d) { return new Point(x *= d, y *= d); }

		public Point clone() { return new Point(x, y); }
		public String toString() { return "Point: " + x + ", " + y; }
	}
}
