﻿///////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016 Laszlo Arvai. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it 
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, 
// or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301  USA
///////////////////////////////////////////////////////////////////////////////
// File description
// ----------------
// Border control which handles zoom and pan operation
///////////////////////////////////////////////////////////////////////////////
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace CygnusControls
{
	public class ZoomAndPanBorder : Border
	{
		private UIElement child = null;
		private Point origin;
		private Point start;

		private TranslateTransform GetTranslateTransform(UIElement element)
		{
			return (TranslateTransform)((TransformGroup)element.RenderTransform)
				.Children.First(tr => tr is TranslateTransform);
		}

		private ScaleTransform GetScaleTransform(UIElement element)
		{
			return (ScaleTransform)((TransformGroup)element.RenderTransform)
				.Children.First(tr => tr is ScaleTransform);
		}

		public override UIElement Child
		{
			get { return base.Child; }
			set
			{
				if (value != null && value != this.Child)
					this.Initialize(value);
				base.Child = value;
			}
		}

		public void Initialize(UIElement element)
		{
			this.child = element;
			if (child != null)
			{
				TransformGroup group = new TransformGroup();
				ScaleTransform st = new ScaleTransform();
				group.Children.Add(st);
				TranslateTransform tt = new TranslateTransform();
				group.Children.Add(tt);
				child.RenderTransform = group;
				child.RenderTransformOrigin = new Point(0.0, 0.0);
				this.MouseWheel += child_MouseWheel;
				this.MouseLeftButtonDown += child_MouseLeftButtonDown;
				this.MouseLeftButtonUp += child_MouseLeftButtonUp;
				this.MouseMove += child_MouseMove;
				this.PreviewMouseRightButtonDown += new MouseButtonEventHandler(
					child_PreviewMouseRightButtonDown);
			}
		}

		public void Reset()
		{
			if (child != null)
			{
				// reset zoom
				var st = GetScaleTransform(child);
				st.ScaleX = 1.0;
				st.ScaleY = 1.0;

				// reset pan
				var tt = GetTranslateTransform(child);
				tt.X = 0.0;
				tt.Y = 0.0;
			}
		}

		#region · Child Events ·

		private void child_MouseWheel(object sender, MouseWheelEventArgs e)
		{
			if (child != null)
			{
				var st = GetScaleTransform(child);
				var tt = GetTranslateTransform(child);

				double zoom = e.Delta > 0 ? .2 : -.2;
				if (!(e.Delta > 0) && (st.ScaleX < .4 || st.ScaleY < .4))
					return;

				Point relative = e.GetPosition(child);
				double abosuluteX;
				double abosuluteY;

				abosuluteX = relative.X * st.ScaleX + tt.X;
				abosuluteY = relative.Y * st.ScaleY + tt.Y;

				double zoomCorrected = zoom * st.ScaleX;
				st.ScaleX += zoomCorrected;
				st.ScaleY += zoomCorrected;

				//st.ScaleX += zoom;
				//st.ScaleY += zoom;

				tt.X = abosuluteX - relative.X * st.ScaleX;
				tt.Y = abosuluteY - relative.Y * st.ScaleY;
			}
		}

		private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			if (child != null)
			{
				var tt = GetTranslateTransform(child);
				start = e.GetPosition(this);
				origin = new Point(tt.X, tt.Y);
				this.Cursor = Cursors.Hand;
				child.CaptureMouse();
			}
		}

		private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			if (child != null)
			{
				child.ReleaseMouseCapture();
				this.Cursor = Cursors.Arrow;
			}
		}

		void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.Reset();
		}

		private void child_MouseMove(object sender, MouseEventArgs e)
		{
			if (child != null)
			{
				if (child.IsMouseCaptured)
				{
					var tt = GetTranslateTransform(child);
					Vector v = start - e.GetPosition(this);
					tt.X = origin.X - v.X;
					tt.Y = origin.Y - v.Y;
				}
			}
		}

		#endregion
	}
}
