/* 
 * 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 Shot Chart. The chart uses SkiaSharp to draw a visual representation of a volleyball
 * court together with markers for each shot. A shot marker consists of a starting circle, directional arrow, and ending
 * circle. Each component can be turned on/off independently.
 */
public class ShotView : SKCanvasView
{
	/* Constants */

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

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

	// Marker component color types
	public enum ColorMode
	{
		Normal,
		Light,
		Dark
	};

	// Result colors
	public static readonly SKColor[] AttemptColors =
	[
		SKColor.Parse( "2196f3" ),		// Normal
		SKColor.Parse( "bcdffb" ),		// Light
		SKColor.Parse( "1666a6" )		// Dark
	];

	public static readonly SKColor[] SuccessColors =
	[
		SKColor.Parse( "00bfA5" ),		// Normal
		SKColor.Parse( "b2ece4" ),		// Light
		SKColor.Parse( "008774" )		// Dark
	];

	public static readonly SKColor[] ErrorColors =
	[
		SKColor.Parse( "fe2c54" ),		// Normal
		SKColor.Parse( "ffbfcb" ),		// Light
		SKColor.Parse( "9e1b34" )		// Dark
	];

	// Rating colors (0-4)
	public static readonly SKColor[][] RatingColors =
	[
		// Normal
		[
			SKColor.Parse( "6a1b9a" ),
			SKColor.Parse( "8e24aa" ),
			SKColor.Parse( "b65fc5" ),
			SKColor.Parse( "c786d3" ),
			SKColor.Parse( "e1Bee7" )
		],
		// Light
		[
			SKColor.Parse( "8520c1" ),
			SKColor.Parse( "a229c3" ),
			SKColor.Parse( "d46fe6" ),
			SKColor.Parse( "efa2fe" ),
			SKColor.Parse( "f9d1ff" )
		],
		// Dark
		[
			SKColor.Parse( "571580" ),
			SKColor.Parse( "6e1c84" ),
			SKColor.Parse( "82448e" ),
			SKColor.Parse( "a16dab" ),
			SKColor.Parse( "ba9cbe" )
		]
	];

	// Unknown result/rating
	private static readonly SKColor UnknownColor = SKColor.Parse( "ffffff" );

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

	/* Properties */

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

	// Results or ratings?
	public string ShotType { get; set; }

	// Marker options
	public bool ShowStart { get; set; }
	public bool ShowArrow { get; set; }
	public bool ShowEnd { get; set; }

	// External ref
	public ShotCard Card { get; set; }

	/* Fields */

	// Shot list external ref
	private List<Stat> shotList;

	/* Methods */
	public void Init()
	{
		// Defaults
		ShowStart = true;
		ShowArrow = true;
		ShowEnd = true;

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

	// Draw entire specified shot list
	public void DrawShots( List<Stat> shots )
	{
		shotList = shots;

		Redraw();

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

	// Forces redraw of court and entire shot list
	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 );

		// Circle size relative to canvas
		size = (canvasHt * 0.045f);

		// All components relative to circle size
		radius = (size * 0.50f);
		strokeWd = (size * 0.18f);
		arrowLen = (size * 0.50f);
		arrowGap = strokeWd;

		halfNet = (netWd * 0.5f);

		// Draw entire shot list
		foreach ( Stat shot in shotList )
		{
			DrawShot( canvas, shot );
		}
	}

	/* Draw */

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

	private float courtSize;
	private float netWd;

	private float courtX;
	private float courtY;

	// Only needs to be calculated once per loop
	private float size;

	private float radius;
	private float strokeWd;
	private float arrowLen;
	private float arrowGap;

	private float fullWd;
	private float halfNet;

	// Draws marker (start, arrow, end) for specified shot
	public void DrawShot( SKCanvas canvas, Stat shot )
	{
		float startX = shot.StartX;
		float startY = shot.StartY;

		float endX = shot.EndX;
		float endY = shot.EndY;

		Point normStart;
		Point normEnd;

		bool startA = (startX < 0.5);

		// Team 1 always left-to-right, Team 2 always right-to-left

		// Already correct side
		if ( (startA && IsTeam1) || (!startA && !IsTeam1) )
		{
			normStart = new Point( startX, startY );
			normEnd = new Point( endX, endY );
		}
		// Normalize 
		else
		{
			bool endA = (endX < 0.5);

			normStart = RecordCourt.NormalizeSide( startX, startY, (IsTeam1 ? RecordCourt.Side.SideA : RecordCourt.Side.SideB) );
			normEnd = RecordCourt.NormalizeSide( endX, endY, (endA ? RecordCourt.Side.SideB : RecordCourt.Side.SideA) );
		}

		// Start location
		float x1 = (float)(courtX + (normStart.X * fullWd));
		float y1 = (float)(courtY + (normStart.Y * courtSize));

		// Account for net
		if ( startX > 0.5 )
		{
			x1 += netWd;
		}
		else if ( !(startX < 0.5) )
		{
			x1 += halfNet;
		}

		// End location			
		float x2 = (float)(courtX + (normEnd.X * fullWd));
		float y2 = (float)(courtY + (normEnd.Y * courtSize));

		// Account for net
		if ( endX > 0.5 )
		{
			x2 += netWd;
		}
		else if ( !(endX < 0.5) )
		{
			x2 += halfNet;
		}

		SKColor color = GetColor( shot, ColorMode.Normal );

		// Stroke color based on result/rating
		using SKPaint stroke = new();
		
		stroke.Style = SKPaintStyle.Stroke;
		stroke.StrokeWidth = strokeWd;
		stroke.Color = color;
		stroke.IsAntialias = true;

		// Start circle
		if ( ShowStart )
		{
			SKColor darkColor = GetColor( shot, ColorMode.Dark );

			// Dark inside
			using SKPaint fill = new();
			
			fill.Style = SKPaintStyle.Fill;
			fill.Color = darkColor;
			fill.IsAntialias = true;

			canvas.DrawCircle( x1, y1, radius, fill );

			// Outer stroke
			canvas.DrawCircle( x1, y1, radius, stroke );
		}

		// Arrow
		if ( ShowArrow )
		{
			DXGraphics.DrawArrow( canvas, x1, y1, x2, y2, color, strokeWd, arrowLen, (radius + 5), (radius + 5), arrowGap, 0 );
		}

		// End circle
		if ( ShowEnd )
		{
			SKColor lightColor = GetColor( shot, ColorMode.Light );

			// Light inside
			using SKPaint fill = new();
			
			fill.Style = SKPaintStyle.Fill;
			fill.Color = lightColor;
			fill.IsAntialias = true;

			canvas.DrawCircle( x2, y2, radius, fill );

			// Outer stroke
			canvas.DrawCircle( x2, y2, radius, stroke );
		}
	}

	// Draws court, net and lines
	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;
		float 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 );
	}

	// Used internally to determine marker color based on result/rating
	public SKColor GetColor( Stat shot, ColorMode colorMode )
	{
		int mode = (int) colorMode;

		switch ( ShotType )
		{
			// Result colors (blue, green, red)
			case ShotChart.ResultType:
			{
				switch ( shot.Result )
				{
					case Stats.AttemptKey: return AttemptColors[ mode ];
					case Stats.ErrorKey: return ErrorColors[ mode ];
					case Stats.AceKey:
					case Stats.KillKey:
					case Stats.AssistKey: return SuccessColors[ mode ];
				}

				break;
			}
			// Rating color (purples)
			case ShotChart.RatingType:
			{
				int rating = Stat.MapRating( shot.Rating, Card.IsPassing() );

				// Must map between 0-4 and 0-3
				if ( (rating >= 0) && (rating < Card.RatingCount) )
				{
					return RatingColors[ mode ][ rating ];
				}

				break;
			}
		}

		// Debug
		return UnknownColor;
	}

	/* 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();
	}
}

//
