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

using SkiaSharp;
using SkiaSharp.Views.Maui;
using SkiaSharp.Views.Maui.Controls;

using DXLib.Utils;

namespace iStatVball3;

/*
 * Primitive drawing view for a Heat Map. The map uses SkiaSharp to draw a visual representation of a volleyball court
 * covered with rectangular areas. Each area is color-coded to indicate the range of metric values represented by that
 * area. The number of areas along each axis can be customized.
 */
public class HeatView : SKCanvasView
{
	/* Constants */
	public const float OutlineWd = 2;

	// Undefined result display
	public static readonly string EmptyResult = string.Empty;

	// Court colors
	public static readonly SKColor OuterColor = SKColor.Parse( "cccccc" );
	public static readonly SKColor InnerColor = SKColor.Parse( "7c8590" );

	public static readonly SKColor LineColor = SKColor.Parse( "ffffff" );
	public static readonly SKColor NetColor = SKColor.Parse( "ffffff" );

	// Area colors
	public static readonly SKColor TextColor = SKColor.Parse( "07182a" );
	public static readonly SKColor EmptyColor = SKColor.Parse( "dddddd" );
	public static readonly SKColor OutlineColor = SKColor.Parse( "ffffff" );

	// Green-yellow-red color palette
	private static readonly SKColor[] StoplightColors =
	[
		SKColor.Parse( "f86e89" ),
		SKColor.Parse( "fbc975" ),
		SKColor.Parse( "fef58a" ),
		SKColor.Parse( "e1fbab" ),
		SKColor.Parse( "b1f3be" ),
		SKColor.Parse( "82e5ce" )
	];

	// Purple color palette (0-4)
	private static readonly SKColor[] PurpleColors =
	[
		SKColor.Parse( "6a1b9a" ),
		SKColor.Parse( "8e24aa" ),
		SKColor.Parse( "b65fc5" ),
		SKColor.Parse( "c786d3" ),
		SKColor.Parse( "e1Bee7" )
	];

	/* Events */
	public Action LoadComplete { get; set; }

	/* Properties */

	// Primary team or opponent?
	public bool IsTeam1 { get; set; }

	/* Fields */
	private int areasX;
	private int areasY;

	private float[] resultList;
	private RecordCourt.Side courtSide;

	// JSON config options
	private string jsonType;
	private float[] jsonRanges;

	/* Methods */
	public void Init( JsonHeat json )
	{
		jsonType = json.Type;
		jsonRanges = json.Ranges;

		// Register for paint events
		PaintSurface += OnPaintSurface;
	}

	// Dynamically changes map size
	public void SetAreas( int x, int y )
	{
		areasX = x;
		areasY = y;
	}

	// Redraws heat map given specified result set
	public void DrawResults( float[] results, RecordCourt.Side side )
	{
		resultList = results;
		courtSide = side;

		Redraw();

		// Notify listener
		LoadComplete?.Invoke();
	}

	// Forces redraw of court and all map areas
	public void Redraw()
	{
		InvalidateSurface();
	}

	/* Event Callbacks */

	// Called for each draw loop
	private void OnPaintSurface( object sender, SKPaintSurfaceEventArgs args )
	{
		// Canvas dimensions
		canvasWd = CanvasSize.Width;
		canvasHt = CanvasSize.Height;

		SKCanvas canvas = args.Surface.Canvas;

		// Draw court (always landscape orientation)
		DrawCourt( canvas );

		// Draw XxY color-coded areas
		DrawAreas( canvas );
	}

	/* Draw */

	// Internal draw positioning
	private float canvasWd;
	private float canvasHt;

	private float courtSize;
	private float netWd;

	private float courtX;
	private float courtY;
	private float courtX2;

	private float fullWd;

	// Draws court, net and lines (same as shot chart)
	private void DrawCourt( SKCanvas canvas )
	{
		/* Layout Calculations */

		float wd = canvasWd;
		float ht = canvasHt;

		// Minimum out-of-bounds area
		float minOutWd = (ht * 0.130f);
		float minOutHt = (ht * 0.100f);

		// Proportional net size
		netWd = (ht * 0.030f);

		// Court size determined by minimum out-of-bounds
		float maxWd = (wd - (minOutWd * 2) - netWd) / 2;
		float maxHt = (ht - (minOutHt * 2));

		courtSize = Math.Min( maxWd, maxHt );
		fullWd = (courtSize + netWd + courtSize);

		// Courts always perfect square
		courtX = (wd - (courtSize * 2) - netWd) / 2;
		courtX2 = (courtX + courtSize + netWd);

		courtY = (ht - courtSize) / 2;
		float courtY2 = (courtY + courtSize);

		float netX = (courtX + courtSize);
		float netY = courtY;

		float antennaeSize = netWd;

		float frontrowWd = (courtSize / 3);
		float backrowWd = (frontrowWd * 2);

		// Line thickness proportional to court size
		float lineSize = (courtSize * 0.008f);

		/* Drawing */

		// Clear to outer court color
		canvas.Clear( OuterColor );

		// Fill inner court area
		using ( SKPaint courtFill = new() )
		{
			courtFill.Style = SKPaintStyle.Fill;
			courtFill.Color = InnerColor;
			courtFill.IsAntialias = false;
			
			canvas.DrawRect( courtX, courtY, fullWd, courtSize, courtFill );
		}

		// Draw court lines
		using SKPaint lineStroke = new();
		
		lineStroke.Style = SKPaintStyle.Stroke;
		lineStroke.StrokeWidth = lineSize;
		lineStroke.Color = LineColor;
		lineStroke.IsAntialias = false;

		canvas.DrawRect( courtX, courtY, fullWd, courtSize, lineStroke );

		// Draw net (no shadow)
		using ( SKPaint netFill = new() )
		{
			netFill.Style = SKPaintStyle.Fill;
			netFill.Color = NetColor;
			netFill.IsAntialias = true;
			
			canvas.DrawRect( netX, netY, netWd, courtSize, netFill );

			// Draw antennae (no shadow)
			canvas.DrawRect( netX, (courtY - antennaeSize), antennaeSize, antennaeSize, netFill );
			canvas.DrawRect( netX, courtY2, antennaeSize, antennaeSize, netFill );
		}

		// Draw 10ft lines
		float lineX = (courtX + backrowWd);
		float lineX2 = (courtX2 + frontrowWd);

		canvas.DrawLine( lineX, courtY, lineX, courtY2, lineStroke );
		canvas.DrawLine( lineX2, courtY, lineX2, courtY2, lineStroke );
	}

	// Draws all color-coded heat map areas
	private void DrawAreas( SKCanvas canvas )
	{
		// Dimensions of each area
		float wd = (courtSize / areasX);
		float ht = (courtSize / areasY);

		// Font size based on smaller dimension
		float fontSize = (Math.Min( wd, ht ) / 4);

		bool sideA = (courtSide == RecordCourt.Side.SideA);

		// Drawing court side A or B?
		float startX = sideA ? courtX : courtX2;

		// Traverse grid
		for ( int areaY = 0; areaY < areasY; areaY++ )
		{
			for ( int areaX = 0; areaX < areasX; areaX++ )
			{
				// Index into map
				int index = (areaY * areasX) + areaX;

				// Determine color for result value
				float result = resultList[ index ];
				SKColor color = GetColor( result );

				// Fill area
				using SKPaint areaFill = new();
				
				areaFill.Style = SKPaintStyle.Fill;
				areaFill.Color = color;
				areaFill.IsAntialias = false;

				float x = startX + (areaX * wd);
				float y = courtY + (areaY * ht);

				canvas.DrawRect( x, y, wd, ht, areaFill );

				// Draw area outline
				using SKPaint outlineStroke = new();
				
				outlineStroke.Style = SKPaintStyle.Stroke;
				outlineStroke.StrokeWidth = OutlineWd;
				outlineStroke.Color = OutlineColor;
				outlineStroke.IsAntialias = false;

				canvas.DrawRect( x, y, wd, ht, outlineStroke );

				// String display is map type specific
				string format = GetFormat();

				// Draw result value
				string resultStr = double.IsNaN( result ) ? EmptyResult : result.ToString( format );

				// Custom font
				SKFont font  = DXGraphics.GetFont( DXGraphics.Font.Oswald );
				
				font.Size = fontSize;
				font.Embolden = false;
				
				// Solid text
				using SKPaint textPaint = new();
				
				textPaint.Style = SKPaintStyle.Fill;
				textPaint.Color = TextColor;
				textPaint.IsAntialias = true;

				// Get dynamic text size
				font.MeasureText( resultStr, out var textBounds, textPaint );

				float centerX = x + (wd / 2);
				float centerY = y + (ht / 2);

				// Center text
				float drawX = (centerX - textBounds.MidX);
				float drawY = (centerY - textBounds.MidY);

				// Draw
				canvas.DrawText( resultStr, drawX, drawY, font, textPaint );
			}
		}
	}

	// Maps specified value to matching color from correct palette
	public SKColor GetColor( float value )
	{
		// Empty result stays gray
		if ( !float.IsNaN( value ) )
		{
			switch ( jsonType )
			{
				// Efficiency/count use dynamic ranges from JSON
				case HeatMap.EfficiencyType:
				case HeatMap.CountType:
				{
					for ( int range = (jsonRanges.Length - 1); range >= 0; range-- )
					{
						if ( value >= jsonRanges[ range ] )
						{
							return StoplightColors[ range ];
						}
					}

					break;
				}
				// Rating map ranges are fixed
				case HeatMap.RatingType:
				{
					int index = (int) Math.Floor( value );

					// Bounds check
					index = Math.Min( index, PurpleColors.Length );
					index = Math.Max( index, 0 );

					return PurpleColors[ index ];
				}
			}
		}

		return EmptyColor;
	}

	// Returns area value string format for current heat map type
	public string GetFormat()
	{
		return jsonType switch
		{
			HeatMap.EfficiencyType => "N3",
			HeatMap.RatingType => "N1",
			HeatMap.CountType => "N1",

			_ => null,
		};
	}

	/* Layout */

	// Device/orientation specific layout
	public void UpdateLayout( LayoutType type )
	{
		double wd = DXDevice.GetScreenWd();
		double ht = DXDevice.GetScreenHt();
		
		double padWd = (ReportLayout.BasePadding * 2) + Margin.HorizontalThickness;
		const double stripeWd = ReportCard.StripeWd;

		// Dynamically size based on orientation
		if ( DXDevice.IsMobile )
		{
			WidthRequest = (type == LayoutType.MobilePortrait) ? (wd * 1.75) : (wd - DXDevice.SafeArea().Left - padWd - stripeWd);
			HeightRequest = (type == LayoutType.MobileLandscape) ? 462 : 375;
		}
		else
		{
			WidthRequest = (wd - padWd - stripeWd);
			HeightRequest = (ht * ((type == LayoutType.Landscape) ? 0.602 : 0.366));
		}

		Redraw();
	}
}

//
