Ui/Paint/PaintUi.razor.cs
using System;
using System.IO;
using System.Text;
using Clover.Player;
using Clover.Ui.Tools;
using Clover.Utilities;
using Sandbox.Diagnostics;
using Sandbox.UI;

namespace Clover.Ui;

public partial class PaintUi
{
	public struct DecalEntry
	{
		public Decals.DecalData Decal;

		// public string ResourcePath;
		public string FileName;
	}

	// TODO: maybe don't hardcode this
	public enum PaintType
	{
		Decal = 1,
		Pumpkin = 2,
		Image = 3,
		Snowman = 4,
	}

	public enum PaintTool
	{
		Pencil,
		Eraser,

		// Line,
		Fill,
		Spray,
		Eyedropper,

		Line,
		Rectangle,
		Circle,

		Move,
		Clone,

		Dodge,
		Burn,
	}

	private PaintTool _previousTool = PaintTool.Pencil;

	private PaintTool _currentTool = PaintTool.Pencil;

	private PaintTool CurrentTool
	{
		get => _currentTool;
		set
		{
			_previousTool = _currentTool;
			_currentTool = value;
			_isDrawing = false;
			_isMoving = false;
			ClearPreview();
		}
	}

	private PaintType _currentPaintType = PaintType.Decal;

	private List<DecalEntry> Decals = new();
	private List<Texture> Images = new();

	private Texture DrawTexture;

	// private Texture GridTexture;
	private Texture PreviewTexture;

	private byte[] DrawTextureData;

	private Panel Window;
	private Panel CanvasContainer;
	private Panel CanvasSquare;
	private Panel CanvasImage;
	private Panel Grid;
	private Panel Crosshair;
	private Panel PreviewOverlay;

	private bool Monochrome = false;

	private string PaletteName = "windows-95-256-colours-1x";
	private List<Color32> Palette = new();

	private byte[] FavoriteColors = new byte[FavoriteColorAmount];

	private int LeftPaletteIndex = 0;
	private int RightPaletteIndex = 1;
	private int CurrentPaletteIndex = 0;

	private Decals.DecalData CurrentDecalData;
	private string CurrentFileName = "";
	private string CurrentName = "";

	// private int TextureSize = 32;
	private Vector2Int TextureSize = new(32, 32);

	private int BaseCanvasSize = 512;
	private float CanvasZoom = 10.0f;
	private float MinCanvasZoom = 0.1f;
	private float MaxCanvasZoom = 20f;

	// private int CanvasSize;

	private static int FavoriteColorAmount = 40;

	private bool ShowPalettes = false;
	private bool ShowFileActions = false;
	private bool ShowFavoritesEditor = false;

	private int SelectedFavorite = -1; // TODO: don't allow setting when -1

	private bool ShowGrid = true;

	private Vector2Int ClipboardSize;
	private byte[] ClipboardData;

	// private Color PreviewColor = Color.Red;
	private Color PreviewColor => GetCurrentColor();

	// private int BrushSize = 1;

	private Dictionary<PaintTool, int> BrushSizes = new()
	{
		{ PaintTool.Pencil, 1 },
		{ PaintTool.Eraser, 1 },
		{ PaintTool.Fill, 1 },
		{ PaintTool.Spray, 1 },
		{ PaintTool.Eyedropper, 1 },
		{ PaintTool.Line, 1 },
		{ PaintTool.Rectangle, 1 },
		{ PaintTool.Circle, 1 },
		{ PaintTool.Move, 1 },
		{ PaintTool.Clone, 1 },
		{ PaintTool.Dodge, 1 },
		{ PaintTool.Burn, 1 },
	};

	private bool ShowBrushSizeForCurrentTool()
	{
		return CurrentTool is PaintTool.Pencil or PaintTool.Spray or PaintTool.Eraser or PaintTool.Burn
			or PaintTool.Dodge;
	}

	private int BrushSize
	{
		get => BrushSizes[CurrentTool];
		set => BrushSizes[CurrentTool] = value;
	}

	private Color32 ForegroundColor => Palette.ElementAtOrDefault( LeftPaletteIndex );
	private Color32 BackgroundColor => Palette.ElementAtOrDefault( RightPaletteIndex );

	private Color GetCurrentColor()
	{
		if ( CurrentPaletteIndex >= Palette.Count )
		{
			Log.Error( $"CurrentPaletteIndex {CurrentPaletteIndex} is out of bounds" );
			return default;
		}

		return Palette[CurrentPaletteIndex];
	}

	public void OpenPaint( PaintType type, int width, int height, bool monochrome )
	{
		if ( !ValidateTextureSize( width, height ) )
		{
			Log.Error( "Invalid texture size" );
			return;
		}

		_currentPaintType = type;
		TextureSize = new Vector2Int( width, height );
		Monochrome = monochrome;
		Enabled = true;

		if ( Monochrome )
		{
			SetPalette( "monochrome" );
		}
		else
		{
			SetPalette( "windows-95-256-colours-1x" );
		}

		InitialiseTexture();
		LoadFavoriteColors();
	}

	private static bool ValidateTextureSize( int width, int height )
	{
		if ( width < 1 || height < 1 )
		{
			return false;
		}

		if ( width > 1024 || height > 1024 )
		{
			return false;
		}

		// check if it's a power of 2 (thanks copilot)
		if ( (width & (width - 1)) != 0 || (height & (height - 1)) != 0 )
		{
			return false;
		}

		return true;
	}

	protected override void OnStart()
	{
		base.OnStart();

		Palette = Utilities.Decals.GetPalette( PaletteName ).ToList();

		InitialiseTexture();

		Panel.ButtonInput = PanelInputType.UI;

		Enabled = false;

		PopulateDecals();
		PopulateImages();

		LoadFavoriteColors();
	}

	private void ResetPaint()
	{
		CurrentDecalData = new Decals.DecalData();
		CurrentFileName = "";
		CurrentName = "";
		ZoomReset();

		_isDrawing = false;
		_isMoving = false;

		RedoStack.Clear();
		UndoStack.Clear();

		ClearTexture();
	}

	/*private void ResetDecalPaint()
	{
		Log.Info( "[Paint] Reset" );

		// PaletteName = "windows-95-256-colours-1x";
		// Palette = Utilities.Decals.GetPalette( PaletteName ).ToList();
		SetPalette( "windows-95-256-colours-1x" );

		LeftPaletteIndex = Utilities.Decals.GetClosestPaletteColor( Palette.ToArray(), Color.Black );
		RightPaletteIndex = Utilities.Decals.GetClosestPaletteColor( Palette.ToArray(), Color.White );
		CurrentPaletteIndex = LeftPaletteIndex;

		UpdateCanvas();
		Clear();
	}

	private void ResetPumpkinPaint()
	{
		Log.Info( "[Paint] Reset" );

		// PaletteName = "monochrome";
		// Palette = Utilities.Decals.GetPalette( PaletteName ).ToList();
		SetPalette( "monochrome" );

		LeftPaletteIndex = 0;
		RightPaletteIndex = 1;
		CurrentPaletteIndex = LeftPaletteIndex;

		UpdateCanvas();
		Clear();
	}*/

	private void New()
	{
		Log.Info( "[Paint] New" );

		ResetPaint();
	}

	private void InitialiseTexture()
	{
		Log.Info( "[Paint] Initialising texture" );

		DrawTexture = Texture.Create( TextureSize.x, TextureSize.y ).WithDynamicUsage().Finish();
		DrawTextureData = new byte[TextureSize.x * TextureSize.y];
		ClearTexture();

		UndoStack = new();

		/*// draw line grid with guide lines
		GridTexture = Texture.Create( 1024, 1024 ).Finish();
		var pixels = new Color32[1024 * 1024];
		for ( var x = 0; x < 1024; x++ )
		{
			for ( var y = 0; y < 1024; y++ )
			{
				var color = Color.Transparent;
				if ( x % 32 == 0 || y % 32 == 0 )
				{
					color = Color.Gray;
				}

				if ( x % 128 == 0 || y % 128 == 0 )
				{
					color = Color.White;
				}

				pixels[x + y * 1024] = color;
			}
		}

		GridTexture.Update( pixels );*/

		// draw preview overlay
		PreviewTexture = Texture.Create( TextureSize.x, TextureSize.y ).WithDynamicUsage().Finish();
	}


	private void SetColor( PanelEvent ev, int index )
	{
		if ( ev is not MousePanelEvent e )
		{
			Log.Warning( "Event is not MousePanelEvent" );
			return;
		}

		Log.Info( $"Setting color to {index}" );

		if ( e.MouseButton == MouseButtons.Left )
		{
			Log.Info( $"Setting left color to {index}" );
			LeftPaletteIndex = index;
		}
		else if ( e.MouseButton == MouseButtons.Right )
		{
			Log.Info( $"Setting right color to {index}" );
			RightPaletteIndex = index;
		}
		else
		{
			Log.Warning( "Unknown mouse button" );
		}
	}

	private void SetPalette( string name )
	{
		PaletteName = name;
		Palette = Utilities.Decals.GetPalette( PaletteName ).ToList();

		LeftPaletteIndex = Utilities.Decals.GetClosestPaletteColor( Palette.ToArray(), Color.Black );
		RightPaletteIndex = Utilities.Decals.GetClosestPaletteColor( Palette.ToArray(), Color.White );
		CurrentPaletteIndex = LeftPaletteIndex;

		if ( DrawTextureData != null ) PushByteDataToTexture();
		LoadFavoriteColors();
	}

	private bool _isDrawing;

	private Vector2Int GetCurrentMousePixel()
	{
		if ( !CanvasImage.IsValid() )
		{
			return Vector2Int.Zero;
		}

		var mousePosition = Mouse.Position;
		var canvasPosition = CanvasImage.Box.Rect.Position;
		var canvasSize = CanvasImage.Box.Rect.Size;

		var mousePositionInCanvas = mousePosition - canvasPosition;
		var mousePositionInCanvasNormalized = mousePositionInCanvas / canvasSize;

		var x = (int)(mousePositionInCanvasNormalized.x * DrawTexture.Width);
		var y = (int)(mousePositionInCanvasNormalized.y * DrawTexture.Height);

		return new Vector2Int( x, y );
	}

	public Vector2Int GetCurrentBrushPosition()
	{
		var mousePosition = GetCurrentMousePixel();

		var brushSizeOffset = BrushSize == 1 ? 0 : MathF.Ceiling( BrushSize / 2f );

		var brushPosition = new Vector2Int( (int)Math.Round( mousePosition.x - brushSizeOffset ),
			(int)Math.Round( mousePosition.y - brushSizeOffset ) );
		return brushPosition;
	}

	private bool IsMouseInsideCanvas()
	{
		var mousePosition = Mouse.Position;
		var canvasPosition = CanvasImage.Box.Rect.Position;
		var canvasSize = CanvasImage.Box.Rect.Size;

		return mousePosition.x >= canvasPosition.x && mousePosition.x <= canvasPosition.x + canvasSize.x &&
		       mousePosition.y >= canvasPosition.y && mousePosition.y <= canvasPosition.y + canvasSize.y;
	}

	private bool IsMouseInsideCanvasContainer()
	{
		var mousePosition = Mouse.Position;
		var canvasPosition = CanvasContainer.Box.Rect.Position;
		var canvasSize = CanvasContainer.Box.Rect.Size;

		return mousePosition.x >= canvasPosition.x && mousePosition.x <= canvasPosition.x + canvasSize.x &&
		       mousePosition.y >= canvasPosition.y && mousePosition.y <= canvasPosition.y + canvasSize.y;
	}


	protected override void OnUpdate()
	{
		if ( Input.Released( "PaintUndo" ) )
		{
			Undo();
			return;
		}
		else if ( Input.Released( "PaintRedo" ) )
		{
			Redo();
			return;
		}
		else if ( Input.Released( "PaintPencil" ) )
		{
			CurrentTool = PaintTool.Pencil;
		}
		/*else if ( Input.Released( "PaintEraser" ) )
		{
			CurrentTool = PaintTool.Eraser;
		}
		else if ( Input.Released( "PaintFill" ) )
		{
			CurrentTool = PaintTool.Fill;
		}
		else if ( Input.Released( "PaintSpray" ) )
		{
			CurrentTool = PaintTool.Spray;
		}*/
		else if ( Input.Released( "PaintEyedropper" ) )
		{
			CurrentTool = PaintTool.Eyedropper;
		}

		/*if ( !IsMouseInsideCanvas() )
		{
			return;
		}*/

		var brushPosition = GetCurrentBrushPosition();

		DrawCrosshair( brushPosition );

		// PreviewTexture.Update( Color.Red, brushPosition.x, brushPosition.y );

		if ( (CurrentTool == PaintTool.Move || CurrentTool == PaintTool.Clone) && _isMoving )
		{
			// Log.Info( "Moving" );
			ClearPreview();
			PasteClipboardToPreview( brushPosition );
		}

		if ( _isDrawing )
		{
			if ( CurrentTool == PaintTool.Pencil )
			{
				Draw( brushPosition );
			}
			else if ( CurrentTool == PaintTool.Fill )
			{
				Fill( brushPosition );
			}
			else if ( CurrentTool == PaintTool.Spray )
			{
				Spray( brushPosition );
			}
			else if ( CurrentTool == PaintTool.Eraser )
			{
				Eraser( brushPosition );
			}
			else if ( CurrentTool == PaintTool.Line )
			{
				LinePreview( brushPosition );
			}
			else if ( CurrentTool == PaintTool.Rectangle || CurrentTool == PaintTool.Move ||
			          CurrentTool == PaintTool.Clone )
			{
				RectanglePreview( brushPosition );
			}
			else if ( CurrentTool == PaintTool.Circle )
			{
				CirclePreview( brushPosition );
			}
		}
	}


	private void DrawCrosshair( Vector2Int brushPosition )
	{
		if ( !CanvasImage.IsValid() )
		{
			Log.Warning( "CanvasImage is not valid in DrawCrosshair" );
			return;
		}

		// var texturePixelScreenSize = CanvasSize / TextureSize.y;
		var texturePixelScreenSize = DrawTexture.Width * CanvasZoom / TextureSize.x;

		var crosshairRound = false;

		var crosshairX = CanvasImage.Box.Left * Panel.ScaleFromScreen;
		crosshairX += texturePixelScreenSize * brushPosition.x;
		crosshairX += CanvasContainer.ScrollOffset.x * Panel.ScaleFromScreen;
		crosshairX -= CanvasContainer.Box.Left * Panel.ScaleFromScreen;

		var crosshairY = CanvasImage.Box.Top * Panel.ScaleFromScreen;
		crosshairY += texturePixelScreenSize * brushPosition.y;
		crosshairY += CanvasContainer.ScrollOffset.y * Panel.ScaleFromScreen;
		crosshairY -= CanvasContainer.Box.Top * Panel.ScaleFromScreen;

		var crosshairSize = texturePixelScreenSize * BrushSize;

		if ( CurrentTool == PaintTool.Fill )
		{
			// crosshairSize = texturePixelScreenSize;
		}
		else if ( CurrentTool == PaintTool.Spray )
		{
			crosshairSize *= 6;
			crosshairX -= crosshairSize / 2f;
			crosshairY -= crosshairSize / 2f;
			crosshairRound = true;
		}


		Crosshair.Style.Left = Length.Pixels( (int)crosshairX );
		Crosshair.Style.Top = Length.Pixels( (int)crosshairY );
		Crosshair.Style.Width = crosshairSize;
		Crosshair.Style.Height = crosshairSize;


		Crosshair.Style.BorderTopLeftRadius = crosshairRound ? Length.Percent( 100 ) : Length.Pixels( 0 );
		Crosshair.Style.BorderTopRightRadius = crosshairRound ? Length.Percent( 100 ) : Length.Pixels( 0 );
		Crosshair.Style.BorderBottomLeftRadius = crosshairRound ? Length.Percent( 100 ) : Length.Pixels( 0 );
		Crosshair.Style.BorderBottomRightRadius = crosshairRound ? Length.Percent( 100 ) : Length.Pixels( 0 );
	}

	public void PushRectToBoth( Rect rect, int colorIndex = -1 )
	{
		PushRectToByteData( rect, colorIndex );
		PushRectToTexture( rect, colorIndex );
	}

	private void PushByteDataToTexture()
	{
		Assert.NotNull( DrawTextureData, "DrawTextureData is null" );
		Assert.NotNull( Palette, "Palette is null" );
		Assert.True( Palette.Count > 0, "Palette is empty" );
		Assert.True( DrawTextureData.Length > 0, "DrawTextureData is empty" );
		Assert.True( DrawTextureData.Length == DrawTexture.Width * DrawTexture.Height,
			"DrawTextureData length does not match texture size" );

		var data = Utilities.Decals.ByteArrayToColor32( DrawTextureData, Palette.ToArray() );

		DrawTexture.Update( data, 0, 0,
			DrawTexture.Width,
			DrawTexture.Height );
	}

	private void PushRectToTexture( Rect rect, int colorIndex = -1 )
	{
		DrawTexture.Update( colorIndex == -1 ? GetCurrentColor() : Palette[colorIndex], rect );
	}

	private void PushRectToByteData( Rect rect, int colorIndex = -1 )
	{
		for ( var x = (int)rect.Left; x < rect.Left + rect.Width; x++ )
		{
			for ( var y = (int)rect.Top; y < rect.Top + rect.Height; y++ )
			{
				var index = x + y * DrawTexture.Width;
				if ( index >= 0 && index < DrawTextureData.Length )
				{
					DrawTextureData[index] = (byte)(colorIndex == -1 ? CurrentPaletteIndex : colorIndex);
				}
			}
		}
	}

	/// <summary>
	///  Draw a line between two points using Bresenham's line algorithm
	/// </summary>
	/// <param name="lastBrushPosition"></param>
	/// <param name="brushPosition"></param>
	private void DrawLineBetween( Vector2Int lastBrushPosition, Vector2Int brushPosition )
	{
		var x0 = (int)lastBrushPosition.x;
		var y0 = (int)lastBrushPosition.y;
		var x1 = (int)brushPosition.x;
		var y1 = (int)brushPosition.y;

		var dx = Math.Abs( x1 - x0 );
		var dy = Math.Abs( y1 - y0 );

		var sx = x0 < x1 ? 1 : -1;
		var sy = y0 < y1 ? 1 : -1;

		var err = dx - dy;

		while ( true )
		{
			var rect = new Rect( x0, y0, BrushSize, BrushSize );
			// DrawTexture.Update( GetCurrentColor(), rect );
			// PushRectToByteData( rect );
			PushRectToBoth( rect );

			if ( x0 == x1 && y0 == y1 )
			{
				break;
			}

			var e2 = 2 * err;
			if ( e2 > -dy )
			{
				err -= dy;
				x0 += sx;
			}

			if ( e2 < dx )
			{
				err += dx;
				y0 += sy;
			}
		}
	}

	// TODO: this is a duplicate of DrawLineBetween
	private void DrawLineBetweenTex( Texture tex, Color32 col, Vector2Int lastBrushPosition, Vector2Int brushPosition )
	{
		var x0 = (int)lastBrushPosition.x;
		var y0 = (int)lastBrushPosition.y;
		var x1 = (int)brushPosition.x;
		var y1 = (int)brushPosition.y;

		var dx = Math.Abs( x1 - x0 );
		var dy = Math.Abs( y1 - y0 );

		var sx = x0 < x1 ? 1 : -1;
		var sy = y0 < y1 ? 1 : -1;

		var err = dx - dy;

		while ( true )
		{
			tex.Update( col, x0, y0 );

			if ( x0 == x1 && y0 == y1 )
			{
				break;
			}

			var e2 = 2 * err;
			if ( e2 > -dy )
			{
				err -= dy;
				x0 += sx;
			}

			if ( e2 < dx )
			{
				err += dx;
				y0 += sy;
			}
		}
	}


	private Vector2Int? _mouseDownPosition;
	private Vector2Int? _mouseUpPosition;

	private bool _isMoving;

	private void SetClipboard( Vector2Int start, Vector2Int end )
	{
		Log.Info( $"Setting clipboard at {start} to {end}" );

		var x = Math.Min( start.x, end.x );
		var y = Math.Min( start.y, end.y );
		var width = Math.Abs( start.x - end.x );
		var height = Math.Abs( start.y - end.y );

		ClipboardSize = new Vector2Int( width, height );
		ClipboardData = new byte[width * height];

		for ( var i = 0; i < width; i++ )
		{
			for ( var j = 0; j < height; j++ )
			{
				var index = x + i + (y + j) * DrawTexture.Width;
				if ( index >= 0 && index < DrawTextureData.Length )
				{
					ClipboardData[i + j * width] = DrawTextureData[index];
				}
			}
		}
	}

	private void PasteClipboardToPreview( Vector2Int position )
	{
		// Log.Info( $"Pasting clipboard to preview at {position}, size {ClipboardSize}" );

		var width = ClipboardSize.x;
		var height = ClipboardSize.y;

		// clamp so it doesn't go out of bounds
		var x = Math.Clamp( position.x, 0, DrawTexture.Width );
		var y = Math.Clamp( position.y, 0, DrawTexture.Height );

		width = Math.Min( width, DrawTexture.Width - x );
		height = Math.Min( height, DrawTexture.Height - y );

		if ( width == 0 || height == 0 )
		{
			return;
		}

		var colors = new Color32[width * height];

		for ( var i = 0; i < width; i++ )
		{
			for ( var j = 0; j < height; j++ )
			{
				var index = i + j * width;
				colors[index] = Palette[ClipboardData[i + j * ClipboardSize.x]];
			}
		}

		PreviewTexture.Update( colors, x, y, width, height );
	}

	private void ClearTexture()
	{
		if ( DrawTexture.IsValid() && DrawTextureData != null )
		{
			if ( RightPaletteIndex < Palette.Count )
			{
				DrawTexture.Update( Palette[RightPaletteIndex],
					new Rect( 0, 0, DrawTexture.Width, DrawTexture.Height ) );
				DrawTextureData = Enumerable.Repeat( (byte)RightPaletteIndex, DrawTexture.Width * DrawTexture.Height )
					.ToArray();
			}
			else
			{
				Log.Warning( "Right palette index is out of bounds" );
			}
		}
		else
		{
			Log.Warning( "DrawTexture/DrawTextureData is not valid" );
		}

		ClearPreview();

		_lastBrushPosition = null;

		PushUndo();
	}

	private void ClearPreview()
	{
		PreviewTexture?.Update( Color32.Transparent, new Rect( 0, 0, PreviewTexture.Width, PreviewTexture.Height ) );
	}

	private void Zoom( float value, Vector2 target = default )
	{
		CanvasZoom += value;
		CanvasZoom = Math.Clamp( CanvasZoom, MinCanvasZoom, MaxCanvasZoom );

		// CanvasSize = (int)(BaseCanvasSize * CanvasZoom);

		Log.Info( $"Zooming to {CanvasZoom}" );

		// CanvasSize = (int)Math.Round( (BaseCanvasSize * CanvasZoom).SnapToGrid( 32 ) );

		UpdateCanvas();
	}

	private void ZoomIn()
	{
		Zoom( 0.2f );
	}

	private void ZoomOut()
	{
		Zoom( -0.2f );
	}

	private void ZoomReset()
	{
		// CanvasSize = 512;
		CanvasZoom = 10.0f;
		UpdateCanvas();
	}

	private void SetBrushSize( int size )
	{
		BrushSize = size;
	}

	private void IncreaseBrushSize()
	{
		BrushSize++;
	}

	private void DecreaseBrushSize()
	{
		BrushSize--;
		if ( BrushSize < 1 )
		{
			BrushSize = 1;
		}
	}

	private void UpdateCanvas()
	{
		// CanvasImage.Style.Width = Length.Pixels( CanvasSize );
		// CanvasImage.Style.Height = Length.Pixels( CanvasSize );

		if ( !CanvasImage.IsValid() )
		{
			Log.Warning( "CanvasImage is not valid" );

			Task.Frame().ContinueWith( _ =>
			{
				if ( !CanvasImage.IsValid() )
				{
					Log.Warning( "CanvasImage is still not valid" );
					return;
				}

				UpdateCanvas();
			} );

			return;
		}

		CanvasImage.Style.Width = Length.Pixels( DrawTexture.Width * CanvasZoom );
		CanvasImage.Style.Height = Length.Pixels( DrawTexture.Height * CanvasZoom );
	}

	private Color GetColorFromByte( byte index )
	{
		if ( index >= Palette.Count )
		{
			Log.Error( $"Index {index} is out of bounds" );
			return Color.Transparent;
		}

		return Palette[index];
	}

	private Color GetColorFromByte( int index )
	{
		if ( index >= Palette.Count )
		{
			Log.Error( $"Index {index} is out of bounds" );
			return Color.Transparent;
		}

		return Palette[index];
	}


	protected override int BuildHash()
	{
		return HashCode.Combine( CurrentTool );
	}

	private static ReadOnlySpan<Color32> ResizeTexture( Color32[] sourcePixels, Vector2Int sourceSize,
		Vector2Int destinationSize )
	{
		if ( sourceSize == destinationSize )
		{
			return sourcePixels;
		}

		// Old square code
		/*var destinationPixels = new Color32[destinationSize * destinationSize];

		var ratio = (float)destinationSize / sourceSize;

		for ( var y = 0; y < destinationSize; y++ )
		{
			for ( var x = 0; x < destinationSize; x++ )
			{
				var sourceX = (int)(x / ratio);
				var sourceY = (int)(y / ratio);

				var sourceIndex = sourceX + sourceY * sourceSize;
				var destinationIndex = x + y * destinationSize;

				destinationPixels[destinationIndex] = sourcePixels[sourceIndex];
			}
		}*/

		var destinationPixels = new Color32[destinationSize.x * destinationSize.y];

		var ratioX = (float)destinationSize.x / sourceSize.x;
		var ratioY = (float)destinationSize.y / sourceSize.y;

		for ( var y = 0; y < destinationSize.y; y++ )
		{
			for ( var x = 0; x < destinationSize.x; x++ )
			{
				var sourceX = (int)(x / ratioX);
				var sourceY = (int)(y / ratioY);

				var sourceIndex = sourceX + sourceY * sourceSize.x;
				var destinationIndex = x + y * destinationSize.x;

				destinationPixels[destinationIndex] = sourcePixels[sourceIndex];
			}
		}


		return destinationPixels;
	}
}

public interface IPaintEvent
{
	public void OnFileSaved( string path );
}