using System;
using System.IO;
using System.Numerics;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Rendering.Composition;
using Avalonia.VisualTree;

namespace Avalonia.Gif
{
    public class GifImage : Control
    {
        public static readonly StyledProperty<string> SourceUriRawProperty = AvaloniaProperty.Register<
            GifImage,
            string
        >("SourceUriRaw");

        public static readonly StyledProperty<Uri> SourceUriProperty = AvaloniaProperty.Register<
            GifImage,
            Uri
        >("SourceUri");

        public static readonly StyledProperty<Stream> SourceStreamProperty = AvaloniaProperty.Register<
            GifImage,
            Stream
        >("SourceStream");

        public static readonly StyledProperty<IterationCount> IterationCountProperty =
            AvaloniaProperty.Register<GifImage, IterationCount>("IterationCount", IterationCount.Infinite);

        private IGifInstance? _gifInstance;

        public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
            AvaloniaProperty.Register<GifImage, StretchDirection>("StretchDirection");

        public static readonly StyledProperty<Stretch> StretchProperty = AvaloniaProperty.Register<
            GifImage,
            Stretch
        >("Stretch");

        private CompositionCustomVisual? _customVisual;

        private object? _initialSource = null;

        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
        {
            switch (change.Property.Name)
            {
                case nameof(SourceUriRaw):
                case nameof(SourceUri):
                case nameof(SourceStream):
                    SourceChanged(change);
                    break;
                case nameof(Stretch):
                case nameof(StretchDirection):
                    InvalidateArrange();
                    InvalidateMeasure();
                    Update();
                    break;
                case nameof(IterationCount):
                    IterationCountChanged(change);
                    break;
                case nameof(Bounds):
                    Update();
                    break;
            }

            base.OnPropertyChanged(change);
        }

        public string SourceUriRaw
        {
            get => GetValue(SourceUriRawProperty);
            set => SetValue(SourceUriRawProperty, value);
        }

        public Uri SourceUri
        {
            get => GetValue(SourceUriProperty);
            set => SetValue(SourceUriProperty, value);
        }

        public Stream SourceStream
        {
            get => GetValue(SourceStreamProperty);
            set => SetValue(SourceStreamProperty, value);
        }

        public IterationCount IterationCount
        {
            get => GetValue(IterationCountProperty);
            set => SetValue(IterationCountProperty, value);
        }

        public StretchDirection StretchDirection
        {
            get => GetValue(StretchDirectionProperty);
            set => SetValue(StretchDirectionProperty, value);
        }

        public Stretch Stretch
        {
            get => GetValue(StretchProperty);
            set => SetValue(StretchProperty, value);
        }

        private static void IterationCountChanged(AvaloniaPropertyChangedEventArgs e)
        {
            var image = e.Sender as GifImage;
            if (image is null || e.NewValue is not IterationCount iterationCount)
                return;

            image.IterationCount = iterationCount;
        }

        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
        {
            var compositor = ElementComposition.GetElementVisual(this)?.Compositor;
            if (compositor == null || _customVisual?.Compositor == compositor)
                return;
            _customVisual = compositor.CreateCustomVisual(new CustomVisualHandler());
            ElementComposition.SetElementChildVisual(this, _customVisual);
            _customVisual.SendHandlerMessage(CustomVisualHandler.StartMessage);

            if (_initialSource is not null)
            {
                UpdateGifInstance(_initialSource);
                _initialSource = null;
            }

            Update();
            base.OnAttachedToVisualTree(e);
        }

        private void Update()
        {
            if (_customVisual is null || _gifInstance is null)
                return;

            var dpi = this.GetVisualRoot()?.RenderScaling ?? 1.0;
            var sourceSize = _gifInstance.GifPixelSize.ToSize(dpi);
            var viewPort = new Rect(Bounds.Size);

            var scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
            var scaledSize = sourceSize * scale;
            var destRect = viewPort.CenterRect(new Rect(scaledSize)).Intersect(viewPort);

            if (Stretch == Stretch.None)
            {
                _customVisual.Size = new Vector2((float)sourceSize.Width, (float)sourceSize.Height);
            }
            else
            {
                _customVisual.Size = new Vector2((float)destRect.Size.Width, (float)destRect.Size.Height);
            }

            _customVisual.Offset = new Vector3((float)destRect.Position.X, (float)destRect.Position.Y, 0);
        }

        private class CustomVisualHandler : CompositionCustomVisualHandler
        {
            private TimeSpan _animationElapsed;
            private TimeSpan? _lastServerTime;
            private IGifInstance? _currentInstance;
            private bool _running;

            public static readonly object StopMessage = new(),
                StartMessage = new();

            public override void OnMessage(object message)
            {
                if (message == StartMessage)
                {
                    _running = true;
                    _lastServerTime = null;
                    RegisterForNextAnimationFrameUpdate();
                }
                else if (message == StopMessage)
                {
                    _running = false;
                }
                else if (message is IGifInstance instance)
                {
                    _currentInstance?.Dispose();
                    _currentInstance = instance;
                }
            }

            public override void OnAnimationFrameUpdate()
            {
                if (!_running)
                    return;
                Invalidate();
                RegisterForNextAnimationFrameUpdate();
            }

            public override void OnRender(ImmediateDrawingContext drawingContext)
            {
                if (_running)
                {
                    if (_lastServerTime.HasValue)
                        _animationElapsed += (CompositionNow - _lastServerTime.Value);
                    _lastServerTime = CompositionNow;
                }

                try
                {
                    if (_currentInstance is null || _currentInstance.IsDisposed)
                        return;

                    var bitmap = _currentInstance.ProcessFrameTime(_animationElapsed);
                    if (bitmap is not null)
                    {
                        drawingContext.DrawBitmap(
                            bitmap,
                            new Rect(_currentInstance.GifPixelSize.ToSize(1)),
                            GetRenderBounds()
                        );
                    }
                }
                catch (Exception e)
                {
                    Logger.Sink?.Log(LogEventLevel.Error, "GifImage Renderer ", this, e.ToString());
                }
            }
        }

        /// <summary>
        /// Measures the control.
        /// </summary>
        /// <param name="availableSize">The available size.</param>
        /// <returns>The desired size of the control.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            var result = new Size();
            var scaling = this.GetVisualRoot()?.RenderScaling ?? 1.0;
            if (_gifInstance != null)
            {
                result = Stretch.CalculateSize(
                    availableSize,
                    _gifInstance.GifPixelSize.ToSize(scaling),
                    StretchDirection
                );
            }

            return result;
        }

        /// <inheritdoc/>
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (_gifInstance is null)
                return new Size();
            var scaling = this.GetVisualRoot()?.RenderScaling ?? 1.0;
            var sourceSize = _gifInstance.GifPixelSize.ToSize(scaling);
            var result = Stretch.CalculateSize(finalSize, sourceSize);
            return result;
        }

        private void SourceChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (
                e.NewValue is null
                || (e.NewValue is string value && !Uri.IsWellFormedUriString(value, UriKind.Absolute))
            )
            {
                return;
            }

            if (_customVisual is null)
            {
                _initialSource = e.NewValue;
                return;
            }

            UpdateGifInstance(e.NewValue);

            InvalidateArrange();
            InvalidateMeasure();
            Update();
        }

        private void UpdateGifInstance(object source)
        {
            _gifInstance?.Dispose();

            try
            {
                _gifInstance = new WebpInstance(source);
                // _gifInstance = new GifInstance(source);

                _gifInstance.IterationCount = IterationCount;
                _customVisual?.SendHandlerMessage(_gifInstance);
            }
            catch (Exception e)
            {
                Logger.Sink?.Log(LogEventLevel.Warning, "GifImage Update Source ", this, e.ToString());
            }
        }
    }
}
