using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Input;
using System.Diagnostics;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Windows.Media;
using Microsoft.Research.DynamicDataDisplay;
using Microsoft.Research.DynamicDataDisplay.Common;

namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
{
	internal sealed class PhysicalRectAnimation
	{
		Vector position = new Vector();
		Vector velocity = new Vector();
		public Vector Velocity
		{
			get { return velocity; }
			set { velocity = value; }
		}

		Vector acceleration = new Vector();
		private double mass = 1; // kilogramms
		public double Mass
		{
			get { return mass; }
			set { mass = value; }
		}

		private double frictionCalmCoeff = 0.0;
		public double FrictionCalmCoeff
		{
			get { return frictionCalmCoeff; }
			set { frictionCalmCoeff = value; }
		}

		double frictionMovementCoeff = 0.1;
		public double FrictionMovementCoeff
		{
			get { return frictionMovementCoeff; }
			set { frictionMovementCoeff = value; }
		}

		double springCoeff = 50;
		public double SpringCoeff
		{
			get { return springCoeff; }
			set { springCoeff = value; }
		}
		
		double liquidFrictionCoeff = 1;
		public double LiquidFrictionCoeff
		{
			get { return liquidFrictionCoeff; }
			set { liquidFrictionCoeff = value; }
		}
		
		double liquidFrictionQuadraticCoeff = 10;
		public double LiquidFrictionQuadraticCoeff
		{
			get { return liquidFrictionQuadraticCoeff; }
			set { liquidFrictionQuadraticCoeff = value; }
		}

		const double G = 9.81;

		DataRect from;
		Viewport2D viewport;
		Point initialMousePos;
		CoordinateTransform initialTransform;

		public PhysicalRectAnimation(Viewport2D viewport, Point initialMousePos)
		{
			this.from = viewport.Visible;
			this.viewport = viewport;
			this.initialMousePos = initialMousePos;

			initialTransform = viewport.Transform;

			position = from.Location.ToVector();
		}

		double prevTime;

		private bool isFinished = false;
		public bool IsFinished
		{
			get { return isFinished; }
		}

		private bool useMouse = true;
		public bool UseMouse
		{
			get { return useMouse; }
			set { useMouse = value; }
		}

		public DataRect GetValue(TimeSpan timeSpan)
		{
			double time = timeSpan.TotalSeconds;

			double dtime = time - prevTime;

			acceleration = GetForces() / mass;

			velocity += acceleration * dtime;
			var shift = velocity * dtime;

			double viewportSize = Math.Sqrt(from.Width * from.Width + from.Height * from.Height);
			if (!(shift.Length < viewportSize * 0.002 && time > 0.5))
			{
				position += shift;
			}
			else
			{
				isFinished = true;
			}

			prevTime = time;

			Point pos = new Point(position.X, position.Y);
			DataRect bounds = new DataRect(pos, from.Size);

			return bounds;
		}

		private Vector GetForces()
		{
			Vector springForce = new Vector();
			if (useMouse)
			{
				Point mousePos = GetMousePosition();
				if (!mousePos.IsFinite()) { }

				Point p1 = initialMousePos.ScreenToData(initialTransform);
				Point p2 = mousePos.ScreenToData(viewport.Transform);

				var transform = viewport.Transform;

				Vector diff = p2 - p1;
				springForce = -diff * springCoeff;
			}

			Vector frictionForce = GetFrictionForce(springForce);

			Vector liquidFriction = -liquidFrictionCoeff * velocity - liquidFrictionQuadraticCoeff * velocity * velocity.Length;

			Vector result = springForce + frictionForce + liquidFriction;
			return result;
		}

		private Vector GetFrictionForce(Vector springForce)
		{
			double maxCalmFriction = frictionCalmCoeff * mass * G;
			if (maxCalmFriction >= springForce.Length)
				return -springForce;

			if (velocity.Length == 0)
				return new Vector();

			return -velocity / velocity.Length * frictionMovementCoeff * mass * G;
		}

		private Point GetMousePosition()
		{
			return Mouse.GetPosition(viewport.Plotter.ViewportPanel);
		}
	}
}
