using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.Graphics.Effects;
using Windows.UI;

namespace BetterLyrics.WinUI3.ViewModels
{
    public partial class LyricsRendererViewModel
    {
        public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
        {
            // Blurred lyrics layer
            using var blurredLyrics = new CanvasCommandList(control);
            using (var blurredLyricsDs = blurredLyrics.CreateDrawingSession())
            {
                DrawBlurredLyrics(control, blurredLyricsDs);
            }

            if (_lastAlbumArtSwBitmap != null && _lastAlbumArtCanvasBitmap == null)
            {
                _lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
            }
            if (_albumArtSwBitmap != null && _albumArtCanvasBitmap == null)
            {
                _albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
            }

            using var combined = new CanvasCommandList(control);
            using var combinedDs = combined.CreateDrawingSession();

            if (_isDockMode)
            {
                DrawImmersiveBackground(control, combinedDs, 0f);
            }
            else if (_isDesktopMode)
            {
                DrawImmersiveBackground(control, combinedDs, 0f);
            }
            else
            {
                DrawAlbumArtBackground(control, combinedDs);
            }

            combinedDs.DrawImage(blurredLyrics);

            ds.DrawImage(combined);

            DrawAlbumArt(control, ds);
            DrawTitleAndArtist(control, ds);

            if (_isDebugOverlayEnabled)
            {
                var currentPlayingLine = _lyricsDataArr
                    .ElementAtOrDefault(_langIndex)
                    ?.LyricsLines.ElementAtOrDefault(_playingLineIndex);

                if (currentPlayingLine != null)
                {
                    GetLinePlayingProgress(
                        _playingLineIndex,
                        out int charStartIndex,
                        out int charLength,
                        out float charProgress
                    );

                    ds.DrawText(
                        $"[DEBUG]\n" +
                            $"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
                            $"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
                            $"Cur time {TotalTime + _positionOffset}\n" +
                            $"Lang size {_lyricsDataArr.Count}\n" +
                            $"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
                        new Vector2(10, 10),
                        ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
                    );

                    //for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
                    //{
                    //    LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
                    //    if (line != null)
                    //    {
                    //        ds.DrawText(
                    //            $"[{i}] {line.OriginalText} {line.HighlightOpacityTransition.Value}",
                    //            new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
                    //            ThemeTypeSent == ElementTheme.Light ? Colors.Black : Colors.White
                    //        );
                    //    }
                    //}
                }
            }
        }

        private void DrawBackgroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
        {
            float imageWidth = (float)canvasBitmap.Size.Width;
            float imageHeight = (float)canvasBitmap.Size.Height;

            float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2)) * 1.4f;
            float scaleFactor = targetSize / MathF.Min(imageWidth, imageHeight);

            float x = _canvasWidth / 2 - imageWidth * scaleFactor / 2;
            float y = _canvasHeight / 2 - imageHeight * scaleFactor / 2;

            // Original source: https://zhuanlan.zhihu.com/p/37178216
            float gain = _lyricsBgBrightnessTransition.Value;

            float whiteX = 1 - 0.5f * gain;
            float whiteY = 0.5f + 0.5f * gain;
            float blackX = 0.5f - 0.5f * gain;
            float blackY = 0 + 0.5f * gain;

            ds.DrawImage(new OpacityEffect
            {
                Source = new BrightnessEffect
                {
                    Source = new ScaleEffect
                    {
                        Scale = new Vector2(scaleFactor),
                        Source = canvasBitmap,
                    },
                    WhitePoint = new Vector2(whiteX, whiteY),
                    BlackPoint = new Vector2(blackX, blackY),
                },
                Opacity = opacity,
            }, new Vector2(x, y));
        }

        private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
        {
            if (opacity == 0) return;

            float imageWidth = (float)canvasBitmap.Size.Width;
            float imageHeight = (float)canvasBitmap.Size.Height;

            float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
            if (scaleFactor < 0.01f) return;

            float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;

            using var cornerRadiusMask = new CanvasCommandList(control.Device);
            using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
            cornerRadiusMaskDs.FillRoundedRectangle(
                new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
                cornerRadius, cornerRadius, Colors.White
            );

            ds.DrawImage(new OpacityEffect
            {
                Source = new AlphaMaskEffect
                {
                    Source = new ScaleEffect
                    {
                        Scale = new Vector2(scaleFactor),
                        Source = canvasBitmap,
                    },
                    AlphaMask = cornerRadiusMask,
                },
                Opacity = opacity,
            }, new Vector2(_albumArtXTransition.Value, _albumArtY)
            );
        }

        private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
        {
            //ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);

            using var overlappedCovers = new CanvasCommandList(control.Device);
            using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
            overlappedCoversDs.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);

            if (_lastAlbumArtCanvasBitmap != null)
            {
                DrawBackgroundImgae(control, overlappedCoversDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
            }
            if (_albumArtCanvasBitmap != null)
            {
                DrawBackgroundImgae(control, overlappedCoversDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
            }

            overlappedCoversDs.Transform = Matrix3x2.Identity;

            IGraphicsEffectSource blurredCover = new GaussianBlurEffect
            {
                BlurAmount = _albumArtBgBlurAmount,
                Source = overlappedCovers,
                BorderMode = EffectBorderMode.Soft,
                Optimization = EffectOptimization.Speed,
            };

            // 应用亚克力噪点效果
            // TODO: 没有写_coverAcrylicNoiseCanvasBitmap加载的代码
            if (_coverAcrylicEffectAmount > 0 && _coverAcrylicNoiseCanvasBitmap != null)
            {
                blurredCover = new BlendEffect
                {
                    Mode = BlendEffectMode.SoftLight,
                    Background = blurredCover,
                    Foreground = new OpacityEffect
                    {
                        Source = _coverAcrylicNoiseCanvasBitmap,
                        Opacity = _coverAcrylicEffectAmount / 100f,
                    },
                };
            }

            var coverOverlayEffect = new OpacityEffect
            {
                Opacity = _albumArtBgOpacity / 100f,
                Source = blurredCover,
            };
            ds.DrawImage(coverOverlayEffect);

            //ds.Transform = Matrix3x2.Identity;
        }

        private void DrawAlbumArt(ICanvasAnimatedControl control, CanvasDrawingSession ds)
        {
            using var albumArt = new CanvasCommandList(control.Device);
            using var albumArtDs = albumArt.CreateDrawingSession();
            if (_albumArtCanvasBitmap != null)
            {
                DrawForegroundImgae(control, albumArtDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
            }
            if (_lastAlbumArtCanvasBitmap != null)
            {
                DrawForegroundImgae(control, albumArtDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
            }

            using var opacity = new CanvasCommandList(control.Device);
            using var opacityDs = opacity.CreateDrawingSession();
            opacityDs.DrawImage(new GaussianBlurEffect
            {
                Source = albumArt,
                BlurAmount = 12f,
                Optimization = EffectOptimization.Quality,
            });
            opacityDs.DrawImage(albumArt);

            ds.DrawImage(new OpacityEffect
            {
                Source = opacity,
                Opacity = _albumArtOpacityTransition.Value
            });
        }

        private void DrawTitleAndArtist(ICanvasAnimatedControl control, CanvasDrawingSession ds)
        {
            if (_lastSongTitle != null || _lastSongArtist != null)
            {
                DrawSingleTitleAndArtist(control, ds, _lastSongTitle, _lastSongArtist, 1 - _songInfoOpacityTransition.Value);
            }
            if (_songTitle != null || _songArtist != null)
            {
                DrawSingleTitleAndArtist(control, ds, _songTitle, _songArtist, _songInfoOpacityTransition.Value);
            }
        }

        private void DrawSingleTitleAndArtist(ICanvasAnimatedControl control, CanvasDrawingSession ds, string? title, string? artist, float opacity)
        {
            CanvasTextLayout titleLayout = new(
                control, title ?? string.Empty,
                _titleTextFormat, _albumArtSize, _canvasHeight
            );
            CanvasTextLayout artistLayout = new(
                control, artist ?? string.Empty,
                _artistTextFormat, _albumArtSize, _canvasHeight
            );
            ds.DrawTextLayout(
                titleLayout,
                new Vector2(_albumArtXTransition.Value, _titleY),
                _bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 255 * opacity)));
            ds.DrawTextLayout(
                artistLayout,
                new Vector2(_albumArtXTransition.Value, _titleY + (float)titleLayout.LayoutBounds.Height),
                _bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 128 * opacity)));
        }

        private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
        {
            var currentPlayingLine = _lyricsDataArr
                .ElementAtOrDefault(_langIndex)
                ?.LyricsLines.ElementAtOrDefault(_playingLineIndex);

            if (currentPlayingLine == null)
            {
                return;
            }

            for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
            {
                var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
                if (line == null) continue;

                var textLayout = line.CanvasTextLayout;
                if (textLayout == null) continue;

                var position = new Vector2(line.Position.X, line.Position.Y);

                float layoutWidth = (float)textLayout.LayoutBounds.Width;
                float layoutHeight = (float)textLayout.LayoutBounds.Height;

                if (layoutWidth <= 0 || layoutHeight <= 0) continue;

                float centerX = position.X;
                float centerY = position.Y + layoutHeight / 2;

                switch (_lyricsAlignmentType)
                {
                    case TextAlignmentType.Left:
                        textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
                        break;
                    case TextAlignmentType.Center:
                        textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
                        centerX += _maxLyricsWidth / 2;
                        break;
                    case TextAlignmentType.Right:
                        textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
                        centerX += _maxLyricsWidth;
                        break;
                    default:
                        break;
                }

                float xOffset = _lyricsXTransition.Value;
                float yOffset = _canvasYScrollTransition.Value + _canvasHeight / 2;

                // 组合变换：缩放 -> 旋转 -> 平移
                ds.Transform =
                    Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
                    * Matrix3x2.CreateRotation(line.AngleTransition.Value, currentPlayingLine.Position)
                    * Matrix3x2.CreateTranslation(xOffset, yOffset);

                // Create the background lyrics line with stroke and fill
                using var bgLyrics = new CanvasCommandList(control.Device);
                using var bgLyricsDs = bgLyrics.CreateDrawingSession();

                // Create the foreground lyrics line with stroke and fill
                using var fgLyrics = new CanvasCommandList(control.Device);
                using var fgLyricsDs = fgLyrics.CreateDrawingSession();

                // 创建文字几何体
                using var textGeometry = CanvasGeometry.CreateText(textLayout);
                if (_isDesktopMode)
                {
                    bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
                    fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
                }

                bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
                fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充

                using var combined = new CanvasCommandList(control.Device);
                using var combinedDs = combined.CreateDrawingSession();

                // Mock gradient blurred lyrics layer
                // 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层（背景歌词层次）
                // Current line will not be blurred
                combinedDs.DrawImage(
                    new GaussianBlurEffect
                    {
                        Source = new OpacityEffect { Source = bgLyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
                        BlurAmount = line.BlurAmountTransition.Value,
                        Optimization = EffectOptimization.Quality,
                        BorderMode = EffectBorderMode.Soft,
                    }
                );

                if (line.HighlightOpacityTransition.Value != 0)
                {
                    // 再叠加高亮行歌词层（前景歌词层）
                    using var mask = new CanvasCommandList(control.Device);
                    using var maskDs = mask.CreateDrawingSession();

                    using var highlightMask = new CanvasCommandList(control.Device);
                    using var highlightMaskDs = highlightMask.CreateDrawingSession();

                    if (i == _playingLineIndex)
                    {
                        GetLinePlayingProgress(
                            i,
                            out int charStartIndex,
                            out int charLength,
                            out float charProgress
                        );
                        var regions = textLayout.GetCharacterRegions(0, charStartIndex);
                        var highlightRegion = textLayout
                            .GetCharacterRegions(charStartIndex, charLength)
                            .FirstOrDefault();
                        if (regions.Length > 0)
                        {
                            // Draw the mask for the current line
                            for (int j = 0; j < regions.Length; j++)
                            {
                                var region = regions[j];
                                var rect = new Rect(
                                    region.LayoutBounds.X,
                                    region.LayoutBounds.Y + position.Y,
                                    region.LayoutBounds.Width,
                                    region.LayoutBounds.Height
                                );
                                maskDs.FillRectangle(rect, Color.FromArgb(255, 128, 128, 128));
                            }
                        }

                        float highlightTotalWidth = (float)highlightRegion.LayoutBounds.Width;
                        // Draw the highlight for the current character
                        float highlightWidth = highlightTotalWidth * charProgress;

                        float fadingWidth = (float)highlightRegion.LayoutBounds.Height / 2;

                        // Rects
                        var highlightRect = new Rect(
                            highlightRegion.LayoutBounds.X,
                            highlightRegion.LayoutBounds.Y + position.Y,
                            highlightWidth,
                            highlightRegion.LayoutBounds.Height
                        );

                        var fadeInRect = new Rect(
                            highlightRect.Right - fadingWidth,
                            highlightRegion.LayoutBounds.Y + position.Y,
                            fadingWidth,
                            highlightRegion.LayoutBounds.Height
                        );
                        var fadeOutRect = new Rect(
                            highlightRect.Right,
                            highlightRegion.LayoutBounds.Y + position.Y,
                            fadingWidth,
                            highlightRegion.LayoutBounds.Height
                        );

                        // Brushes
                        using var fadeInBrush = GetHorizontalFillBrush(
                            control,
                            [(0f, 0f), (1f, 1f)],
                            (float)highlightRect.Right - fadingWidth,
                            fadingWidth
                        );
                        using var fadeOutBrush = GetHorizontalFillBrush(
                            control,
                            [(0f, 1f), (1f, 0f)],
                            (float)highlightRect.Right,
                            fadingWidth
                        );

                        maskDs.FillRectangle(highlightRect, Color.FromArgb(255, 128, 128, 128));
                        maskDs.FillRectangle(fadeOutRect, fadeOutBrush);

                        highlightMaskDs.FillRectangle(fadeInRect, fadeInBrush);
                        highlightMaskDs.FillRectangle(fadeOutRect, fadeOutBrush);
                    }
                    else
                    {
                        float height = 0f;
                        //var regions = textLayout.GetCharacterRegions(0, string.Join("", line.LyricsChars.Select(x => x.Text)).Length);
                        var regions = textLayout.GetCharacterRegions(0, line.OriginalText.Length);
                        if (regions.Length > 0)
                        {
                            height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
                        }

                        maskDs.FillRectangle(
                            new Rect(
                                textLayout.LayoutBounds.X,
                                position.Y,
                                textLayout.LayoutBounds.Width,
                                height
                            ),
                            Colors.White
                        );
                    }

                    using var opacityEffect = new OpacityEffect
                    {
                        Source = new BlendEffect
                        {
                            Background = _isLyricsGlowEffectEnabled
                                    ? new GaussianBlurEffect
                                    {
                                        Source = new AlphaMaskEffect
                                        {
                                            Source = fgLyrics,
                                            AlphaMask = _lyricsGlowEffectScope switch
                                            {
                                                LineRenderingType.CurrentChar => highlightMask,
                                                LineRenderingType.LineStartToCurrentChar => mask,
                                                LineRenderingType.CurrentLine => fgLyrics,
                                                _ => mask,
                                            },
                                        },
                                        BlurAmount = _lyricsGlowEffectAmount,
                                        Optimization = EffectOptimization.Quality,
                                    }
                                    : new CanvasCommandList(control.Device),
                            Foreground = new AlphaMaskEffect
                            {
                                Source = fgLyrics,
                                AlphaMask = _lyricsHighlightScope switch
                                {
                                    LineRenderingType.CurrentChar => highlightMask,
                                    LineRenderingType.LineStartToCurrentChar => mask,
                                    LineRenderingType.CurrentLine => fgLyrics,
                                    _ => mask,
                                },
                            },
                        },
                        Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
                    };

                    combinedDs.DrawImage(opacityEffect);

                    if (i == _playingLineIndex)
                    {
                        if (_isLyricsFloatAnimationEnabled)
                        {
                            ds.DrawImage(new DisplacementMapEffect
                            {
                                Source = combined,
                                Displacement = mask,
                                XChannelSelect = EffectChannelSelect.Red,
                                YChannelSelect = EffectChannelSelect.Alpha,
                                Amount = 1f
                            });
                        }
                        else
                        {
                            ds.DrawImage(combined);
                        }
                    }
                    else
                    {
                        ds.DrawImage(combined);
                    }
                }
                else
                {
                    ds.DrawImage(combined);
                }

                // Reset scale
                ds.Transform = Matrix3x2.Identity;
            }
        }

        private void DrawImmersiveBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds, float radius)
        {
            CanvasCommandList list = new(control.Device);
            using var listDs = list.CreateDrawingSession();
            listDs.FillRoundedRectangle(
                new Rect(0, 0, _canvasWidth, _canvasHeight),
                radius,
                radius,
                _immersiveBgTransition.Value
            );
            ds.DrawImage(new OpacityEffect
            {
                Source = list,
                Opacity = _immersiveBgOpacityTransition.Value
            });
        }

        private CanvasLinearGradientBrush GetHorizontalFillBrush(
            ICanvasAnimatedControl control,
            List<(float position, float opacity)> stops,
            float startX,
            float width
        )
        {
            return new CanvasLinearGradientBrush(
                control,
                stops
                    .Select(stops => new CanvasGradientStop
                    {
                        Position = stops.position,
                        Color = Color.FromArgb((byte)(stops.opacity * 255), 128, 128, 128),
                    })
                    .ToArray()
            )
            {
                StartPoint = new Vector2(startX, 0),
                EndPoint = new Vector2(startX + width, 0),
            };
        }
    }
}
