using ChunkyImageLib.DataHolders;
using ChunkyImageLib.Operations;
using Drawie.Backend.Core.Surfaces;
using Drawie.Backend.Core.Surfaces.PaintImpl;
using Drawie.Numerics;

namespace ChunkyImageLib;

public static class IReadOnlyChunkyImageEx
{
    /// <summary>
    /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
    /// The region is taken from the most up to date version of the <see cref="ChunkyImage"/>
    /// </summary>
    /// <param name="image"><see cref="ChunkyImage"/> to extract the region from</param>
    /// <param name="fullResRegion">The region to extract</param>
    /// <param name="resolution">Chunk resolution</param>
    /// <param name="surface">Surface to draw onto</param>
    /// <param name="pos">Starting position on the surface</param>
    /// <param name="paint">Paint to use for drawing</param>
    public static void DrawMostUpToDateRegionOn
    (this IReadOnlyChunkyImage image, RectD fullResRegion, ChunkResolution resolution, Canvas surface,
        VecD pos, Paint? paint = null, SamplingOptions? sampling = null, bool drawPaintOnEmpty = false)
    {
        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint, sampling, drawPaintOnEmpty);
    }

    /// <summary>
    /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
    /// The region is taken from the most up to date version of the <see cref="ChunkyImage"/>
    /// </summary>
    /// <param name="image"><see cref="ChunkyImage"/> to extract the region from</param>
    /// <param name="fullResRegion">The region to extract</param>
    /// <param name="resolution">Chunk resolution</param>
    /// <param name="surface">Surface to draw onto</param>
    /// <param name="pos">Starting position on the surface</param>
    /// <param name="paint">Paint to use for drawing</param>
    public static void DrawMostUpToDateRegionOnWithAffected
    (this IReadOnlyChunkyImage image, RectD fullResRegion, ChunkResolution resolution, Canvas surface,
        AffectedArea affectedArea, VecD pos, Paint? paint = null, SamplingOptions? sampling = null, bool drawPaintOnEmpty = false)
    {
        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn,
            image.DrawCachedMostUpToDateChunkOn, affectedArea, paint, sampling, drawPaintOnEmpty);
    }

    /// <summary>
    /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
    /// The region is taken from the most up to date version of the <see cref="ChunkyImage"/>
    /// </summary>
    /// <param name="image"><see cref="ChunkyImage"/> to extract the region from</param>
    /// <param name="resolution">Chunk resolution</param>
    /// <param name="surface">Surface to draw onto</param>
    /// <param name="pos">Starting position on the surface</param>
    /// <param name="paint">Paint to use for drawing</param>
    public static void DrawMostUpToDateAffectedArea
    (this IReadOnlyChunkyImage image, RectD fullResRegion, ChunkResolution resolution, Canvas surface,
        AffectedArea affectedArea, VecD pos, Paint? paint = null, Paint? emptyPaint = null, SamplingOptions? sampling = null)
    {
        if (affectedArea.Chunks is null)
            return;

        int count = surface.Save();
        surface.ClipRect(new RectD(pos, fullResRegion.Size));

        VecI chunkTopLeft = OperationHelper.GetChunkPos((VecI)fullResRegion.TopLeft, ChunkyImage.FullChunkSize);
        VecI chunkBotRight = OperationHelper.GetChunkPos((VecI)fullResRegion.BottomRight, ChunkyImage.FullChunkSize);
        VecD offsetFullRes = (chunkTopLeft * ChunkyImage.FullChunkSize) - fullResRegion.Pos;
        VecD offsetTargetRes = offsetFullRes * resolution.Multiplier();

        foreach (var chunkPos in affectedArea.Chunks)
        {
            if (!image.DrawMostUpToDateChunkOn(chunkPos, resolution, surface,
                    offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint, sampling) && emptyPaint != null)
            {
                surface.DrawRect(new RectD(
                    offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos,
                    new VecD(resolution.PixelSize())), emptyPaint);
            }
        }
        surface.RestoreToCount(count);
    }

    /// <summary>
    /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
    /// The region is taken from the committed version of the <see cref="ChunkyImage"/>
    /// </summary>
    /// <param name="image"><see cref="ChunkyImage"/> to extract the region from</param>
    /// <param name="fullResRegion">The region to extract</param>
    /// <param name="resolution">Chunk resolution</param>
    /// <param name="surface">Surface to draw onto</param>
    /// <param name="pos">Starting position on the surface</param>
    /// <param name="paint">Paint to use for drawing</param>
    public static void DrawCommittedRegionOn
    (this IReadOnlyChunkyImage image, RectD fullResRegion, ChunkResolution resolution, Canvas surface,
        VecI pos, Paint? paint = null, SamplingOptions? samplingOptions = null, bool drawPaintOnEmpty = false)
    {
        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint, samplingOptions, drawPaintOnEmpty);
    }

    private static void DrawRegionOn(
        RectD fullResRegion,
        ChunkResolution resolution,
        Canvas surface,
        VecD pos,
        Func<VecI, ChunkResolution, Canvas, VecD, Paint?, SamplingOptions?, bool> drawingFunc,
        Paint? paint = null, SamplingOptions? samplingOptions = null, bool drawPaintOnEmpty = false)
    {
        int count = surface.Save();
        surface.ClipRect(new RectD(pos, fullResRegion.Size));

        VecI chunkTopLeft = OperationHelper.GetChunkPos((VecI)fullResRegion.TopLeft, ChunkyImage.FullChunkSize);
        VecI chunkBotRight = OperationHelper.GetChunkPos((VecI)fullResRegion.BottomRight, ChunkyImage.FullChunkSize);
        VecD offsetFullRes = (chunkTopLeft * ChunkyImage.FullChunkSize) - fullResRegion.Pos;
        VecD offsetTargetRes = offsetFullRes * resolution.Multiplier();

        for (int j = chunkTopLeft.Y; j <= chunkBotRight.Y; j++)
        {
            for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
            {
                var chunkPos = new VecI(i, j);
                if (!drawingFunc(chunkPos, resolution, surface,
                        offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint,
                        samplingOptions) && paint != null && drawPaintOnEmpty)
                {
                    surface.DrawRect(new RectD(
                        offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos,
                        new VecD(resolution.PixelSize())), paint);
                }
            }
        }

        surface.RestoreToCount(count);
    }

    private static void DrawRegionOn(
        RectD fullResRegion,
        ChunkResolution resolution,
        Canvas surface,
        VecD pos,
        Func<VecI, ChunkResolution, Canvas, VecD, Paint?, SamplingOptions?, bool> drawingFunc,
        Func<VecI, ChunkResolution, Canvas, VecD, Paint?, SamplingOptions?, bool> quickDrawingFunc,
        AffectedArea area,
        Paint? paint = null, SamplingOptions? samplingOptions = null, bool drawPaintOnEmpty = false)
    {
        int count = surface.Save();
        surface.ClipRect(new RectD(pos, fullResRegion.Size));

        VecI chunkTopLeft = OperationHelper.GetChunkPos((VecI)fullResRegion.TopLeft, ChunkyImage.FullChunkSize);
        VecI chunkBotRight = OperationHelper.GetChunkPos((VecI)fullResRegion.BottomRight, ChunkyImage.FullChunkSize);
        VecD offsetFullRes = (chunkTopLeft * ChunkyImage.FullChunkSize) - fullResRegion.Pos;
        VecD offsetTargetRes = offsetFullRes * resolution.Multiplier();

        for (int j = chunkTopLeft.Y; j <= chunkBotRight.Y; j++)
        {
            for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
            {
                var chunkPos = new VecI(i, j);
                if (area.Chunks != null && area.Chunks.Contains(chunkPos))
                {
                    if (!drawingFunc(chunkPos, resolution, surface,
                            offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint,
                            samplingOptions) && paint != null && drawPaintOnEmpty)
                    {
                        surface.DrawRect(new RectD(
                            offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos,
                            new VecD(resolution.PixelSize())), paint);
                    }
                }
                else
                {
                    if (!quickDrawingFunc(chunkPos, resolution, surface,
                            offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint,
                            samplingOptions) && paint != null && drawPaintOnEmpty)
                    {
                        surface.DrawRect(new RectD(
                            offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos,
                            new VecD(resolution.PixelSize())), paint);
                    }
                }
            }
        }

        surface.RestoreToCount(count);
    }
}