using Avalonia;
using Avalonia.Animation;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using PicView.Core.DebugTools;
using SkiaSharp;

namespace PicView.Avalonia.AnimatedImage;

public class WebpInstance : IGifInstance
{
    private readonly SKCodec? _codec;
    private readonly List<TimeSpan> _frameTimes;

    private readonly WriteableBitmap? _targetBitmap;
    private int _currentFrameIndex;
    private uint _iterationCount;
    private TimeSpan _totalTime;

    public WebpInstance(Stream currentStream)
    {
        if (!currentStream.CanSeek)
        {
            throw new InvalidDataException("The provided stream is not seekable.");
        }

        if (!currentStream.CanRead)
        {
            throw new InvalidOperationException("Can't read the stream provided.");
        }

        currentStream.Seek(0, SeekOrigin.Begin);

        CurrentCts = new CancellationTokenSource();

        var managedStream = new SKManagedStream(currentStream);
        _codec = SKCodec.Create(managedStream);

        var pixSize = new PixelSize(_codec.Info.Width, _codec.Info.Height);

        // Different on os: https://github.com/mono/SkiaSharp/issues/1492#issuecomment-689015409
        // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault
        var format = SKImageInfo.PlatformColorType switch
        {
            SKColorType.Bgra8888 => PixelFormat.Bgra8888,
            SKColorType.Rgba8888 => PixelFormat.Rgba8888,
            _ => throw new NotSupportedException($"Unsupported color type: {SKImageInfo.PlatformColorType}")
        };

        _targetBitmap = new WriteableBitmap(pixSize, new Vector(96, 96), format, AlphaFormat.Opaque);
        GifPixelSize = pixSize;

        _totalTime = TimeSpan.Zero;

        _frameTimes = _codec.FrameInfo.Select(frame =>
            {
                _totalTime = _totalTime.Add(TimeSpan.FromMilliseconds(frame.Duration));
                return _totalTime;
            })
            .ToList();

        RenderFrame(_codec, _targetBitmap, 0);
    }

    public IterationCount IterationCount { get; set; }
    public bool AutoStart => true;

    public CancellationTokenSource CurrentCts { get; }

    public int GifFrameCount => _frameTimes.Count;

    public PixelSize GifPixelSize { get; }

    public void Dispose()
    {
        if (IsDisposed)
        {
            return;
        }

        GC.SuppressFinalize(this);

        IsDisposed = true;
        CurrentCts.Cancel();
        _targetBitmap?.Dispose();
        _codec?.Dispose();
    }

    public bool IsDisposed { get; private set; }

    public WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed)
    {
        if (!IterationCount.IsInfinite && _iterationCount > IterationCount.Value)
        {
            return null;
        }

        if (CurrentCts.IsCancellationRequested || _targetBitmap is null)
        {
            return null;
        }

        var elapsedTicks = stopwatchElapsed.Ticks;
        var timeModulus = TimeSpan.FromTicks(elapsedTicks % _totalTime.Ticks);
        var targetFrame = _frameTimes.FirstOrDefault(x => timeModulus < x);
        var currentFrame = _frameTimes.IndexOf(targetFrame);
        if (currentFrame == -1)
        {
            currentFrame = 0;
        }

        if (_currentFrameIndex == currentFrame)
        {
            return _targetBitmap;
        }

        _iterationCount = (uint)(elapsedTicks / _totalTime.Ticks);

        return ProcessFrameIndex(currentFrame);
    }

    private static void RenderFrame(SKCodec codec, WriteableBitmap targetBitmap, int index)
    {
        codec.GetFrameInfo(index, out var frameInfo);

        var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);
        var decodeInfo = info.WithAlphaType(frameInfo.AlphaType);

        using var frameBuffer = targetBitmap.Lock();

        var result = codec.GetPixels(decodeInfo, frameBuffer.Address, new SKCodecOptions(index));

        if (result != SKCodecResult.Success)
        {
            throw new InvalidDataException($"Could not decode frame {index} of {codec.FrameCount}.");
        }
    }

    private static void RenderFrame(SKCodec codec, WriteableBitmap targetBitmap, int index, int priorIndex)
    {
        codec.GetFrameInfo(index, out var frameInfo);

        var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);
        var decodeInfo = info.WithAlphaType(frameInfo.AlphaType);

        using var frameBuffer = targetBitmap.Lock();

        try
        {
            var result = codec.GetPixels(decodeInfo, frameBuffer.Address, new SKCodecOptions(index, priorIndex));
            if (result != SKCodecResult.Success)
            {
                throw new InvalidDataException($"Could not decode frame {index} of {codec.FrameCount}.");
            }
        }
        catch (Exception e)
        {
            DebugHelper.LogDebug(nameof(WebpInstance), nameof(RenderFrame), e);
        }
    }

    internal WriteableBitmap ProcessFrameIndex(int frameIndex)
    {
        if (_codec is null)
        {
            throw new InvalidOperationException("The codec is null.");
        }

        if (_targetBitmap is null)
        {
            throw new InvalidOperationException("The target bitmap is null.");
        }

        RenderFrame(_codec, _targetBitmap, frameIndex, _currentFrameIndex);
        _currentFrameIndex = frameIndex;

        return _targetBitmap;
    }
}