using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;
using HandyControl.Data;

namespace HandyControl.Controls
{
    public class FlipNumber : Viewport3D
    {
        private bool _isLoaded;

        private TextBlock _page1TextDown;

        private TextBlock _page2TextUp;

        private TextBlock _page2TextDown;

        private TextBlock _page3TextUp;

        private ContainerUIElement3D _page1;

        private ContainerUIElement3D _page2;

        private ContainerUIElement3D _page3;

        private ContainerUIElement3D _content;

        private readonly AxisAngleRotation3D _pageRotation3D;

        private readonly DoubleAnimation _animation;

        private bool _isAnimating;

        public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(
            "CornerRadius", typeof(CornerRadius), typeof(FlipNumber), new PropertyMetadata(new CornerRadius(4)));

        public CornerRadius CornerRadius
        {
            get => (CornerRadius)GetValue(CornerRadiusProperty);
            set => SetValue(CornerRadiusProperty, value);
        }

        public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register(
            "Background", typeof(Brush), typeof(FlipNumber), new PropertyMetadata(default(Brush)));

        public Brush Background
        {
            get => (Brush)GetValue(BackgroundProperty);
            set => SetValue(BackgroundProperty, value);
        }

        public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register(
            "Foreground", typeof(Brush), typeof(FlipNumber), new PropertyMetadata(default(Brush)));

        public Brush Foreground
        {
            get => (Brush)GetValue(ForegroundProperty);
            set => SetValue(ForegroundProperty, value);
        }

        public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register(
            "FontSize", typeof(double), typeof(FlipNumber), new PropertyMetadata(70.0));

        [TypeConverter(typeof(FontSizeConverter))]
        public double FontSize
        {
            get => (double)GetValue(FontSizeProperty);
            set => SetValue(FontSizeProperty, value);
        }

        public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
            "Number", typeof(int), typeof(FlipNumber), new PropertyMetadata(ValueBoxes.Int0Box, OnNumberChanged));

        private static void OnNumberChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) =>
            ((FlipNumber)s).OnNumberChanged();

        public int Number
        {
            get => (int)GetValue(NumberProperty);
            set => SetValue(NumberProperty, value);
        }

        public FlipNumber()
        {
            var visual3D = new ModelVisual3D
            {
                Content = new DirectionalLight()
            };
            Children.Add(visual3D);

            _pageRotation3D = new AxisAngleRotation3D
            {
                Angle = 0,
                Axis = new Vector3D(1, 0, 0)
            };

            _animation = new DoubleAnimation(0, 180, new Duration(TimeSpan.FromSeconds(0.8)))
            {
                FillBehavior = FillBehavior.Stop
            };
            _animation.Completed += Animation_Completed;

            Loaded += (s, e) =>
            {
                if (_isLoaded) return;
                _isLoaded = true;
                InitNumber();
                var transform3D = new RotateTransform3D(_pageRotation3D);
                _page2.Transform = transform3D;
            };
        }

        private void Animation_Completed(object sender, EventArgs e)
        {
            _isAnimating = false;
            UpdateNumber();
        }

        private void InitNumber()
        {
            _page1 = new ContainerUIElement3D();
            var num1 = Number > 8 ? 0 : Number + 1;
            var page1NumDown = CreateNumber(num1, false, out _page1TextDown);
            _page1.Children.Add(page1NumDown);

            _page2 = new ContainerUIElement3D();
            var page2NumUp = CreateNumber(num1, true, out _page2TextUp);
            var page2NumDown = CreateNumber(Number, false, out _page2TextDown);
            _page2.Children.Add(page2NumUp);
            _page2.Children.Add(page2NumDown);

            _page3 = new ContainerUIElement3D();
            var page3NumUp = CreateNumber(Number, true, out _page3TextUp);
            _page3.Children.Add(page3NumUp);
            var transform3D = new RotateTransform3D(new AxisAngleRotation3D
            {
                Angle = 180,
                Axis = new Vector3D(1, 0, 0)
            });
            _page3.Transform = transform3D;

            _content = new ContainerUIElement3D
            {
                Children =
                {
                    _page1,
                    _page2,
                    _page3
                }
            };
            Children.Add(_content);
        }

        private bool CheckNull() => _page1TextDown != null && _page2TextUp != null && _page2TextDown != null && _page3TextUp != null;

        private void OnNumberChanged()
        {
            if (!CheckNull()) return;

            InitNewNumber();

            if (_isAnimating)
            {
                _isAnimating = false;
                UpdateNumber();
                return;
            }

            _isAnimating = true;
            _pageRotation3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, _animation);
        }

        private void InitNewNumber()
        {
            var num1 = Number.ToString();
            _page1TextDown.Text = num1;
            _page2TextUp.Text = num1;
        }

        private void UpdateNumber()
        {
            _pageRotation3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, null);
            _pageRotation3D.Angle = 0;

            var num = Number.ToString();
            _page2TextDown.Text = num;
            _page3TextUp.Text = num;

            _isAnimating = false;
        }

        private Viewport2DVisual3D CreateNumber(int num, bool isUp, out TextBlock textBlock)
        {
            int flag;
            var rotateTransform = new RotateTransform();

            if (isUp)
            {
                flag = -1;
                rotateTransform.Angle = 180;
            }
            else
            {
                flag = 1;
            }
            var halfWidth = ActualWidth / 2;
            var quarterWidth = ActualWidth / 4;
            var quarterHeight = ActualHeight / 4;

            var meMaterial = new DiffuseMaterial();
            Viewport2DVisual3D.SetIsVisualHostMaterial(meMaterial, true);

            textBlock = new TextBlock
            {
                RenderTransformOrigin = new Point(0.5, 0.5),
                Foreground = Foreground,
                FontSize = FontSize,
                VerticalAlignment = VerticalAlignment.Center,
                HorizontalAlignment = HorizontalAlignment.Center,
                TextAlignment = TextAlignment.Center,
                Text = num.ToString(),
                RenderTransform = rotateTransform,
                Margin = new Thickness(0, 0, 0, -quarterHeight),
                FontFamily = new FontFamily("Consolas")
            };

            var border = new Border
            {
                ClipToBounds = true,
                CornerRadius = new CornerRadius(CornerRadius.TopLeft, CornerRadius.TopRight, 0, 0),
                Background = Background,
                Width = halfWidth,
                Height = quarterHeight,
                Child = textBlock
            };

            var positions = new Point3DCollection
            {
                new Point3D(-quarterWidth * flag, quarterHeight, 0),
                new Point3D(-quarterWidth * flag, 0, 0),
                new Point3D(quarterWidth * flag, 0, 0),
                new Point3D(quarterWidth * flag, quarterHeight, 0)
            };

            var triangleIndices = new Int32Collection
            {
                0,
                1,
                2,
                0,
                2,
                3
            };

            var textureCoordinates = new PointCollection
            {
                new Point(0, 0),
                new Point(0, 1),
                new Point(1, 1),
                new Point(1, 0)
            };

            var geometry3D = new MeshGeometry3D
            {
                Positions = positions,
                TriangleIndices = triangleIndices,
                TextureCoordinates = textureCoordinates
            };

            var child = new Viewport2DVisual3D
            {
                Geometry = geometry3D,
                Visual = border,
                Material = meMaterial
            };

            return child;
        }
    }
}