using ChunkyImageLib.Operations;
using PixiEditor.ChangeableDocument.ChangeInfos.Root;
using PixiEditor.ChangeableDocument.Enums;
using PixiEditor.DrawingApi.Core.Numerics;
using PixiEditor.DrawingApi.Core.Surface;
using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;

namespace PixiEditor.ChangeableDocument.Changes.Root;

internal sealed class FlipImage_Change : Change
{
    private readonly FlipType flipType;
    private List<Guid> membersToFlip;

    [GenerateMakeChangeAction]
    public FlipImage_Change(FlipType flipType, List<Guid>? membersToFlip = null)
    {
        this.flipType = flipType;
        membersToFlip ??= new List<Guid>();
        this.membersToFlip = membersToFlip;
    }
    
    public override bool InitializeAndValidate(Document target)
    {
        if (membersToFlip.Count > 0)
        {
            membersToFlip = target.ExtractLayers(membersToFlip);
            
            foreach (var layer in membersToFlip)
            {
                if (!target.HasMember(layer)) return false;
            }  
        }
        
        return true;
    }

    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
    {
        var changes = Flip(target);
        
        ignoreInUndo = false;
        return changes;
    }

    private void FlipImage(ChunkyImage img)
    {
        using Paint paint = new()
        {
            BlendMode = DrawingApi.Core.Surface.BlendMode.Src
        };

        RectI bounds = new RectI(VecI.Zero, img.LatestSize);
        if (membersToFlip.Count > 0)
        {
            var preciseBounds = img.FindTightCommittedBounds();
            if (preciseBounds.HasValue)
            {
                bounds = preciseBounds.Value;
            }
        }

        using Surface originalSurface = new(img.LatestSize);
        img.DrawMostUpToDateRegionOn(
            new RectI(VecI.Zero, img.LatestSize), 
            ChunkResolution.Full,
            originalSurface.DrawingSurface,
            VecI.Zero);

        using Surface flipped = new Surface(img.LatestSize);

        bool flipX = flipType == FlipType.Horizontal;
        bool flipY = flipType == FlipType.Vertical;
        
        flipped.DrawingSurface.Canvas.Save();
        flipped.DrawingSurface.Canvas.Scale(
            flipX ? -1 : 1, 
            flipY ? -1 : 1, 
            flipX ? bounds.X + (bounds.Width / 2f) : 0,
            flipY ? bounds.Y + (bounds.Height / 2f) : 0f);
        flipped.DrawingSurface.Canvas.DrawSurface(originalSurface.DrawingSurface, 0, 0, paint);
        flipped.DrawingSurface.Canvas.Restore();
        
        img.EnqueueClear();
        img.EnqueueDrawImage(VecI.Zero, flipped);
    }

    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
    {
        return Flip(target);
    }

    private OneOf<None, IChangeInfo, List<IChangeInfo>> Flip(Document target)
    {
        List<IChangeInfo> changes = new List<IChangeInfo>();

        target.ForEveryMember(member =>
        {
            if (membersToFlip.Count == 0 || membersToFlip.Contains(member.GuidValue))
            {
                if (member is Layer layer)
                {
                    FlipImage(layer.LayerImage);
                    changes.Add(
                        new LayerImageArea_ChangeInfo(member.GuidValue, layer.LayerImage.FindAffectedArea()));
                    layer.LayerImage.CommitChanges();
                }

                if (member.Mask is not null)
                {
                    FlipImage(member.Mask);
                    changes.Add(
                        new MaskArea_ChangeInfo(member.GuidValue, member.Mask.FindAffectedArea()));
                    member.Mask.CommitChanges();
                }
            }
        });

        return changes;
    }
}
