/* 
 * 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 DXLib.Utils;

namespace iStatVball3;

/*
 * Responsible for drawing all ball markers based on the stats currently at top of stack. There will never be more than
 * 4 markers visible at a time. When the ball crosses the net, only the last marker on the originating side is kept.
 * That marker is removed as soon as a second contact is made on the destination side. The markers can optionally be
 * connected by directional arrows showing the flow of the rally. 
 */ 
public class MarkerStack
{
	/* Constants */
	private const int MaxMarkers = 4;

	/* Properties */

	// Toggles all display on/off
	public bool IsDisabled { get; set; }

	// Draw arrows in dark color for higher contrast?
	public bool DarkArrows { get; set; }

	// Access to stats stack
	public RecordHandler Handler { get; set; }

	// Needed for pre-serve marker
	public RecordCourt.Side ServeSide { get; set; }

	/* Fields */
	private readonly List<Marker> markers;

	// Cached markers
	private readonly Marker markerPreS;

	private readonly Marker markerS;
	private readonly Marker marker1a;
	private readonly Marker marker1b;
	private readonly Marker marker2;
	private readonly Marker marker3;
	private readonly Marker markerB;
	private readonly Marker markerO;

	// External refs
	private readonly CourtView courtView;
	private Set markerSet;

	// Controls draw rules
	private RecordCourt.Side startSide;
	private RecordCourt.Side prevSide;

	private int sameCount;
	private int oppCount;
	private int changeCount;

	/* Methods */
	public MarkerStack( CourtView court )
	{
		courtView = court;

		// Allocate memory
		markers = new List<Marker>( MaxMarkers );

		// Cache all markers
		markerPreS = new Marker
		{
			Resource = "marker.serve",
			IsFilled = false,
			Alpha = 127
		};

		markerS = new Marker { Resource = "marker.serve" };
		marker1a = new Marker { Resource = "marker.first" };
		marker1b = new Marker { Resource = "marker.first" };
		marker2 = new Marker { Resource = "marker.second" };
		marker3 = new Marker { Resource = "marker.third" };
		markerB = new Marker { Resource = "marker.block" };
		markerO = new Marker { Resource = "marker.over" };

		// Default
		IsDisabled = false;
	}

	// Post construction initialization
	public void Init( Set set )
	{
		markerSet = set;
	}

	// Updates draw location for each marker based on current orientation
	public void Update()
	{
		markers.Clear();

		// All markers can be hidden
		if ( IsDisabled )
		{
			return;
		}

		int count = Handler.StatCount;

		sameCount = 0;
		oppCount = 0;
		changeCount = 0;

		bool prevPreS = false;

		// Never more than 4 at a time (B,1,2,3)
		for ( int i = 0; i < MaxMarkers; i++ )
		{
			int index = (count - i - 1);

			// Might be less than 3
			if ( index >= 0 )
			{
				Stat stat = Handler.GetStat( index );

				// Non-action events only show PreS
				if ( stat.IsLineup || stat.IsUpdate )
				{
					// Do not draw multiple times in row (transparency)
					if ( !prevPreS )
					{
						ShowPreS();

						prevPreS = true;
					}
				}
				else
				{
					prevPreS = false;

					// Remember where rally started
					if ( i == 0 )
					{
						startSide = (RecordCourt.Side) stat.State?.BallSide!;
						prevSide = startSide;
					}

					// Follow rules for which marker(s) to draw
					if ( !ShouldDraw( stat ) )
					{
						return;
					}

					Marker marker = null;
					const bool error = false;

					// Draw marker based on action
					switch ( stat.Action )
					{
						// Point (directly to pre-serve)
						case Stats.PointKey:
						{
							ShowPreS();
							return;
						}
						// Serve (only marker)
						case Stats.ServeKey:
						{
							AddMarker( markerS, stat, false );
							return;
						}
						// First ball
						case Stats.ReceiveKey:
						case Stats.DefenseKey:
						case Stats.FirstKey:
						case Stats.FreeballKey:
						case Stats.PutbackKey:
						{
							marker = markers.Contains( marker1a ) ? marker1b : marker1a;
							break;
						}
						// Second ball
						case Stats.SecondKey:
						{
							marker = marker2;
							break;
						}
						// Third ball
						case Stats.ThirdKey:
						{
							marker = marker3;
							break;
						}
						// Block
						case Stats.BlockKey:
						{
							marker = markerB;
							break;
						}
						// Overpass
						case Stats.OverpassKey:
						{
							marker = markerO;
							break;
						}
					}

					AddMarker( marker, stat, error );
				}
			}
		}

		// No actions, start of set
		if ( markers.Count == 0 )
		{
			ShowPreS();
		}
	}

	// Adds specified marker to stack at correct court location
	private void AddMarker( Marker marker, Stat stat, bool error )
	{
		if ( marker != null )
		{
			// Convert back to draw location
			Point pt = courtView.Denormalize( stat.StartX, stat.StartY );

			float x = (float)pt.X;
			float y = (float)pt.Y;

			marker.Location = new SKPoint( x, y );

			// Error color preset green/red, otherwise team color
			marker.Color = error ? marker.Color : markerSet.Match.ColorForTeam( stat.TeamId ).ToSKColor();

			// Error marker smaller
			marker.IsSmall = error;

			// Add to stack
			markers.Add( marker );
		}
	}
	
	// Shows pre-serve marker on appropriate side of court
	private void ShowPreS()
	{
		Player server = null;
		Stat lastServe;

		RecordState sm = courtView.StateMachine;
		bool team1 = sm.Team1HasBall();

		// Find last server (by player)
		if ( team1 )
		{
			server = sm.GetServer( true )?.Player;
			lastServe = Handler.PreviousStat( Stats.ServeKey, server );
		}
		// Anonymous team (by rotation)
		else
		{
			string teamId = sm.IdForTeam( 2 );
			int rotation = sm.RotationForTeam( 2 );

			lastServe = Handler.PreviousStat( teamId, Stats.ServeKey, rotation );
		}

		Point defaultPt = (ServeSide == RecordCourt.Side.SideA) ? CourtView.ServeA : CourtView.ServeB;
		Point servePt;

		// Use default location if no last serve
		if ( lastServe == null )
		{
			servePt = defaultPt;
		}
		// Use last serve (may need to switch to current court side)
		else
		{
			Point convertPt = RecordCourt.NormalizeSide( lastServe.StartX, lastServe.StartY, ServeSide );
			servePt = courtView.Denormalize( convertPt.X, convertPt.Y );
		}

		// Move marker
		markerPreS.Location = new SKPoint( (float)servePt.X, (float)servePt.Y );

		// Mobile primary team shows server jersey number
		markerPreS.Number = DXDevice.IsMobile ? server?.Number : null;

		markers.Add( markerPreS );
	}

	// Implements rules for which markers to draw given current state
	private bool ShouldDraw( Stat stat )
	{
		// Do not draw after set ended
		if ( stat.State == null )
		{
			return false;
		}

		RecordCourt.Side side = (RecordCourt.Side) stat.State.BallSide;

		// Track how many times ball changes sides
		if ( side != prevSide )
		{
			changeCount++;
		}

		// Do not draw more than 1 transition
		if ( changeCount > 1 )
		{
			return false;
		}

		bool sameSide = (side == startSide);

		// Track how many markers on each side of court
		sameCount += sameSide ? 1 : 0;
		oppCount += sameSide ? 0 : 1;

		// Max 3 on same side
		if ( sameCount > 3 )
		{
			return false;
		}

		// Once 2 markers on same side, stop drawing other side
		if ( (sameCount > 1) && (side != startSide) )
		{
			return false;
		}

		if ( oppCount > 1 )
		{
			return false;
		}

		prevSide = side;

		// OK to draw
		return true;
	}

	// Draws each marker on stack with arrows between them
	public void Paint( SKCanvas canvas )
	{
		// Reverse for proper z-order
		for ( int i = (markers.Count - 1); i >= 0; i-- )
		{
			Marker fromMarker = markers[i];

			// Always drawing at least 1 marker
			fromMarker.Paint( canvas );

			// Draw arrow to destination marker				 
			if ( i > 0 )
			{
				Marker toMarker = markers[i - 1];

				// Arrow thickness/size relative to marker
				MarkerArrow arrow = new()
				{
					From = fromMarker,
					To = toMarker,
					IsDark = DarkArrows
				};

				arrow.Paint( canvas );
			}
		}
	}
}

//
