using System;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;

namespace SurfacePlot3D.Behaviors
{
    public class Zoom
    {
        #region Private Constants

        private const String EnableMouseWheelPropertyName = "EnableMouseWheel";
        private const String MaximumScalePropertyName = "MaximumScale";
        private const String MinimumScalePropertyName = "MinimumScale";
        private const String ScaleFactorPropertyName = "ScaleFactor";

        #endregion

        #region EnableMouseWheel

        public static readonly DependencyProperty EnableMouseWheelProperty = DependencyProperty.RegisterAttached(EnableMouseWheelPropertyName, typeof(Boolean), typeof(Zoom), new FrameworkPropertyMetadata(OnEnableMouseWheelChanged));
        public static readonly DependencyProperty MaximumScaleProperty = DependencyProperty.RegisterAttached(MaximumScalePropertyName, typeof(Double), typeof(Zoom), new UIPropertyMetadata(10.0, OnMinMaxChanged));
        public static readonly DependencyProperty MinimumScaleProperty = DependencyProperty.RegisterAttached(MinimumScalePropertyName, typeof(Double), typeof(Zoom), new FrameworkPropertyMetadata(0.25, OnMinMaxChanged));
        private static DependencyProperty ScaleFactorProperty = DependencyProperty.RegisterAttached(ScaleFactorPropertyName, typeof(Double), typeof(Zoom), new FrameworkPropertyMetadata(0.0, null, CoerceScaleFactor));

        #endregion

        #region Get Accessors

        [AttachedPropertyBrowsableForType(typeof(UIElement))]
        public static Boolean GetEnableMouseWheel(UIElement obj)
        {
            return (Boolean)obj.GetValue(EnableMouseWheelProperty);
        }

        public static Double GetMaximumScale(DependencyObject obj)
        {
            return (Double)obj.GetValue(MaximumScaleProperty);
        }

        public static Double GetMinimumScale(DependencyObject obj)
        {
            return (Double)obj.GetValue(MinimumScaleProperty);
        }

        #endregion

        #region Set Accessors

        public static void SetEnableMouseWheel(UIElement obj, Boolean value)
        {
            obj?.SetValue(EnableMouseWheelProperty, value);
        }
        public static void SetMaximumScale(DependencyObject obj, Double value)
        {
            obj?.SetValue(MaximumScaleProperty, value);
        }

        public static void SetMinimumScale(DependencyObject obj, Double value)
        {
            obj?.SetValue(MinimumScaleProperty, value);
        }

        #endregion

        #region Coersion

        private static Object CoerceScaleFactor(DependencyObject d, Object value)
        {
            Double val = (Double)value;
            Double min = (Double)d.GetValue(MinimumScaleProperty);
            Double max = (Double)d.GetValue(MaximumScaleProperty);

            val = Math.Pow(Math.E, val);
            if (val < min)
                return Math.Log(min);
            else if (val > max)
                return Math.Log(max);

            return value;
        }

        #endregion

        #region Callbacks

        static void OnMinMaxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            d.InvalidateProperty(ScaleFactorProperty);
            SetZoom((FrameworkElement)d, 0);
        }

        static void OnEnableMouseWheelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UIElement elem = d as UIElement;
            if (elem == null)
                throw new NotSupportedException("Can only set the Zoom.EnableMouseWheel attached behavior on a UIElement.");

            if ((Boolean)e.NewValue)
                elem.PreviewMouseWheel += OnMouseWheel;
            else
                elem.PreviewMouseWheel -= OnMouseWheel;
        }

        static void OnMouseWheel(Object sender, MouseWheelEventArgs e)
        {
            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
                SetZoom(sender as FrameworkElement, e.Delta / 1000.0);
        }

        #endregion

        #region Private Static Methods

        private static void SetZoom(FrameworkElement elem, Double delta)
        {
            if (GetEnableMouseWheel(elem))
            {
                EnsureRenderTransform(elem);
                ScaleTransform xform = (ScaleTransform)elem.LayoutTransform; //  RenderTransform; //;LayoutTransform

                Double toSet = (Double)elem.GetValue(ScaleFactorProperty);
                elem.SetValue(ScaleFactorProperty, toSet + delta);
                toSet = Math.Pow(Math.E, (Double)elem.GetValue(ScaleFactorProperty));

                xform.ScaleX = xform.ScaleY = toSet;
            }
        }

        private static void EnsureRenderTransform(FrameworkElement elem)
        {
            if (elem.LayoutTransform == null || !(elem.LayoutTransform is ScaleTransform))
                elem.LayoutTransform = new ScaleTransform(1, 1);
            //if (elem.RenderTransform == null || !(elem.RenderTransform is ScaleTransform))
            //    elem.RenderTransform = new ScaleTransform(1, 1);
        }

        #endregion
    }
}