Ui/Paint/PaintUi.Operations.cs
using System;

namespace Clover.Ui;

public partial class PaintUi
{
	private Vector2Int? _lastBrushPosition;

	private void Draw( Vector2Int brushPosition )
	{
		var rect = new Rect( brushPosition.x, brushPosition.y, BrushSize, BrushSize );
		// DrawTexture.Update( GetCurrentColor(), rect );

		// PushRectToByteData( rect );
		PushRectToBoth( rect );

		// Draw line between last and current position
		if ( _lastBrushPosition.HasValue && _lastBrushPosition.Value != brushPosition )
		{
			DrawLineBetween( _lastBrushPosition.Value, brushPosition );
		}

		_lastBrushPosition = brushPosition;
	}

	private void Fill( Vector2Int brushPosition )
	{
		var targetColor = DrawTextureData[brushPosition.x + brushPosition.y * DrawTexture.Width];
		var replacementColor = (byte)CurrentPaletteIndex;

		if ( targetColor == replacementColor )
		{
			return;
		}

		FloodFill( brushPosition, targetColor, replacementColor );
	}

	private void FloodFill( Vector2Int position, byte targetColor, byte replacementColor )
	{
		var positionX = position.x;
		var positionY = position.y;
		if ( positionX < 0 || positionX >= DrawTexture.Width || positionY < 0 || positionY >= DrawTexture.Height )
		{
			return;
		}

		if ( DrawTextureData[positionX + positionY * DrawTexture.Width] != targetColor )
		{
			return;
		}

		// DrawTextureData[positionX + positionY * DrawTexture.Width] = replacementColor;
		// DrawTexture.Update( GetCurrentColor(), new Rect( positionX, positionY, 1, 1 ) );
		PushRectToBoth( new Rect( positionX, positionY, 1, 1 ) );

		// FloodFill( positionX + 1, positionY, targetColor, replacementColor );
		// FloodFill( positionX - 1, positionY, targetColor, replacementColor );
		// FloodFill( positionX, positionY + 1, targetColor, replacementColor );
		// FloodFill( positionX, positionY - 1, targetColor, replacementColor );
		
		FloodFill( position + Vector2Int.Right, targetColor, replacementColor );
		FloodFill( position + Vector2Int.Left, targetColor, replacementColor );
		FloodFill( position + Vector2Int.Up, targetColor, replacementColor );
		FloodFill( position + Vector2Int.Down, targetColor, replacementColor );
		
	}

	private TimeSince _lastSpray;

	/// <summary>
	///  Spray random paint in a circle around the brush position
	/// </summary>
	/// <param name="brushPosition"></param>
	private void Spray( Vector2Int brushPosition )
	{
		var radius = BrushSize * 3;
		var radiusSquared = radius * radius;

		if ( _lastSpray > 0.03f )
		{
			_lastSpray = 0;
		}
		else
		{
			return;
		}

		for ( var i = 0; i < 30; i++ )
		{
			// var randomX = MathF.RoundToInt( MathF.Random.Range( -radius, radius ) );
			// var randomY = MathF.RoundToInt( MathF.Random.Range( -radius, radius ) );
			var randomX = Random.Shared.Next( -radius, radius );
			var randomY = Random.Shared.Next( -radius, radius );

			if ( randomX * randomX + randomY * randomY <= radiusSquared )
			{
				var x = brushPosition.x + randomX;
				var y = brushPosition.y + randomY;

				if ( x >= 0 && x < DrawTexture.Width && y >= 0 && y < DrawTexture.Height )
				{
					var rect = new Rect( x, y, 1, 1 );
					// DrawTexture.Update( GetCurrentColor(), rect );
					// PushRectToByteData( rect );
					PushRectToBoth( rect );
				}
			}
		}
	}

	private void Eraser( Vector2Int brushPosition )
	{
		// DrawTexture.Update( BackgroundColor,
		// 	new Rect( brushPosition.x, brushPosition.y, BrushSize, BrushSize ) );
		// PushRectToByteData( new Rect( brushPosition.x, brushPosition.y, BrushSize, BrushSize ) );
		PushRectToBoth( new Rect( brushPosition.x, brushPosition.y, BrushSize, BrushSize ) );
	}

	private void Eyedropper( Vector2Int brushPosition, MouseButtons mouseButton )
	{
		var index = DrawTextureData[brushPosition.x + brushPosition.y * DrawTexture.Width];
		if ( mouseButton == MouseButtons.Left )
		{
			LeftPaletteIndex = index;
		}
		else if ( mouseButton == MouseButtons.Right )
		{
			RightPaletteIndex = index;
		}
	}

	private void DrawLine( Vector2Int startPos, Vector2Int endPos )
	{
		DrawLineBetween( startPos, endPos );
	}

	private void LinePreview( Vector2Int brushPosition )
	{
		ClearPreview();

		if ( !_mouseDownPosition.HasValue )
		{
			return;
		}

		DrawLineBetweenTex( PreviewTexture, PreviewColor, _mouseDownPosition.Value, brushPosition );
	}

	private void RectanglePreview( Vector2Int brushPosition )
	{
		ClearPreview();

		if ( !_mouseDownPosition.HasValue )
		{
			return;
		}

		DrawRectangle( _mouseDownPosition.Value, brushPosition, true );
	}

	// draw outline of rectangle
	private void DrawRectangle( Vector2Int mouseDownPosition, Vector2Int mouseUpPosition, bool preview = false )
	{
		var x = Math.Min( mouseDownPosition.x, mouseUpPosition.x );
		var y = Math.Min( mouseDownPosition.y, mouseUpPosition.y );
		var width = Math.Abs( mouseDownPosition.x - mouseUpPosition.x );
		var height = Math.Abs( mouseDownPosition.y - mouseUpPosition.y );

		if ( preview )
		{
			PreviewTexture.Update( PreviewColor, new Rect( x, y, width, 1 ) );
			PreviewTexture.Update( PreviewColor, new Rect( x, y, 1, height ) );
			PreviewTexture.Update( PreviewColor, new Rect( x + width, y, 1, height ) );
			PreviewTexture.Update( PreviewColor, new Rect( x, y + height, width + 1, 1 ) ); // TODO: why +1?
		}
		else
		{
			PushRectToBoth( new Rect( x, y, width, 1 ) );
			PushRectToBoth( new Rect( x, y, 1, height ) );
			PushRectToBoth( new Rect( x + width, y, 1, height ) );
			PushRectToBoth( new Rect( x, y + height, width + 1, 1 ) ); // TODO: why +1?
		}
	}

	private void CirclePreview( Vector2Int brushPosition )
	{
		ClearPreview();

		if ( !_mouseDownPosition.HasValue )
		{
			return;
		}

		DrawCircle( _mouseDownPosition.Value, brushPosition, true );
	}

	private void DrawCircle( Vector2Int startPosition, Vector2Int endPosition, bool preview = false )
	{
		var x = Math.Min( startPosition.x, endPosition.x );
		var y = Math.Min( startPosition.y, endPosition.y );
		var width = Math.Abs( startPosition.x - endPosition.x );
		var height = Math.Abs( startPosition.y - endPosition.y );

		var radius = Math.Min( width, height ) / 2;
		var centerX = x + width / 2;
		var centerY = y + height / 2;

		for ( var i = 0; i < 360; i++ )
		{
			var angle = MathX.DegreeToRadian( i );
			var circleX = centerX + MathF.Cos( angle ) * radius;
			var circleY = centerY + MathF.Sin( angle ) * radius;

			if ( preview )
			{
				PreviewTexture.Update( PreviewColor,
					new Rect( (int)Math.Round( circleX ), (int)Math.Round( circleY ), 1, 1 ) );
			}
			else
			{
				PushRectToBoth( new Rect( (int)Math.Round( circleX ), (int)Math.Round( circleY ), 1, 1 ) );
			}
		}
	}
	
	private void PasteClipboard( Vector2Int position )
	{
		Log.Info( $"Pasting clipboard at {position}, size {ClipboardSize}" );
		
		/*var x = position.x;
		var y = position.y;

		for ( var i = 0; i < ClipboardSize.x; i++ )
		{
			for ( var j = 0; j < ClipboardSize.y; j++ )
			{
				var index = x + i + (y + j) * DrawTexture.Width;
				if ( index >= 0 && index < DrawTextureData.Length )
				{
					DrawTextureData[index] = ClipboardData[i + j * ClipboardSize.x];
				}
			}
		}*/
		
		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;
		}
		
		for ( var i = 0; i < width; i++ )
		{
			for ( var j = 0; j < height; j++ )
			{
				var index = i + j * width;
				DrawTextureData[x + i + (y + j) * DrawTexture.Width] = ClipboardData[index];
			}
		}

		PushByteDataToTexture();
	}
	
	private void FillArea( Vector2Int start, Vector2Int end, int colorIndex )
	{
		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 );
		
		PushRectToBoth( new Rect( x, y, width, height ), colorIndex );

		/*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 )
				{
					DrawTextureData[index] = (byte)colorIndex;
				}
			}
		}

		PushByteDataToTexture();*/
	}

	private void Burn( Vector2Int position )
	{
		Log.Info( $"Burning at {position}" );
		
		var rect = new Rect( position.x, position.y, BrushSize, BrushSize );
		
		// go through each pixel in the rect and darken it
		for ( var i = 0; i < rect.Width; i++ )
		{
			for ( var j = 0; j < rect.Height; j++ )
			{
				var index = (int)Math.Round( rect.Left + i + (rect.Top + j) * DrawTexture.Width);
				if ( index >= 0 && index < DrawTextureData.Length )
				{
					var color = DrawTextureData[index];
					if ( color > 0 )
					{
						var darkenedColor = GetClosestColorWithValueOffset( color, false );
						DrawTextureData[index] = darkenedColor;
					}
				}
			}
		}
		
		PushByteDataToTexture();
	}
	
	/// <summary>
	///  
	/// </summary>
	/// <param name="colorIndex"></param>
	/// <param name="brighter">If true, will find a brighter color, otherwise a darker color</param>
	/// <returns></returns>
	private byte GetClosestColorWithValueOffset( int colorIndex, bool brighter )
	{
		var realColor = GetColorFromByte( colorIndex );

		var foundIndex = colorIndex;

		ColorHsv hsv = realColor;
		
		while ( hsv.Value > 0.1f && hsv.Value < 1.0f )
		{
			// hsv.Value -= 0.1f;
			if ( brighter )
			{
				hsv.Value += 0.1f;
			}
			else
			{
				hsv.Value -= 0.1f;
			}

			var testColor = Utilities.Decals.GetClosestPaletteColor( Palette.ToArray(), hsv.ToColor() );
			
			if ( testColor != colorIndex )
			{
				foundIndex = testColor;
				break;
			}

		}
		
		if ( foundIndex == colorIndex )
		{
			Log.Warning( $"Could not find a darker color for {colorIndex}" );
		}
		
		return (byte)foundIndex;
		
	}
	
	private void Dodge( Vector2Int position )
	{
		Log.Info( $"Dodging at {position}" );
		
		var rect = new Rect( position.x, position.y, BrushSize, BrushSize );
		
		// go through each pixel in the rect and lighten it
		for ( var i = 0; i < rect.Width; i++ )
		{
			for ( var j = 0; j < rect.Height; j++ )
			{
				var index = (int)Math.Round( rect.Left + i + (rect.Top + j) * DrawTexture.Width);
				if ( index >= 0 && index < DrawTextureData.Length )
				{
					var color = DrawTextureData[index];
					if ( color > 0 )
					{
						var lightenedColor = GetClosestColorWithValueOffset( color, true );
						DrawTextureData[index] = lightenedColor;
					}
				}
			}
		}
		
		PushByteDataToTexture();
	}
	
}