Editor/Sprite/SpriteEditor/Preview/Preview.cs
using Editor;
using Sandbox;
using System;
using System.Linq;

namespace SpriteTools.SpriteEditor.Preview;

public class Preview : Widget
{
	public MainWindow MainWindow { get; }
	private readonly RenderingWidget Rendering;

	Widget Overlay;
	WidgetWindow overlayWindowZoom;
	WidgetWindow overlayWindowPoint;

	Vector2 attachmentCreatePosition;

	public Preview ( MainWindow mainWindow ) : base( null )
	{
		MainWindow = mainWindow;

		Name = "Preview";
		WindowTitle = "Preview";
		SetWindowIcon( "emoji_emotions" );

		MinimumSize = new Vector2( 256, 256 );

		Layout = Layout.Column();

		Rendering = new RenderingWidget( MainWindow, this );
		Layout.Add( Rendering );

		Overlay = new Widget( this )
		{
			Layout = Layout.Row(),
			TranslucentBackground = true,
			NoSystemBackground = true,
			WindowFlags = WindowFlags.FramelessWindowHint | WindowFlags.Tool
		};
		overlayWindowZoom = new WidgetWindow( this );
		overlayWindowZoom.Parent = Overlay;
		overlayWindowZoom.Layout = Layout.Row();
		overlayWindowZoom.Layout.Spacing = 4;
		overlayWindowZoom.Layout.Margin = 4;
		var btnZoomOut = overlayWindowZoom.Layout.Add( new IconButton( "zoom_out" ) );
		btnZoomOut.OnClick = () =>
		{
			Rendering.Zoom( -250 );
		};
		btnZoomOut.ToolTip = "Zoom Out";
		btnZoomOut.StatusTip = "Zoom Out View";
		var btnZoomIn = overlayWindowZoom.Layout.Add( new IconButton( "zoom_in" ) );
		btnZoomIn.OnClick = () =>
		{
			Rendering.Zoom( 250 );
		};
		btnZoomIn.ToolTip = "Zoom In";
		btnZoomIn.StatusTip = "Zoom In View";
		overlayWindowZoom.Layout.AddSeparator();
		var btnFit = overlayWindowZoom.Layout.Add( new IconButton( "zoom_out_map" ) );
		btnFit.OnClick = () =>
		{
			Rendering.Fit();
		};
		btnFit.ToolTip = "Fit to Screen";
		btnFit.StatusTip = "Fit View to Screen";
		overlayWindowZoom.WindowTitle = "Zoom Controls";

		Overlay.Layout.Add( overlayWindowZoom );

		overlayWindowPoint = new WidgetWindow( this );
		overlayWindowPoint.Parent = Overlay;
		overlayWindowPoint.Layout = Layout.Column();
		overlayWindowPoint.Layout.Margin = 4;
		overlayWindowPoint.WindowTitle = "Point Controls";

		var row1 = overlayWindowPoint.Layout.AddRow();
		var btnTopLeft = row1.Add( new TextureModifyButton( this, "Align Top-Left", "Images/grid-align-top-left.png", () => SetOrigin( new Vector2( 0, 0f ) ) ) );
		var btnTopMiddle = row1.Add( new TextureModifyButton( this, "Align Top-Center", "Images/grid-align-top-center.png", () => SetOrigin( new Vector2( 0.5f, 0f ) ) ) );
		var btnTopRight = row1.Add( new TextureModifyButton( this, "Align Top-Right", "Images/grid-align-top-right.png", () => SetOrigin( new Vector2( 1f, 0f ) ) ) );

		var row2 = overlayWindowPoint.Layout.AddRow();
		var btnMiddleLeft = row2.Add( new TextureModifyButton( this, "Align Middle-Left", "Images/grid-align-middle-left.png", () => SetOrigin( new Vector2( 0f, 0.5f ) ) ) );
		var btnMiddleCenter = row2.Add( new TextureModifyButton( this, "Align Middle-Center", "Images/grid-align-middle-center.png", () => SetOrigin( new Vector2( 0.5f, 0.5f ) ) ) );
		var btnMiddleRight = row2.Add( new TextureModifyButton( this, "Align Middle-Right", "Images/grid-align-middle-right.png", () => SetOrigin( new Vector2( 1f, 0.5f ) ) ) );

		var row3 = overlayWindowPoint.Layout.AddRow();
		var btnBottomLeft = row3.Add( new TextureModifyButton( this, "Align Bottom-Left", "Images/grid-align-bottom-left.png", () => SetOrigin( new Vector2( 0, 1f ) ) ) );
		var btnBottomCenter = row3.Add( new TextureModifyButton( this, "Align Bottom-Center", "Images/grid-align-bottom-center.png", () => SetOrigin( new Vector2( 0.5f, 1f ) ) ) );
		var btnBottomRight = row3.Add( new TextureModifyButton( this, "Align Bottom-Right", "Images/grid-align-bottom-right.png", () => SetOrigin( new Vector2( 1f, 1f ) ) ) );
		Overlay.Layout.Add( overlayWindowPoint );

		Overlay.Show();
		UpdateTexture();
		SetSizeMode( SizeMode.Default, SizeMode.CanShrink );

		MainWindow.OnTextureUpdate += UpdateTexture;

		MainWindow.Moved += DoLayout;
	}

	public override void OnDestroyed ()
	{
		base.OnDestroyed();

		MainWindow.OnTextureUpdate -= UpdateTexture;
		MainWindow.Moved -= DoLayout;
	}

	void SetOrigin ( Vector2 origin )
	{
		if ( MainWindow.SelectedAnimation is null ) return;
		MainWindow.SelectedAnimation.Origin = origin;
	}

	void UpdateTexture ()
	{
		if ( MainWindow.Sprite is null ) return;
		if ( MainWindow.SelectedAnimation is null ) return;
		if ( MainWindow.SelectedAnimation.Frames.Count <= 0 ) return;
		if ( MainWindow.CurrentFrameIndex < 0 ) return;
		if ( MainWindow.CurrentFrameIndex >= MainWindow.SelectedAnimation.Frames.Count ) return;
		var frame = MainWindow.SelectedAnimation.Frames[MainWindow.CurrentFrameIndex];
		if ( frame is null ) return;
		if ( !Sandbox.FileSystem.Mounted.FileExists( frame.FilePath ) ) return;
		var texture = Texture.LoadFromFileSystem( frame.FilePath, Sandbox.FileSystem.Mounted );
		if ( texture is null ) return;
		Rendering.SetTexture( texture, frame.SpriteSheetRect );
	}

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

		if ( Overlay.IsValid() && Rendering.IsValid() )
		{
			Overlay.Position = Rendering.ScreenPosition;
			Overlay.Size = Rendering.Size + 1;

			if ( overlayWindowPoint.Visible )
			{
				overlayWindowPoint.AdjustSize();
				overlayWindowPoint.AlignToParent( TextFlag.LeftTop, 4 );
			}

			overlayWindowZoom.AdjustSize();
			overlayWindowZoom.AlignToParent( TextFlag.RightTop, 4 );
		}
	}

	protected override void OnVisibilityChanged ( bool visible )
	{
		base.OnVisibilityChanged( visible );

		if ( Overlay is not null )
		{
			Overlay.Visible = visible;
		}
	}

	protected override void OnKeyRelease ( KeyEvent e )
	{
		base.OnKeyRelease( e );

		if ( e.Key == KeyCode.Left )
		{
			MainWindow.FramePrevious();
		}
		else if ( e.Key == KeyCode.Right )
		{
			MainWindow.FrameNext();
		}
	}

	void CreateAttachmentPopup ()
	{
		var popup = new PopupWidget( MainWindow );
		popup.Layout = Layout.Column();
		popup.Layout.Margin = 16;
		popup.Layout.Spacing = 8;

		popup.Layout.Add( new Label( $"What would you like to name the attachment point?" ) );

		var entry = new LineEdit( popup );
		var button = new Button.Primary( "Create" );

		button.MouseClick = () =>
		{
			if ( !string.IsNullOrEmpty( entry.Text ) && !MainWindow.SelectedAnimation.Attachments.Any( a => a.Name.ToLowerInvariant() == entry.Text.ToLowerInvariant() ) )
			{
				CreateAttachment( entry.Text );
			}
			else
			{
				ShowNamingError( entry.Text );
			}
			popup.Visible = false;
		};

		entry.ReturnPressed += button.MouseClick;

		popup.Layout.Add( entry );

		var bottomBar = popup.Layout.AddRow();
		bottomBar.AddStretchCell();
		bottomBar.Add( button );

		popup.Position = Editor.Application.CursorPosition;
		popup.Visible = true;

		entry.Focus();
	}

	void CreateAttachment ( string name )
	{
		MainWindow.PushUndo( "Add Attachment Point " + name );
		var tr = Rendering.Scene.Trace.Ray( Rendering.Camera.ScreenPixelToRay( attachmentCreatePosition ), 5000f ).Run();
		var pos = tr.EndPosition.WithZ( 0f );
		var attachPos = new Vector2( pos.y, pos.x );
		attachPos = ( attachPos / 100f ) + ( Vector2.One * 0.5f );
		var attachment = new SpriteAttachment( name );
		attachment.Points.Add( attachPos );
		MainWindow.SelectedAnimation.Attachments.Add( attachment );
		// MainWindow.SelectedAnimation.Frames[MainWindow.CurrentFrameIndex].AttachmentPoints[name] = attachPos;
		MainWindow.PushRedo();
	}

	static void ShowNamingError ( string name )
	{
		if ( string.IsNullOrWhiteSpace( name ) )
		{
			var confirm = new PopupWindow( "Invalid name ''", "You cannot give an attachment point an empty name", "OK" );
			confirm.Show();

			return;
		}

		var confirm2 = new PopupWindow( "Invalid name", $"An attachment point named '{name}' already exists", "OK" );
		confirm2.Show();
	}

	protected override void OnContextMenu ( ContextMenuEvent e )
	{
		base.OnContextMenu( e );

		attachmentCreatePosition = e.LocalPosition;

		var m = new Menu( this );

		m.AddOption( "Add Attach Point", "push_pin", CreateAttachmentPopup );

		m.OpenAtCursor( false );
	}


	private class TextureModifyButton : Widget
	{
		private readonly string Icon;
		private Pixmap pixmap;

		public TextureModifyButton ( Widget parent, string tooltip, string icon, Action onClick ) : base( parent )
		{
			Icon = icon;
			ToolTip = tooltip;
			FixedSize = 26;
			Cursor = CursorShape.Finger;
			MouseClick = onClick;
			var fullPath = Editor.FileSystem.Content.GetFullPath( Icon );
			if ( fullPath is not null )
			{
				pixmap = Pixmap.FromFile( fullPath );
			}
		}

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

			if ( Paint.HasMouseOver )
			{
				Paint.ClearPen();
				var bg = Theme.ControlBackground.Lighten( 0.3f );
				Paint.SetBrush( bg );
				Paint.DrawRect( LocalRect, Theme.ControlRadius );
			}
			if ( pixmap is not null )
			{
				Paint.Draw( LocalRect.Shrink( 5 ), pixmap );
			}
		}
	}
}