using System;
using System.Globalization;
using System.Windows;
using System.Windows.Media;

namespace AIStudio.Wpf.DiagramDesigner.Additionals.Controls
{
    /// <summary>
    /// A ruler control which supports both centimeters and inches. In order to use it vertically, change the <see cref="Marks">Marks</see> property to <c>Up</c> and rotate it ninety degrees.
    /// </summary>
    /// <remarks>
    /// Contributions from <see
    /// cref="http://www.orbifold.net/default/?p=2295&amp;cpage=1#comment-61500">Raf
    /// Lenfers</see>
    /// </remarks>
    public class Ruler : FrameworkElement
    {
        #region Fields
        private double SegmentHeight;
        private readonly Pen p = new Pen(Brushes.Black, 1.0);
        private readonly Pen ThinPen = new Pen(Brushes.Black, 0.5);
        private readonly Pen BorderPen = new Pen(Brushes.Gray, 1.0);
        private readonly Pen RedPen = new Pen(Brushes.Red, 2.0);
        #endregion

        #region Properties
        #region Background
        public Brush Background
        {
            get
            {
                return (Brush)GetValue(BackgroundProperty);
            }
            set
            {
                SetValue(BackgroundProperty, value);
            }
        }

        /// <summary>
        /// Identifies the Length dependency property.
        /// </summary>
        public static readonly DependencyProperty BackgroundProperty =
             DependencyProperty.Register(
                  "Background",
                  typeof(Brush),
                  typeof(Ruler),
                  new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), FrameworkPropertyMetadataOptions.AffectsRender));
        #endregion

        #region Length
        /// <summary>
        /// Gets or sets the length of the ruler. If the <see cref="AutoSize"/> property is set to false (default) this
        /// is a fixed length. Otherwise the length is calculated based on the actual width of the ruler.
        /// </summary>
        public double Length
        {
            get
            {
                if (this.AutoSize)
                {
                    return (double)(Unit == Unit.Cm ? ScreenHelper.WidthToCm(this.ActualWidth) : ScreenHelper.WidthToInch(this.ActualWidth)) / this.Zoom;
                }
                else
                {
                    return (double)GetValue(LengthProperty);
                }
            }
            set
            {
                SetValue(LengthProperty, value);
            }
        }

        /// <summary>
        /// Identifies the Length dependency property.
        /// </summary>
        public static readonly DependencyProperty LengthProperty =
             DependencyProperty.Register(
                  "Length",
                  typeof(double),
                  typeof(Ruler),
                  new FrameworkPropertyMetadata(20D, FrameworkPropertyMetadataOptions.AffectsRender));
        #endregion

        #region AutoSize
        /// <summary>
        /// Gets or sets the AutoSize behavior of the ruler.
        /// false (default): the lenght of the ruler results from the <see cref="Length"/> property. If the window size is changed, e.g. wider
        ///						than the rulers length, free space is shown at the end of the ruler. No rescaling is done.
        /// true				 : the length of the ruler is always adjusted to its actual width. This ensures that the ruler is shown
        ///						for the actual width of the window.
        /// </summary>
        public bool AutoSize
        {
            get
            {
                return (bool)GetValue(AutoSizeProperty);
            }
            set
            {
                SetValue(AutoSizeProperty, value);
                this.InvalidateVisual();
            }
        }

        /// <summary>
        /// Identifies the AutoSize dependency property.
        /// </summary>
        public static readonly DependencyProperty AutoSizeProperty =
             DependencyProperty.Register(
                  "AutoSize",
                  typeof(bool),
                  typeof(Ruler),
                  new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
        #endregion

        #region Zoom
        /// <summary>
        /// Gets or sets the zoom factor for the ruler. The default value is 1.0. 
        /// </summary>
        public double Zoom
        {
            get
            {
                return (double)GetValue(ZoomProperty);
            }
            set
            {
                SetValue(ZoomProperty, value);
                this.InvalidateVisual();
            }
        }

        /// <summary>
        /// Identifies the Zoom dependency property.
        /// </summary>
        public static readonly DependencyProperty ZoomProperty =
            DependencyProperty.Register("Zoom", typeof(double), typeof(Ruler),
            new FrameworkPropertyMetadata((double)1.0,
                FrameworkPropertyMetadataOptions.AffectsRender));

        #endregion

        #region Chip

        /// <summary>
        /// Chip Dependency Property
        /// </summary>
        public static readonly DependencyProperty ChipProperty =
             DependencyProperty.Register("Chip", typeof(double), typeof(Ruler),
                  new FrameworkPropertyMetadata((double)-1000,
                        FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// Sets the location of the chip in the units of the ruler.
        /// So, to set the chip to 10 in cm units the chip needs to be set to 10.
        /// Use the <see cref="DipHelper"/> class for conversions.
        /// </summary>
        public double Chip
        {
            get { return (double)GetValue(ChipProperty); }
            set { SetValue(ChipProperty, value); }
        }
        #endregion

        #region CountShift

        /// <summary>
        /// CountShift Dependency Property
        /// </summary>
        public static readonly DependencyProperty CountShiftProperty =
             DependencyProperty.Register("CountShift", typeof(double), typeof(Ruler),
                  new FrameworkPropertyMetadata(0d,
                        FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// By default the counting of inches or cm starts at zero, this property allows you to shift
        /// the counting.
        /// </summary>
        public double CountShift
        {
            get { return (double)GetValue(CountShiftProperty); }
            set { SetValue(CountShiftProperty, value); }
        }

        #endregion

        #region Marks

        /// <summary>
        /// Marks Dependency Property
        /// </summary>
        public static readonly DependencyProperty MarksProperty =
             DependencyProperty.Register("Marks", typeof(MarksLocation), typeof(Ruler),
                  new FrameworkPropertyMetadata(MarksLocation.Up,
                         FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// Gets or sets where the marks are shown in the ruler.
        /// </summary>
        public MarksLocation Marks
        {
            get { return (MarksLocation)GetValue(MarksProperty); }
            set { SetValue(MarksProperty, value); }
        }

        #endregion

        #region Unit
        /// <summary>
        /// Gets or sets the unit of the ruler.
        /// Default value is Unit.Cm.
        /// </summary>
        public Unit Unit
        {
            get { return (Unit)GetValue(UnitProperty); }
            set { SetValue(UnitProperty, value); }
        }

        /// <summary>
        /// Identifies the Unit dependency property.
        /// </summary>
        public static readonly DependencyProperty UnitProperty =
             DependencyProperty.Register(
                  "Unit",
                  typeof(Unit),
                  typeof(Ruler),
                  new FrameworkPropertyMetadata(Unit.Cm, FrameworkPropertyMetadataOptions.AffectsRender));

        #endregion

        #endregion

        #region Constructor
        static Ruler()
        {
            HeightProperty.OverrideMetadata(typeof(Ruler), new FrameworkPropertyMetadata(20.0));
        }
        public Ruler()
        {
            SegmentHeight = this.Height - 10;
        }
        #endregion

        #region Methods
        /// <summary>
        /// Participates in rendering operations.
        /// </summary>
        /// <param name="drawingContext">The drawing instructions for a specific element. This context is provided to the layout system.</param>
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            var rect = new Rect(0, 0, RenderSize.Width, RenderSize.Height);
            drawingContext.DrawRectangle(Background, null, rect);

            double xDest = (Unit == Unit.Cm ? ScreenHelper.CmToWidth(Length) : ScreenHelper.InchToWidth(Length)) * this.Zoom;
            drawingContext.DrawRectangle(null, BorderPen, new Rect(new Point(0.0, 0.0), new Point(xDest, Height)));
            double chip = Unit == Unit.Cm ? ScreenHelper.CmToWidth(Chip) : ScreenHelper.InchToWidth(Chip);
            drawingContext.DrawLine(RedPen, new Point(chip, 0), new Point(chip, Height));

            //画偏移位置之前的
            if (CountShift < 0)
            {
                for (double dUnit = -CountShift; dUnit > -1; dUnit--)
                {
                    double d;
                    if (Unit == Unit.Cm)
                    {
                        d = ScreenHelper.CmToWidth(dUnit) * this.Zoom;
                        if (dUnit < Length)
                        {
                            for (int i = 1; i <= 9; i++)
                            {
                                if (i != 5)
                                {
                                    double dMm = ScreenHelper.CmToWidth(dUnit + 0.1 * i) * this.Zoom;
                                    if (Marks == MarksLocation.Up)
                                        drawingContext.DrawLine(ThinPen, new Point(dMm, 0), new Point(dMm, SegmentHeight / 3.0));
                                    else
                                        drawingContext.DrawLine(ThinPen, new Point(dMm, Height), new Point(dMm, Height - SegmentHeight / 3.0));
                                }
                            }
                            double dMiddle = ScreenHelper.CmToWidth(dUnit + 0.5) * this.Zoom;
                            if (Marks == MarksLocation.Up)
                                drawingContext.DrawLine(p, new Point(dMiddle, 0), new Point(dMiddle, SegmentHeight * 2.0 / 3.0));
                            else
                                drawingContext.DrawLine(p, new Point(dMiddle, Height), new Point(dMiddle, Height - SegmentHeight * 2.0 / 3.0));
                        }
                    }
                    else
                    {
                        d = ScreenHelper.InchToWidth(dUnit) * this.Zoom;
                        if (dUnit < Length)
                        {
                            if (Marks == MarksLocation.Up)
                            {
                                double dQuarter = ScreenHelper.InchToWidth(dUnit + 0.25) * this.Zoom;
                                drawingContext.DrawLine(ThinPen, new Point(dQuarter, 0),
                                                                new Point(dQuarter, SegmentHeight / 3.0));
                                double dMiddle = ScreenHelper.InchToWidth(dUnit + 0.5) * this.Zoom;
                                drawingContext.DrawLine(p, new Point(dMiddle, 0),
                                                                new Point(dMiddle, SegmentHeight * 2D / 3D));
                                double d3Quarter = ScreenHelper.InchToWidth(dUnit + 0.75) * this.Zoom;
                                drawingContext.DrawLine(ThinPen, new Point(d3Quarter, 0),
                                                                new Point(d3Quarter, SegmentHeight / 3.0));
                            }
                            else
                            {
                                double dQuarter = ScreenHelper.InchToWidth(dUnit + 0.25) * this.Zoom;
                                drawingContext.DrawLine(ThinPen, new Point(dQuarter, Height),
                                                                new Point(dQuarter, Height - SegmentHeight / 3.0));
                                double dMiddle = ScreenHelper.InchToWidth(dUnit + 0.5) * this.Zoom;
                                drawingContext.DrawLine(p, new Point(dMiddle, Height),
                                                                new Point(dMiddle, Height - SegmentHeight * 2D / 3D));
                                double d3Quarter = ScreenHelper.InchToWidth(dUnit + 0.75) * this.Zoom;
                                drawingContext.DrawLine(ThinPen, new Point(d3Quarter, Height),
                                                                new Point(d3Quarter, Height - SegmentHeight / 3.0));
                            }
                        }
                    }
                    if (Marks == MarksLocation.Up)
                        drawingContext.DrawLine(p, new Point(d, 0), new Point(d, SegmentHeight));
                    else
                        drawingContext.DrawLine(p, new Point(d, Height), new Point(d, Height - SegmentHeight));


                    if ((dUnit != 0.0) && (dUnit < Length))
                    {
                        FormattedText ft = new FormattedText(
                            (Math.Round(dUnit + CountShift,0)).ToString(CultureInfo.CurrentCulture),
                             CultureInfo.CurrentCulture,
                             FlowDirection.LeftToRight,
                             new Typeface("Arial"),
                             9,
                             Brushes.DimGray);
                        ft.SetFontWeight(FontWeights.Regular);
                        ft.TextAlignment = TextAlignment.Center;

                        if (Marks == MarksLocation.Up)
                            drawingContext.DrawText(ft, new Point(d, Height - ft.Height));
                        else
                            drawingContext.DrawText(ft, new Point(d, Height - SegmentHeight - ft.Height));
                    }
                }
            }


            //画偏移位置之后的
            for (double dUnit = -CountShift; dUnit <= Length; dUnit++)
            {
                double d;
                if (Unit == Unit.Cm)
                {
                    d = ScreenHelper.CmToWidth(dUnit) * this.Zoom;
                    if (dUnit < Length)
                    {
                        for (int i = 1; i <= 9; i++)
                        {
                            if (i != 5)
                            {
                                double dMm = ScreenHelper.CmToWidth(dUnit + 0.1 * i) * this.Zoom;
                                if (Marks == MarksLocation.Up)
                                    drawingContext.DrawLine(ThinPen, new Point(dMm, 0), new Point(dMm, SegmentHeight / 3.0));
                                else
                                    drawingContext.DrawLine(ThinPen, new Point(dMm, Height), new Point(dMm, Height - SegmentHeight / 3.0));
                            }
                        }
                        double dMiddle = ScreenHelper.CmToWidth(dUnit + 0.5) * this.Zoom;
                        if (Marks == MarksLocation.Up)
                            drawingContext.DrawLine(p, new Point(dMiddle, 0), new Point(dMiddle, SegmentHeight * 2.0 / 3.0));
                        else
                            drawingContext.DrawLine(p, new Point(dMiddle, Height), new Point(dMiddle, Height - SegmentHeight * 2.0 / 3.0));
                    }
                }
                else
                {
                    d = ScreenHelper.InchToWidth(dUnit) * this.Zoom;
                    if (dUnit < Length)
                    {
                        if (Marks == MarksLocation.Up)
                        {
                            double dQuarter = ScreenHelper.InchToWidth(dUnit + 0.25) * this.Zoom;
                            drawingContext.DrawLine(ThinPen, new Point(dQuarter, 0),
                                                            new Point(dQuarter, SegmentHeight / 3.0));
                            double dMiddle = ScreenHelper.InchToWidth(dUnit + 0.5) * this.Zoom;
                            drawingContext.DrawLine(p, new Point(dMiddle, 0),
                                                            new Point(dMiddle, SegmentHeight * 2D / 3D));
                            double d3Quarter = ScreenHelper.InchToWidth(dUnit + 0.75) * this.Zoom;
                            drawingContext.DrawLine(ThinPen, new Point(d3Quarter, 0),
                                                            new Point(d3Quarter, SegmentHeight / 3.0));
                        }
                        else
                        {
                            double dQuarter = ScreenHelper.InchToWidth(dUnit + 0.25) * this.Zoom;
                            drawingContext.DrawLine(ThinPen, new Point(dQuarter, Height),
                                                            new Point(dQuarter, Height - SegmentHeight / 3.0));
                            double dMiddle = ScreenHelper.InchToWidth(dUnit + 0.5) * this.Zoom;
                            drawingContext.DrawLine(p, new Point(dMiddle, Height),
                                                            new Point(dMiddle, Height - SegmentHeight * 2D / 3D));
                            double d3Quarter = ScreenHelper.InchToWidth(dUnit + 0.75) * this.Zoom;
                            drawingContext.DrawLine(ThinPen, new Point(d3Quarter, Height),
                                                            new Point(d3Quarter, Height - SegmentHeight / 3.0));
                        }
                    }
                }
                if (Marks == MarksLocation.Up)
                    drawingContext.DrawLine(p, new Point(d, 0), new Point(d, SegmentHeight));
                else
                    drawingContext.DrawLine(p, new Point(d, Height), new Point(d, Height - SegmentHeight));


                if ((dUnit != 0.0) && (dUnit < Length))
                {
                    FormattedText ft = new FormattedText(
                        (Math.Round(dUnit + CountShift,0)).ToString(CultureInfo.CurrentCulture),
                         CultureInfo.CurrentCulture,
                         FlowDirection.LeftToRight,
                         new Typeface("Arial"),
                         9,
                         Brushes.DimGray);
                    ft.SetFontWeight(FontWeights.Regular);
                    ft.TextAlignment = TextAlignment.Center;

                    if (Marks == MarksLocation.Up)
                        drawingContext.DrawText(ft, new Point(d, Height - ft.Height));
                    else
                        drawingContext.DrawText(ft, new Point(d, Height - SegmentHeight - ft.Height));
                }
            }
        }

        /// <summary>
        /// Measures an instance during the first layout pass prior to arranging it.
        /// </summary>
        /// <param name="availableSize">A maximum Size to not exceed.</param>
        /// <returns>The maximum Size for the instance.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            return base.MeasureOverride(availableSize);
            //Size desiredSize;
            //if (Unit == Unit.Cm)
            //{
            //    desiredSize = new Size(ScreenHelper.CmToWidth(Length), Height);
            //}
            //else
            //{
            //    desiredSize = new Size(ScreenHelper.InchToWidth(Length), Height);
            //}
            //return desiredSize;
        }
        #endregion
    }

    /// <summary>
    /// The unit type of the ruler.
    /// </summary>
    public enum Unit
    {
        /// <summary>
        /// the unit is Centimeter.
        /// </summary>
        Cm,

        /// <summary>
        /// The unit is Inch.
        /// </summary>
        Inch
    };

    public enum MarksLocation
    {
        Up, Down
    }

    ///// <summary>
    ///// A helper class for DIP (Device Independent Pixels) conversion and scaling operations.
    ///// </summary>
    //public static class DipHelper
    //{
    //    /// <summary>
    //    /// Converts millimeters to DIP (Device Independant Pixels).
    //    /// </summary>
    //    /// <param name="mm">A millimeter value.</param>
    //    /// <returns>A DIP value.</returns>
    //    public static double MmToDip(double mm)
    //    {
    //        return CmToDip(mm / 10.0);
    //    }

    //    /// <summary>
    //    /// Converts centimeters to DIP (Device Independant Pixels).
    //    /// </summary>
    //    /// <param name="cm">A centimeter value.</param>
    //    /// <returns>A DIP value.</returns>
    //    public static double CmToDip(double cm)
    //    {
    //        return (cm * 96.0 / 2.54);
    //    }

    //    /// <summary>
    //    /// Converts inches to DIP (Device Independant Pixels).
    //    /// </summary>
    //    /// <param name="inch">An inch value.</param>
    //    /// <returns>A DIP value.</returns>
    //    public static double InchToDip(double inch)
    //    {
    //        return (inch * 96.0);
    //    }
    //    public static double DipToInch(double dip)
    //    {
    //        return dip / 96D;
    //    }

    //    /// <summary>
    //    /// Converts font points to DIP (Device Independant Pixels).
    //    /// </summary>
    //    /// <param name="pt">A font point value.</param>
    //    /// <returns>A DIP value.</returns>
    //    public static double PtToDip(double pt)
    //    {
    //        return (pt * 96.0 / 72.0);
    //    }

    //    /// <summary>
    //    /// Converts DIP (Device Independant Pixels) to centimeters.
    //    /// </summary>
    //    /// <param name="dip">A DIP value.</param>
    //    /// <returns>A centimeter value.</returns>
    //    public static double DipToCm(double dip)
    //    {
    //        return (dip * 2.54 / 96.0);
    //    }

    //    /// <summary>
    //    /// Converts DIP (Device Independant Pixels) to millimeters.
    //    /// </summary>
    //    /// <param name="dip">A DIP value.</param>
    //    /// <returns>A millimeter value.</returns>
    //    public static double DipToMm(double dip)
    //    {
    //        return DipToCm(dip) * 10.0;
    //    }

    //    /// <summary>
    //    /// Gets the system DPI scale factor (compared to 96 dpi).
    //    /// From http://blogs.msdn.com/jaimer/archive/2007/03/07/getting-system-dpi-in-wpf-app.aspx
    //    /// Should not be called before the Loaded event (else XamlException mat throw)
    //    /// </summary>
    //    /// <returns>A Point object containing the X- and Y- scale factor.</returns>
    //    private static Point GetSystemDpiFactor()
    //    {
    //        PresentationSource source = PresentationSource.FromVisual(Application.Current.MainWindow);
    //        Matrix m = source.CompositionTarget.TransformToDevice;
    //        return new Point(m.M11, m.M22);
    //    }

    //    private const double DpiBase = 96.0;

    //    /// <summary>
    //    /// Gets the system configured DPI.
    //    /// </summary>
    //    /// <returns>A Point object containing the X- and Y- DPI.</returns>
    //    public static Point GetSystemDpi()
    //    {
    //        Point sysDpiFactor = GetSystemDpiFactor();
    //        return new Point(
    //             sysDpiFactor.X * DpiBase,
    //             sysDpiFactor.Y * DpiBase);
    //    }

    //    /// <summary>
    //    /// Gets the physical pixel density (DPI) of the screen.
    //    /// </summary>
    //    /// <param name="diagonalScreenSize">Size - in inch - of the diagonal of the screen.</param>
    //    /// <returns>A Point object containing the X- and Y- DPI.</returns>
    //    public static Point GetPhysicalDpi(double diagonalScreenSize)
    //    {
    //        Point sysDpiFactor = GetSystemDpiFactor();
    //        double pixelScreenWidth = SystemParameters.PrimaryScreenWidth * sysDpiFactor.X;
    //        double pixelScreenHeight = SystemParameters.PrimaryScreenHeight * sysDpiFactor.Y;
    //        double formatRate = pixelScreenWidth / pixelScreenHeight;

    //        double inchHeight = diagonalScreenSize / Math.Sqrt(formatRate * formatRate + 1.0);
    //        double inchWidth = formatRate * inchHeight;

    //        double xDpi = Math.Round(pixelScreenWidth / inchWidth);
    //        double yDpi = Math.Round(pixelScreenHeight / inchHeight);

    //        return new Point(xDpi, yDpi);
    //    }

    //    /// <summary>
    //    /// Converts a DPI into a scale factor (compared to system DPI).
    //    /// </summary>
    //    /// <param name="dpi">A Point object containing the X- and Y- DPI to convert.</param>
    //    /// <returns>A Point object containing the X- and Y- scale factor.</returns>
    //    public static Point DpiToScaleFactor(Point dpi)
    //    {
    //        Point sysDpi = GetSystemDpi();
    //        return new Point(
    //             dpi.X / sysDpi.X,
    //             dpi.Y / sysDpi.Y);
    //    }

    //    /// <summary>
    //    /// Gets the scale factor to apply to a WPF application
    //    /// so that 96 DIP always equals 1 inch on the screen (whatever the system DPI).
    //    /// </summary>
    //    /// <param name="diagonalScreenSize">Size - in inch - of the diagonal of the screen</param>
    //    /// <returns>A Point object containing the X- and Y- scale factor.</returns>
    //    public static Point GetScreenIndependentScaleFactor(double diagonalScreenSize)
    //    {
    //        return DpiToScaleFactor(GetPhysicalDpi(diagonalScreenSize));
    //    }
    //}
}
