using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace smx_config
{
    // A slider with two handles, and a handle connecting them.  Dragging the handle drags both
    // of the sliders.
    class DoubleSlider: Control
    {
        public delegate void ValueChangedDelegate(DoubleSlider slider);
        public event ValueChangedDelegate ValueChanged;

        // The minimum value for either knob.
        public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum",
            typeof(double), typeof(DoubleSlider), new FrameworkPropertyMetadata(0.0));

        public double Minimum {
            get { return (double) GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }

        // The maximum value for either knob.
        public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum",
            typeof(double), typeof(DoubleSlider), new FrameworkPropertyMetadata(20.0));

        public double Maximum {
            get { return (double) GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }

        // The minimum distance between the two values.
        public static readonly DependencyProperty MinimumDistanceProperty = DependencyProperty.Register("MinimumDistance",
            typeof(double), typeof(DoubleSlider), new FrameworkPropertyMetadata(0.0));

        public double MinimumDistance {
            get { return (double) GetValue(MinimumDistanceProperty); }
            set { SetValue(MinimumDistanceProperty, value); }
        }

        // Clamp value between minimum and maximum.
        private double CoerceValueToLimits(double value)
        {
            return Math.Min(Math.Max(value, Minimum), Maximum);
        }

        // Note that we only clamp LowerValue and UpperValue to the min/max values.  We don't
        // clamp them to each other or to MinimumDistance here, since that complicates setting
        // properties a lot.  We only clamp to those when the user manipulates the control, not
        // when we set values directly.
        private static object LowerValueCoerceValueCallback(DependencyObject target, object valueObject)
        {
            DoubleSlider slider = target as DoubleSlider;
            double value = (double)valueObject;
            value = slider.CoerceValueToLimits(value);
            return value;
        }

        private static object UpperValueCoerceValueCallback(DependencyObject target, object valueObject)
        {
            DoubleSlider slider = target as DoubleSlider;
            double value = (double)valueObject;
            value = slider.CoerceValueToLimits(value);
            return value;
        }

        private static void SliderValueChangedCallback(DependencyObject target, DependencyPropertyChangedEventArgs args)
        {
            DoubleSlider slider = target as DoubleSlider;
            if(slider.ValueChanged != null)
                slider.ValueChanged.Invoke(slider);
        }

        public static readonly DependencyProperty LowerValueProperty = DependencyProperty.Register("LowerValue",
            typeof(double), typeof(DoubleSlider),
            new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsArrange, SliderValueChangedCallback, LowerValueCoerceValueCallback));

        public double LowerValue {
            get { return (double) GetValue(LowerValueProperty); }
            set { SetValue(LowerValueProperty, value); }
        }

        public static readonly DependencyProperty UpperValueProperty = DependencyProperty.Register("UpperValue",
            typeof(double), typeof(DoubleSlider),
            new FrameworkPropertyMetadata(15.0, FrameworkPropertyMetadataOptions.AffectsArrange, SliderValueChangedCallback, UpperValueCoerceValueCallback));

        public double UpperValue {
            get { return (double) GetValue(UpperValueProperty); }
            set { SetValue(UpperValueProperty, value); }
        }

        private Thumb Middle;

        Thumb UpperThumb;
        Thumb LowerThumb;

        private RepeatButton DecreaseButton;
        private RepeatButton IncreaseButton;

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            arrangeSize = base.ArrangeOverride(arrangeSize);

            // Figure out the X position of the upper and lower thumbs.  Note that we have to provide
            // our width to GetValueToSize, since ActualWidth isn't available yet.
            double valueToSize = GetValueToSize(arrangeSize.Width);
            double UpperPointX = (UpperValue-Minimum) * valueToSize;
            double LowerPointX = (LowerValue-Minimum) * valueToSize;

            // Move the upper and lower handles out by this much, and extend this middle.  This
            // makes the middle handle bigger.
            double OffsetOutwards = 5;
            Middle.Arrange(new Rect(LowerPointX-OffsetOutwards-1, 0,
                Math.Max(1, UpperPointX-LowerPointX+OffsetOutwards*2+2), arrangeSize.Height));

            // Right-align the lower thumb and left-align the upper thumb.
            LowerThumb.Arrange(new Rect(LowerPointX-LowerThumb.Width-OffsetOutwards, 0, LowerThumb.Width, arrangeSize.Height));
            UpperThumb.Arrange(new Rect(UpperPointX                 +OffsetOutwards, 0, UpperThumb.Width, arrangeSize.Height));

            DecreaseButton.Arrange(new Rect(0, 0, Math.Max(1, LowerPointX), Math.Max(1, arrangeSize.Height)));
            IncreaseButton.Arrange(new Rect(UpperPointX, 0, Math.Max(1, arrangeSize.Width - UpperPointX), arrangeSize.Height));
            return arrangeSize;
        }
        
        private void MoveValue(double delta)
        {
            if(delta > 0)
            {
                // If this increase will be clamped when changing the upper value, reduce it
                // so it clamps the lower value too.  This way, the distance between the upper
                // and lower value stays the same.
                delta = Math.Min(delta, Maximum - UpperValue);
                UpperValue += delta;
                LowerValue += delta;
            }
            else
            {
                delta *= -1;
                delta = Math.Min(delta, LowerValue - Minimum);
                LowerValue -= delta;
                UpperValue -= delta;
            }
        }

        private double GetValueToSize()
        {
            return GetValueToSize(this.ActualWidth);
        }

        private double GetValueToSize(double width)
        {
            double Range = Maximum - Minimum;
            return Math.Max(0.0, (width - UpperThumb.RenderSize.Width) / Range);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            LowerThumb = GetTemplateChild("PART_LowerThumb") as Thumb;
            UpperThumb = GetTemplateChild("PART_UpperThumb") as Thumb;
            Middle = GetTemplateChild("PART_Middle") as Thumb;
            DecreaseButton = GetTemplateChild("PART_DecreaseButton") as RepeatButton;
            IncreaseButton = GetTemplateChild("PART_IncreaseButton") as RepeatButton;
            DecreaseButton.Click += delegate(object sender, RoutedEventArgs e) { MoveValue(-1); };
            IncreaseButton.Click += delegate(object sender, RoutedEventArgs e) { MoveValue(+1); };

            LowerThumb.DragDelta += delegate(object sender, DragDeltaEventArgs e)
            {
                double sizeToValue = 1 / GetValueToSize();

                double NewValue = LowerValue + e.HorizontalChange * sizeToValue;
                NewValue = Math.Min(NewValue, UpperValue - MinimumDistance);
                LowerValue = NewValue;
            };

            UpperThumb.DragDelta += delegate(object sender, DragDeltaEventArgs e)
            {
                double sizeToValue = 1 / GetValueToSize();
                double NewValue = UpperValue + e.HorizontalChange * sizeToValue;
                NewValue = Math.Max(NewValue, LowerValue + MinimumDistance);
                UpperValue = NewValue;
            };

            Middle.DragDelta += delegate(object sender, DragDeltaEventArgs e)
            {
                // Convert the pixel delta to a value change.
                double sizeToValue = 1 / GetValueToSize();
                Console.WriteLine("drag: " + e.HorizontalChange + ", " + sizeToValue + ", " + e.HorizontalChange * sizeToValue);
                MoveValue(e.HorizontalChange * sizeToValue);
            };

            InvalidateArrange();
        }
    }

    class DoubleSliderThumb: Thumb
    {
        public static readonly DependencyProperty ShowUpArrowProperty = DependencyProperty.Register("ShowUpArrow",
            typeof(bool), typeof(DoubleSliderThumb));

        public bool ShowUpArrow {
            get { return (bool) this.GetValue(ShowUpArrowProperty); }
            set { this.SetValue(ShowUpArrowProperty, value); }
        }

        public static readonly DependencyProperty ShowDownArrowProperty = DependencyProperty.Register("ShowDownArrow",
            typeof(bool), typeof(DoubleSliderThumb));

        public bool ShowDownArrow {
            get { return (bool) this.GetValue(ShowDownArrowProperty); }
            set { this.SetValue(ShowDownArrowProperty, value); }
        }
    }
}