/* 
 * Copyright (c)2009-2025 DemiVision, LLC. All Rights Reserved. The information 
 * herein is the CONFIDENTIAL and PROPRIETARY information of DemiVision, LLC. 
 */

using SkiaSharp;

namespace DXLib.Utils;

/*
 * Provides utility methods for working with SkiaSharp graphics including text fonts, arrow drawing, etc. 
 */ 
public static class DXGraphics
{
	/* Constants */

	// Available custom fonts
	public enum Font
	{
		Roboto,
		RobotoBold,

		Oswald,
		OswaldBold
	};

	// Angle of arrowheads
	public const float Theta = (float) (Math.PI / 6.0f);

	/* Properties */

	// Cached fonts
	public static SKFont Roboto { get; private set; }
	public static SKFont RobotoBold { get; private set; }

	public static SKFont Oswald { get; private set; }
	public static SKFont OswaldBold { get; private set; }

	/* Methods */

	// Caches all commonly used fonts
	public static async Task Init()
	{
		Roboto = await LoadFont( Font.Roboto );
		RobotoBold = await LoadFont( Font.RobotoBold );

		Oswald = await LoadFont( Font.Oswald );
		OswaldBold = await LoadFont( Font.OswaldBold );
	}

	// Calculates Euclidean distance between two points
	public static float Distance( Point p1, Point p2 )
	{
		return Distance( (float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y );
	}

	// Calculates Euclidean distance between two points
	public static float Distance( float x1, float y1, float x2, float y2 )
	{
		return (float) Math.Sqrt( Math.Pow( (x2 - x1), 2 ) + Math.Pow( (y2 - y1), 2 ) );
	}

	// Returns specified pre-cached font
	public static SKFont GetFont( Font font )
	{
		return font switch
		{
			Font.Roboto => Roboto,
			Font.RobotoBold => RobotoBold,
			Font.Oswald => Oswald,
			Font.OswaldBold => OswaldBold,

			_ => null,
		};
	}

	// Loads specified custom font
	private static async Task<SKFont> LoadFont( Font font )
	{
		// Map to TTF font file
		string name = font switch
		{
			Font.Roboto => "Roboto-Regular",
			Font.RobotoBold => "Roboto-Bold",
			Font.Oswald => "Oswald-Regular",
			Font.OswaldBold => "Oswald-Medium",
			
			_ => null
		};

		// Open stream from file
		await using Stream stream = await FileSystem.OpenAppPackageFileAsync( $"Fonts/{name}.ttf" );
		
		// Load
		return SKTypeface.FromStream( stream ).ToFont();
	}

	// Draws an arrow between any two points on a SkiaSharp canvas.
	//
	// canvas	SkiaSharp canvas used for drawing
	// from		x,y origin
	// to		x,y destination
	// color	paint color
	// weight	arrow shaft thickness
	// size		arrow head length
	// radius1  radius of src circle (0 if pt)
	// radius2	radius of dest circle (0 if pt)
	// gap		(optional) space between arrow head and dest circle
	// min		(optional) minimum length to be drawn
	//
	public static void DrawArrow( SKCanvas canvas, SKPoint from, SKPoint to, SKColor color, float weight, float size, float radius1, float radius2, float gap = 0, float min = 0 )
	{
		DrawArrow( canvas, from.X, from.Y, to.X, to.Y, color, weight, size, radius1, radius2, gap, min );
	}

	public static void DrawArrow( SKCanvas canvas, float x1, float y1, float x2, float y2, SKColor color, float weight, float size, float radius1, float radius2, float gap = 0, float min = 0 )
	{
		// Connecting outside edges of two circles rather than from pt to pt
		if ( radius1 > 0 )
		{
			float dist1 = Distance( x1, y1, x2, y2 );

			x1 += radius1 * ((x2 - x1) / dist1);
			y1 += radius1 * ((y2 - y1) / dist1);
		}

		if ( radius2 > 0 )
		{
			float dist2 = Distance( x2, y2, x1, y1 );
			float gap2 = (gap * 2);

			x2 += (radius2 + gap2) * ((x1 - x2) / dist2);
			y2 += (radius2 + gap2) * ((y1 - y2) / dist2);
		}

		// Do not draw arrow if less than minimum length
		if ( min > 0 )
		{
			float dist = Distance( x1, y1, x2, y2 );

			if ( dist < min )
			{
				return;
			}
		}

		SKPath path = new();

		// Arrow shaft
		path.MoveTo( x1, y1 );
		path.LineTo( x2, y2 );

		path.MoveTo( x2, y2 );

		float angle = (float) Math.Atan2( (y1 - y2), (x1 - x2) );

		// Arrowhead corner 1
		float ax1 = (float) (x2 + size * Math.Cos( angle + Theta ));
		float ay1 = (float) (y2 + size * Math.Sin( angle + Theta ));

		path.MoveTo( x2, y2 );
		path.LineTo( ax1, ay1 );

		// Arrowhead corner 2
		float ax2 = (float) (x2 + size * Math.Cos( angle - Theta ));
		float ay2 = (float) (y2 + size * Math.Sin( angle - Theta ));

		path.LineTo( ax2, ay2 );
		path.LineTo( x2, y2 );

		// Close contours to make pointed tip
		path.Close();

		// Thickness/color
		SKPaint arrowPaint = new()
		{
			Style = SKPaintStyle.StrokeAndFill,
			StrokeWidth = weight,
			Color = color,
			IsAntialias = true
		};

		// Paint
		canvas.DrawPath( path, arrowPaint );
	}
}

//
