//<auto-generated/>
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.System;
using Windows.UI;

namespace Danmaku.Core
{
    internal class DanmakuRender
    {
        private const float StandardCanvasWidth = 800;
        private const float DefaultRollingSpeed = 0.1f; // pixel per millisecond
        private const float DefaultBottomAndTopDurationMs = 3000f;
        private const string DefaultFontFamilyName = "Segoe UI";

        private CanvasAnimatedControl _canvas;
        private FrameworkElement _container;
        private CanvasDevice _device;
        private readonly RenderLayer[] _renderLayerList;
        private volatile bool _isStopped;
        private readonly float _dpi;

        private volatile bool _autoControlDensity = true;
        private volatile bool _textBold = true;
        private volatile bool _noOverlapSubtitle = false;
        private volatile int _maxDanmakuSize = 0;
        private volatile int _rollingDensity = -1;
        private volatile int _danmakuFontSizeOffset = (int)DanmakuFontSize.Normal;
        private volatile float _rollingAreaRatio = 0.8f;
        private volatile float _rollingSpeed = DefaultRollingSpeed; // 1 to 10
        private volatile bool _isPaused;
        private float _scale = 1.0f;
        private double _textOpacity = 1.0;
        private Color _borderColor = Colors.Blue;
        private string _defaultFontFamilyName = DefaultFontFamilyName;

        public float CanvasWidth
        {
            private set; get;
        }

        public float CanvasHeight
        {
            private set; get;
        }

        public bool DebugMode
        {
            get; set;
        }

        /// <exception cref="System.ArgumentNullException">canvas is null</exception>
        public DanmakuRender(CanvasAnimatedControl canvas, FrameworkElement container)
        {
            _canvas = canvas ?? throw new ArgumentNullException("canvas");
            _container = container;
            _dpi = _canvas.Dpi;
            _scale = (float)(_canvas?.XamlRoot?.RasterizationScale ?? 1.0);
            CanvasWidth = (float)(container.ActualWidth * _scale);
            CanvasHeight = (float)(_container.ActualHeight * _scale);
            _rollingSpeed = (float)(DefaultRollingSpeed * _scale);

            _container.SizeChanged += OnContainerSizeChanged;
            _canvas.CreateResources += OnCanvasCreateResources;
            _canvas.Update += OnCanvasUpdate;
            _canvas.Draw += OnCanvasDraw;

            uint layerCount = DanmakuDefaultLayerDef.DefaultLayerCount;
            _renderLayerList = new RenderLayer[layerCount];
            for (uint i = 0; i < layerCount; i++)
            {
                _renderLayerList[i] = new RenderLayer(i, i == DanmakuDefaultLayerDef.AdvancedLayerId || i == DanmakuDefaultLayerDef.SubtitleLayerId);
                if (CanvasHeight >= 1f)
                {
                    _renderLayerList[i].UpdateYSlotManagerLength((uint)CanvasHeight, _rollingAreaRatio);
                }
            }

            Logger.Log("DanmakuRender is created");
        }

        public void SetAutoControlDensity(bool value)
        {
            _autoControlDensity = value;
        }

        public void SetRollingDensity(int value)
        {
            _rollingDensity = value;
        }

        public void SetRollingAreaRatio(int value)
        {
            if (value > 0 && value <= 10 && _renderLayerList != null)
            {
                _rollingAreaRatio = (float)value / 10;
                for (int i = 0; i < _renderLayerList.Length; i++)
                {
                    _renderLayerList[i].UpdateYSlotManagerLength((uint)CanvasHeight, _rollingAreaRatio);
                }
            }
        }

        public void SetRollingSpeed(double value)
        {
            _rollingSpeed = (float)(value * 0.02f * _scale);
        }

        public void SetOpacity(double value)
        {
            if (value > 0 && value <= 1.0)
            {
                _textOpacity = value;
            }
        }

        public void SetIsTextBold(bool value)
        {
            _textBold = value;
        }

        public void SetDanmakuFontSizeOffset(DanmakuFontSize value)
        {
            if (value >= DanmakuFontSize.Smallest && value <= DanmakuFontSize.Largest)
            {
                _danmakuFontSizeOffset = (int)value;
            }
        }

        public void SetDefaultFontFamilyName(string value)
        {
            _defaultFontFamilyName = value ?? DefaultFontFamilyName;
        }

        public void SetBorderColor(Color color)
        {
            _borderColor = color;
        }

        public void SetNoOverlapSubtitle(bool value)
        {
            _noOverlapSubtitle = value;
        }

        /// <exception cref="System.ArgumentOutOfRangeException">layerId >= max layer count</exception>
        /// <exception cref="System.ArgumentNullException">device is null</exception>
        public void RenderDanmakuItem(uint layerId, DanmakuItem danmakuItem)
        {
            if (_device == null || _isStopped)
            {
                return;
            }

            if (layerId >= _renderLayerList.Length)
            {
                throw new ArgumentOutOfRangeException("layer", $"Max layer count: {_renderLayerList.Length}");
            }

            DanmakuYSlotManager ySlotManager = _renderLayerList[layerId].YSlotManager;

            try
            {
                if (danmakuItem.Mode != DanmakuMode.Advanced && danmakuItem.Mode != DanmakuMode.Subtitle)
                    if (danmakuItem.Mode != DanmakuMode.Advanced && danmakuItem.Mode != DanmakuMode.Subtitle)
                    {
                        danmakuItem.TextColor = Color.FromArgb((byte)(_textOpacity * byte.MaxValue), danmakuItem.TextColor.R, danmakuItem.TextColor.G, danmakuItem.TextColor.B);
                    }
                DanmakuRenderItem renderItem = new DanmakuRenderItem(danmakuItem);
                if (renderItem.Mode == DanmakuMode.Unknown)
                {
                    Logger.Log($"Ignore unknown danmaku type ({renderItem.Mode}): {renderItem.Text}");
                    return;
                }

                if (!_autoControlDensity && _rollingDensity > 0 && renderItem.Mode == DanmakuMode.Rolling && _renderLayerList[layerId].RenderList.Count >= _rollingDensity)
                {
                    // Skip rendering due to _rollingDensity
                    renderItem.Dispose();
                    return;
                }

                using (CanvasTextFormat textFormat = new CanvasTextFormat())
                {
                    textFormat.LocaleName = "zh-CN";
                    bool isBold = renderItem.IsBold == null ? _textBold : renderItem.IsBold.Value;
                    textFormat.FontWeight = isBold ? FontWeights.Bold : FontWeights.Normal;
                    textFormat.WordWrapping = CanvasWordWrapping.NoWrap;
                    textFormat.HorizontalAlignment = CanvasHorizontalAlignment.Center;
                    textFormat.VerticalAlignment = CanvasVerticalAlignment.Center;
                    textFormat.TrimmingGranularity = CanvasTextTrimmingGranularity.None;
                    textFormat.TrimmingSign = CanvasTrimmingSign.None;
                    if (renderItem.Mode == DanmakuMode.Top || renderItem.Mode == DanmakuMode.Bottom || renderItem.Mode == DanmakuMode.Subtitle)
                    {
                        textFormat.WordWrapping = CanvasWordWrapping.Wrap;
                    }

                    textFormat.FontSize = renderItem.FontSize;
                    if (!danmakuItem.KeepDefinedFontSize)
                    {
                        int fontSizeOffset = _danmakuFontSizeOffset;
                        textFormat.FontSize += (fontSizeOffset - 3) * (fontSizeOffset > 3 ? 6 : 3);

                        if (CanvasWidth < StandardCanvasWidth)
                        {
                            textFormat.FontSize = textFormat.FontSize * CanvasWidth / StandardCanvasWidth;
                            if (textFormat.FontSize >= 30f)
                            {
                                textFormat.FontSize *= 0.75f;
                            }
                            renderItem.MarginBottom = (int)(renderItem.MarginBottom * CanvasWidth * 0.75f / StandardCanvasWidth);
                        }
                    }
                    textFormat.FontSize = (int)Math.Max(textFormat.FontSize, 2f);
                    textFormat.FontSize = textFormat.FontSize * _scale;

                    if (!string.IsNullOrWhiteSpace(renderItem.FontFamilyName))
                    {
                        textFormat.FontFamily = renderItem.FontFamilyName;
                    }
                    else if (renderItem.Mode == DanmakuMode.Advanced)
                    {
                        textFormat.FontFamily = DefaultFontFamilyName;
                    }
                    else
                    {
                        textFormat.FontFamily = _defaultFontFamilyName;
                    }

                    string danmakuText = renderItem.Text;

                    // Measure text size
                    using (CanvasRenderTarget tempRenderTarget = new CanvasRenderTarget(_device, 0, 0, _dpi))
                    {
                        using (CanvasDrawingSession drawingSession = tempRenderTarget.CreateDrawingSession())
                        {
                            using (CanvasTextLayout textLayout = new CanvasTextLayout(drawingSession, danmakuText, textFormat, CanvasWidth - 24f, 0))
                            {
                                // Leave padding space for border
                                renderItem.Width = (float)textLayout.LayoutBounds.Width + 8f;
                                renderItem.Height = (float)textLayout.LayoutBounds.Height;
                                if (renderItem.HasOutline)
                                {
                                    renderItem.Height += renderItem.OutlineSize;
                                }

                                if (renderItem.Width <= 0 || renderItem.Height <= 0)
                                {
                                    return;
                                }
                                if (_maxDanmakuSize > 0 && (renderItem.Width >= _maxDanmakuSize || renderItem.Height >= _maxDanmakuSize))
                                {
                                    return;
                                }
                            }
                        }
                    }

                    // Do initial rendering
                    renderItem.RenderTarget = new CanvasRenderTarget(_device, renderItem.Width, renderItem.Height, _dpi);
                    using (CanvasDrawingSession drawingSession = renderItem.RenderTarget.CreateDrawingSession())
                    {
                        using (CanvasTextLayout textLayout = new CanvasTextLayout(drawingSession, danmakuText, textFormat, renderItem.Width, renderItem.Height))
                        {
                            // Calculate initial position
                            switch (renderItem.Mode)
                            {
                                case DanmakuMode.Rolling:
                                    {
                                        renderItem.NeedToReleaseYSlot = ySlotManager.GetY(renderItem.Id, (uint)renderItem.Height, out uint y);
                                        renderItem.StartX = CanvasWidth;
                                        renderItem.StartY = y;
                                        break;
                                    }
                                case DanmakuMode.Bottom:
                                    {
                                        renderItem.NeedToReleaseYSlot = ySlotManager.GetY(renderItem.Id, (uint)renderItem.Height, out uint y);
                                        renderItem.StartY = y;
                                        break;
                                    }
                                case DanmakuMode.Top:
                                    {
                                        renderItem.NeedToReleaseYSlot = ySlotManager.GetY(renderItem.Id, (uint)renderItem.Height, out uint y);
                                        renderItem.StartY = y;
                                        break;
                                    }
                                case DanmakuMode.ReverseRolling:
                                    {
                                        renderItem.NeedToReleaseYSlot = ySlotManager.GetY(renderItem.Id, (uint)renderItem.Height, out uint y);
                                        renderItem.StartX = -renderItem.Width;
                                        renderItem.StartY = y;
                                        break;
                                    }
                                case DanmakuMode.Advanced:
                                    {
                                        if (renderItem.AlignmentMode == DanmakuAlignmentMode.Default)
                                        {
                                            renderItem.StartX = renderItem.DefinedStartX > 1.0f ? renderItem.DefinedStartX : renderItem.DefinedStartX * CanvasWidth;
                                            renderItem.StartY = renderItem.DefinedStartY > 1.0f ? renderItem.DefinedStartY : renderItem.DefinedStartY * CanvasHeight;
                                            renderItem.EndX = renderItem.DefinedEndX > 1.0f ? renderItem.DefinedEndX : renderItem.DefinedEndX * CanvasWidth;
                                            if (renderItem.EndX > renderItem.StartX && renderItem.EndX < CanvasWidth && renderItem.EndX + renderItem.Width > CanvasWidth)
                                            {
                                                renderItem.EndX = renderItem.EndX + renderItem.Width * 0.2f <= CanvasWidth ? CanvasWidth - renderItem.Width : CanvasWidth;
                                            }
                                            renderItem.EndY = renderItem.DefinedEndY > 1.0f ? renderItem.DefinedEndY : renderItem.DefinedEndY * CanvasHeight;
                                            if (renderItem.EndY > renderItem.StartY && renderItem.EndY < CanvasHeight && renderItem.EndY + renderItem.Height > CanvasHeight)
                                            {
                                                renderItem.EndY = renderItem.EndY + renderItem.Height * 0.2f <= CanvasHeight ? CanvasHeight - renderItem.Height : CanvasHeight;
                                            }

                                            if (renderItem.AnchorMode != DanmakuAlignmentMode.UpperLeft)
                                            {
                                                switch (renderItem.AnchorMode)
                                                {
                                                    case DanmakuAlignmentMode.LowerCenter:
                                                    case DanmakuAlignmentMode.MiddleCenter:
                                                    case DanmakuAlignmentMode.UpperCenter:
                                                        {
                                                            renderItem.StartX -= renderItem.Width / 2;
                                                            renderItem.EndX -= renderItem.Width / 2;
                                                            break;
                                                        }
                                                    case DanmakuAlignmentMode.LowerRight:
                                                    case DanmakuAlignmentMode.MiddleRight:
                                                    case DanmakuAlignmentMode.UpperRight:
                                                        {
                                                            renderItem.StartX -= renderItem.Width;
                                                            renderItem.EndX -= renderItem.Width;
                                                            break;
                                                        }
                                                }

                                                switch (renderItem.AnchorMode)
                                                {
                                                    case DanmakuAlignmentMode.LowerLeft:
                                                    case DanmakuAlignmentMode.LowerCenter:
                                                    case DanmakuAlignmentMode.LowerRight:
                                                        {
                                                            renderItem.StartY -= renderItem.Height;
                                                            renderItem.EndY -= renderItem.Height;
                                                            break;
                                                        }
                                                    case DanmakuAlignmentMode.MiddleLeft:
                                                    case DanmakuAlignmentMode.MiddleCenter:
                                                    case DanmakuAlignmentMode.MiddleRight:
                                                        {
                                                            renderItem.StartY -= renderItem.Height / 2;
                                                            renderItem.EndY -= renderItem.Height / 2;
                                                            break;
                                                        }
                                                }
                                            }
                                        }
                                        else
                                        {
                                            if (renderItem.AlignmentMode == DanmakuAlignmentMode.LowerLeft
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.MiddleLeft
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.UpperLeft)
                                            {
                                                renderItem.StartX = renderItem.MarginLeft;
                                            }
                                            else if (renderItem.AlignmentMode == DanmakuAlignmentMode.LowerCenter
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.MiddleCenter
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.UpperCenter)
                                            {
                                                renderItem.StartX = (CanvasWidth - renderItem.Width) / 2;
                                            }
                                            else if (renderItem.AlignmentMode == DanmakuAlignmentMode.LowerRight
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.MiddleRight
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.UpperRight)
                                            {
                                                renderItem.StartX = CanvasWidth - renderItem.Width - renderItem.MarginRight;
                                            }

                                            if (renderItem.AlignmentMode == DanmakuAlignmentMode.LowerLeft
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.LowerCenter
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.LowerRight)
                                            {
                                                renderItem.StartY = CanvasHeight - renderItem.Height - renderItem.MarginBottom;
                                            }
                                            else if (renderItem.AlignmentMode == DanmakuAlignmentMode.MiddleLeft
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.MiddleCenter
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.MiddleRight)
                                            {
                                                renderItem.StartY = (CanvasHeight - renderItem.Width) / 2;
                                            }
                                            else if (renderItem.AlignmentMode == DanmakuAlignmentMode.UpperLeft
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.UpperCenter
                                                || renderItem.AlignmentMode == DanmakuAlignmentMode.UpperRight)
                                            {
                                                renderItem.StartY = 0;
                                            }

                                            renderItem.EndX = renderItem.StartX;
                                            renderItem.EndY = renderItem.StartY;
                                        }
                                        renderItem.TranslationSpeedX = renderItem.DefinedTranslationDurationMs > 0 ? (renderItem.EndX - renderItem.StartX) / renderItem.DefinedTranslationDurationMs : 0;
                                        renderItem.TranslationSpeedY = renderItem.DefinedTranslationDurationMs > 0 ? (renderItem.EndY - renderItem.StartY) / renderItem.DefinedTranslationDurationMs : 0;

                                        break;
                                    }
                            }
                            renderItem.X = renderItem.StartX;

                            if (_autoControlDensity && renderItem.AllowDensityControl && !renderItem.NeedToReleaseYSlot)
                            {
                                // Skip rendering due to _autoControlDensity
                                renderItem.Dispose();
                                return;
                            }

                            using (CanvasGeometry geometry = CanvasGeometry.CreateText(textLayout))
                            {
                                drawingSession.Clear(Colors.Transparent);

                                if (renderItem.HasBorder || DebugMode)
                                {
                                    drawingSession.DrawRectangle(0, 0, renderItem.Width, renderItem.Height, _borderColor, 4f);
                                }
                                if (renderItem.HasOutline)
                                {
                                    Color outlineColor = renderItem.TextColor.R + renderItem.TextColor.G + renderItem.TextColor.B < 0x20 ? Colors.White : renderItem.OutlineColor;
                                    outlineColor.A = renderItem.TextColor.A;

                                    drawingSession.DrawGeometry(geometry, 0, 0, outlineColor, renderItem.OutlineSize);
                                }
                                drawingSession.FillGeometry(geometry, 0, 0, renderItem.TextColor);
                            }
                        }
                    }
                }

                lock (_renderLayerList[layerId].RenderList)
                {
                    _renderLayerList[layerId].RenderList.Add(renderItem);
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex.Message);
                if (_device.IsDeviceLost(ex.HResult))
                {
                    _device.RaiseDeviceLost();
                    // Remove all item referencing old device
                    for (uint i = 0; i < _renderLayerList.Length; i++)
                    {
                        _renderLayerList[i].Clear();
                    }
                }
            }
        }

        public void SetLayerRenderState(uint layerId, bool render)
        {
            if (_renderLayerList is null)
            {
                return;
            }

            _renderLayerList[layerId].IsEnabled = render;
        }

        public void SetSubtitleLayer(uint layerId)
        {
            if (_renderLayerList is null)
            {
                return;
            }

            _renderLayerList[layerId].SetSubtitleLayer(true);
        }

        public void ClearLayer(uint layerId)
        {
            if (_renderLayerList is null)
            {
                return;
            }

            _renderLayerList[layerId].Clear();
        }

        public void Clear()
        {
            if (_renderLayerList is null)
            {
                return;
            }

            for (int i = 0; i < _renderLayerList.Length; i++)
            {
                _renderLayerList[i].Clear();
            }

            Debug.WriteLine("DanmakuRender is cleared");
        }

        public void Start()
        {
            Debug.WriteLine("DanmakuRender is started");

            if (_canvas != null)
            {
                _isPaused = false;
            }

            _isStopped = false;
        }

        public void Pause()
        {
            if (_canvas != null)
            {
                _isPaused = true;
                _canvas.Invalidate();
            }

            Debug.WriteLine("DanmakuRender is paused");
        }

        public void Stop()
        {
            _isStopped = true;
            Clear();

            Logger.Log("DanmakuRender is stopped");
        }

        public void Close()
        {
            Stop();
            if (_container != null)
            {
                _container.SizeChanged -= OnContainerSizeChanged;
                _container = null;
            }

            if (_canvas != null)
            {
                _isPaused = true;
                _canvas.Paused = true;
                _canvas.CreateResources -= OnCanvasCreateResources;
                _canvas.Update -= OnCanvasUpdate;
                _canvas.Draw -= OnCanvasDraw;
                _canvas.RemoveFromVisualTree();
                _canvas = null;
            }

            Logger.Log("DanmakuRender is closed");
        }

        private void OnContainerSizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (_renderLayerList is null)
            {
                return;
            }

            CanvasWidth = (float)(e.NewSize.Width * _scale);
            CanvasHeight = (float)(e.NewSize.Height * _scale);
            for (int i = 0; i < _renderLayerList.Length; i++)
            {
                _renderLayerList[i].UpdateYSlotManagerLength((uint)(e.NewSize.Height * _scale), _rollingAreaRatio);
            }
            Logger.Log($"Update canvas size: {CanvasWidth}x{CanvasHeight}");
        }

        private void OnCanvasCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
        {
            Logger.Log(args.Reason.ToString());
            if (args.Reason == CanvasCreateResourcesReason.NewDevice)
            {
                try
                {
                    _device?.RaiseDeviceLost();
                }
                catch (Exception ex)
                {
                    Logger.Log($"RaiseDeviceLost() failed: {ex.Message}");
                }
            }
            _device = sender.Device;
            _maxDanmakuSize = sender.Dpi > 0 ? (int)(_device.MaximumBitmapSizeInPixels / (sender.Dpi / 96)) : 0; // https://microsoft.github.io/Win2D/html/DPI.htm
        }

        private void OnCanvasUpdate(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs args)
        {
            if (_canvas is null || CanvasWidth <= 0 || CanvasHeight <= 0 || _isPaused)
            {
                return;
            }

            var hasItem = _renderLayerList.SelectMany(x => x.RenderList).Any();
            if (!hasItem)
            {
                return;
            }

            for (uint layerId = 0; layerId < _renderLayerList.Length; layerId++)
            {
                DanmakuYSlotManager ySlotManager = _renderLayerList[layerId].YSlotManager;

                List<DanmakuRenderItem> renderList = _renderLayerList[layerId].RenderList;
                lock (renderList)
                {
                    for (int i = renderList.Count - 1; i >= 0; i--)
                    {
                        if (_isStopped)
                        {
                            return;
                        }

                        DanmakuRenderItem renderItem = renderList[i];
                        if (!renderItem.IsFirstRenderTimeSet)
                        {
                            renderItem.FirstRenderTime = args.Timing.TotalTime;
                            renderItem.IsFirstRenderTimeSet = true;
                        }
                        float elapsedMs = (float)args.Timing.ElapsedTime.TotalMilliseconds;
                        float durationMs = (float)(args.Timing.TotalTime - renderItem.FirstRenderTime).TotalMilliseconds;
                        bool removeItem = false;

                        // Do layout and rendering
                        switch (renderItem.Mode)
                        {
                            case DanmakuMode.Rolling:
                                {
                                    if (!sender.Paused)
                                    {
                                        renderItem.X -= elapsedMs * AdjustRollingSpeedByWidth(_rollingSpeed, renderItem.Width);
                                    }
                                    if (renderItem.NeedToReleaseYSlot && renderItem.X < CanvasWidth - renderItem.Width - 48)
                                    {
                                        ySlotManager.ReleaseYSlot(renderItem.Id, (uint)renderItem.StartY);
                                        renderItem.NeedToReleaseYSlot = false;
                                    }
                                    if (renderItem.X < -renderItem.Width)
                                    {
                                        removeItem = true;
                                    }

                                    break;
                                }
                            case DanmakuMode.Bottom:
                            case DanmakuMode.Top:
                                {
                                    renderItem.X = (CanvasWidth - renderItem.Width) / 2;
                                    float maxDurationMs = renderItem.DefinedDurationMs > 0 ? renderItem.DefinedDurationMs : DefaultBottomAndTopDurationMs;
                                    if (durationMs > maxDurationMs)
                                    {
                                        removeItem = true;
                                        if (renderItem.NeedToReleaseYSlot)
                                        {
                                            ySlotManager.ReleaseYSlot(renderItem.Id, (uint)renderItem.StartY);
                                        }
                                    }

                                    break;
                                }
                            case DanmakuMode.ReverseRolling:
                                {
                                    if (!sender.Paused)
                                    {
                                        renderItem.X += elapsedMs * AdjustRollingSpeedByWidth(_rollingSpeed, renderItem.Width);
                                    }
                                    if (renderItem.NeedToReleaseYSlot && renderItem.X > 48f)
                                    {
                                        ySlotManager.ReleaseYSlot(renderItem.Id, (uint)renderItem.StartY);
                                        renderItem.NeedToReleaseYSlot = false;
                                    }
                                    if (renderItem.X >= CanvasWidth)
                                    {
                                        removeItem = true;
                                    }

                                    break;
                                }
                        }

                        if (removeItem)
                        {
                            renderItem.Dispose();
                            renderList.RemoveAt(i);
                        }
                    }
                }
            }
        }

        private void OnCanvasDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
        {
            try
            {
                if (_canvas is null || CanvasWidth <= 0 || CanvasHeight <= 0)
                {
                    return;
                }

                int totalCount = 0;
                args.DrawingSession.Transform = new Matrix3x2 { M11 = 1f / _scale, M22 = 1f / _scale };
                for (uint layerId = 0; layerId < _renderLayerList.Length; layerId++)
                {
                    if (!_renderLayerList[layerId].IsEnabled)
                    {
                        continue;
                    }

                    List<DanmakuRenderItem> renderList = _renderLayerList[layerId].RenderList;
                    if (renderList.Count == 0)
                    {
                        continue;
                    }

                    // https://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_CanvasSpriteSortMode.htm
                    CanvasSpriteSortMode spriteSortMode = !_renderLayerList[layerId].RequireStrictRenderOrder ? CanvasSpriteSortMode.Bitmap : CanvasSpriteSortMode.None;
                    using (CanvasSpriteBatch spriteBatch = args.DrawingSession.CreateSpriteBatch(spriteSortMode))
                    {
                        lock (renderList)
                        {
                            // First come first render
                            for (int i = 0; i < renderList.Count; i++)
                            {
                                if (_isStopped)
                                {
                                    return;
                                }

                                DanmakuRenderItem renderItem = renderList[i];
                                totalCount++;

                                if (!renderItem.IsFirstRenderTimeSet)
                                {
                                    // Wait for Update event for this item before rendering
                                    continue;
                                }

                                switch (renderItem.Mode)
                                {
                                    case DanmakuMode.Rolling:
                                    case DanmakuMode.ReverseRolling:
                                        {
                                            spriteBatch.Draw(renderItem.RenderTarget, new Vector2(renderItem.X, renderItem.StartY));

                                            break;
                                        }
                                    case DanmakuMode.Bottom:
                                        {
                                            float y = Math.Max((_noOverlapSubtitle ? Math.Max(CanvasHeight - 100f, CanvasHeight * 0.8f) : CanvasHeight) - renderItem.Height - renderItem.StartY, 0);
                                            y -= renderItem.MarginBottom;

                                            spriteBatch.Draw(renderItem.RenderTarget, new Vector2(renderItem.X, y));

                                            break;
                                        }
                                    case DanmakuMode.Top:
                                        {
                                            spriteBatch.Draw(renderItem.RenderTarget, new Vector2(renderItem.X, renderItem.StartY));

                                            break;
                                        }
                                }
                            }
                        }
                    }
                }

                if (totalCount == 0)
                {
                    // Draw an empty frame to keep the animation running.
                    args.DrawingSession.Clear(Colors.Transparent);
                }

                if (DebugMode)
                {
                    if (args.Timing.ElapsedTime.TotalMilliseconds > 0)
                    {
                        int fps = (int)(1000 / args.Timing.ElapsedTime.TotalMilliseconds);
                        args.DrawingSession.FillRectangle(0, 0, 320, 30, fps >= 30 ? Colors.Gray : Colors.Red);
                        string debugText = $"fps:{fps} count:{totalCount} {(int)CanvasWidth}x{(int)CanvasHeight}";
                        args.DrawingSession.DrawText(debugText, 0, 0, Colors.LightGreen);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex.Message);
                if (_device.IsDeviceLost(ex.HResult))
                {
                    Logger.Log("Device is lost!");
                }
            }
            finally
            {
                args.DrawingSession?.Dispose();
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static float DegreeToRadian(float degree)
        {
            return degree * (float)Math.PI / 180f;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static float AdjustRollingSpeedByWidth(float rollingSpeed, float width)
        {
            return rollingSpeed * (Math.Min(width * 0.0015f, 0.2f) + 1f);
        }

        [DebuggerDisplay("{Text} at {FirstRenderTime.TotalMilliseconds}")]
        private class DanmakuRenderItem
        {
            public readonly uint Id;
            public readonly bool HasBorder;
            public readonly bool HasOutline;
            public readonly bool AllowDensityControl;
            public readonly float FontSize;
            public readonly float OutlineSize;
            public readonly string FontFamilyName;
            public readonly string Text;
            public readonly bool? IsBold;
            public readonly DanmakuMode Mode;
            public readonly Color TextColor;
            public readonly Color OutlineColor;

            public volatile bool IsFirstRenderTimeSet;
            public TimeSpan FirstRenderTime;
            public CanvasRenderTarget RenderTarget;
            public Transform3DEffect TransformEffect;

            public volatile float Width;
            public volatile float Height;
            public volatile float X;
            public volatile float Y = 0;
            public volatile bool NeedToReleaseYSlot;

            #region For Advanced mode

            public readonly float DefinedStartX;
            public readonly float DefinedStartY;
            public readonly float DefinedEndX;
            public readonly float DefinedEndY;

            public int MarginLeft;
            public int MarginRight;
            public int MarginBottom;
            public readonly DanmakuAlignmentMode AlignmentMode;
            public readonly DanmakuAlignmentMode AnchorMode;

            public readonly byte DefinedStartAlpha;
            public readonly byte DefinedEndAlpha;

            public readonly ulong DefinedDurationMs;
            public readonly ulong DefinedTranslationDurationMs;
            public readonly ulong DefinedTranslationDelayMs;
            public readonly ulong DefinedAlphaDurationMs;
            public readonly ulong DefinedAlphaDelayMs;

            /// <summary>
            /// Degree
            /// </summary>
            public readonly float DefinedRotateZ;
            /// <summary>
            /// Degree
            /// </summary>
            public readonly float DefinedRotateY;

            public volatile float StartX;
            /// <summary>
            /// Starting Y position of which direction is according to mode
            /// </summary>
            public volatile float StartY;
            public volatile float EndX;
            public volatile float EndY;
            public volatile float TranslationSpeedX;
            public volatile float TranslationSpeedY;
            public volatile byte Alpha;

            #endregion

            private static uint _nextId = 1;

            public DanmakuRenderItem(DanmakuItem danmakuItem)
            {
                Id = GetNextId();
                HasBorder = danmakuItem.HasBorder;
                HasOutline = danmakuItem.HasOutline;
                AllowDensityControl = danmakuItem.AllowDensityControl;
                FontSize = danmakuItem.BaseFontSize;
                OutlineSize = danmakuItem.OutlineSize;
                FontFamilyName = danmakuItem.FontFamilyName;
                Text = danmakuItem.Text;
                IsBold = danmakuItem.IsBold;
                Mode = danmakuItem.Mode;
                TextColor = danmakuItem.TextColor;
                OutlineColor = danmakuItem.OutlineColor;

                if (Mode == DanmakuMode.Top
                    || Mode == DanmakuMode.Bottom
                    || Mode == DanmakuMode.Advanced)
                {
                    MarginBottom = danmakuItem.MarginBottom;
                    DefinedDurationMs = danmakuItem.DurationMs;
                }

                if (Mode == DanmakuMode.Advanced)
                {
                    DefinedStartX = danmakuItem.StartX;
                    DefinedStartY = danmakuItem.StartY;
                    DefinedEndX = danmakuItem.EndX;
                    DefinedEndY = danmakuItem.EndY;
                    MarginLeft = danmakuItem.MarginLeft;
                    MarginRight = danmakuItem.MarginRight;
                    AlignmentMode = danmakuItem.AlignmentMode;
                    AnchorMode = danmakuItem.AnchorMode;
                    DefinedStartAlpha = danmakuItem.StartAlpha;
                    DefinedEndAlpha = danmakuItem.EndAlpha;
                    TextColor.A = byte.MaxValue; // Always draw initial advanced danmaku at full opacity and use Alpha to control the rendering opacity
                    Alpha = DefinedStartAlpha;
                    DefinedTranslationDurationMs = danmakuItem.TranslationDurationMs;
                    DefinedTranslationDelayMs = danmakuItem.TranslationDelayMs;
                    DefinedAlphaDurationMs = danmakuItem.AlphaDurationMs;
                    DefinedAlphaDelayMs = danmakuItem.AlphaDelayMs;
                    DefinedRotateZ = danmakuItem.RotateZ;
                    DefinedRotateY = danmakuItem.RotateY;
                }
                else if (Mode == DanmakuMode.Subtitle)
                {
                    DefinedDurationMs = danmakuItem.DurationMs;
                }
            }

            public void Dispose()
            {
                if (RenderTarget != null)
                {
                    RenderTarget.Dispose();
                    RenderTarget = null;
                }
                if (TransformEffect != null)
                {
                    TransformEffect.Dispose();
                    TransformEffect = null;
                }
            }

            private static uint GetNextId()
            {
                if (_nextId == 0)
                {
                    // Avoid 0 as id value
                    _nextId = 1;
                }
                return _nextId++;
            }
        }

        [DebuggerDisplay("IsEnabled:{IsEnabled} Count:{RenderList.Count}")]
        private class RenderLayer
        {
            /// <summary>
            /// 0 (lowest) --> higher (topmost)
            /// </summary>
            private readonly uint LayerId;

            public readonly List<DanmakuRenderItem> RenderList = new List<DanmakuRenderItem>();
            public readonly DanmakuYSlotManager YSlotManager = new DanmakuYSlotManager(0);
            public readonly bool RequireStrictRenderOrder;
            public bool IsEnabled = true;

            public bool IsSubtitleLayer
            {
                get; private set;
            }

            public RenderLayer(uint layerId, bool requireStrictRenderOrder)
            {
                LayerId = layerId;
                RequireStrictRenderOrder = requireStrictRenderOrder;
            }

            public void UpdateYSlotManagerLength(uint newLength, float rollingLayerAreaRatio)
            {
                if (LayerId == DanmakuDefaultLayerDef.RollingLayerId || LayerId == DanmakuDefaultLayerDef.ReverseRollingLayerId)
                {
                    YSlotManager.UpdateLength((uint)(newLength * rollingLayerAreaRatio));
                }
                else if (LayerId == DanmakuDefaultLayerDef.TopLayerId)
                {
                    YSlotManager.UpdateLength((uint)(newLength * 0.75));
                }
                else if (LayerId == DanmakuDefaultLayerDef.BottomLayerId)
                {
                    YSlotManager.UpdateLength(newLength / 2);
                }
                else
                {
                    YSlotManager.UpdateLength(newLength);
                }
            }

            /// <summary>
            /// Thread safe
            /// </summary>
            public void Clear()
            {
                lock (RenderList)
                {
                    for (int i = 0; i < RenderList.Count; i++)
                    {
                        RenderList[i].Dispose();
                    }
                    RenderList.Clear();
                }
                YSlotManager.Clear();
            }

            public void SetSubtitleLayer(bool isSubtitleLayer)
            {
                IsSubtitleLayer = isSubtitleLayer;
            }
        }
    }
}
