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

using DXLib.UI.Alert;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Implements Legacy engine specific functionality in conjunction with the state machine.
 */
public class Legacy
{
	/* Constants */

	// States
	public const string StartState	 = "start";
	public const string ServeState	 = "serve";
	public const string ReceiveState = "receive";
	public const string DefenseState = "defense";
	public const string SecondState	 = "second";
	public const string ThirdState   = "third";
	public const string BlockState	 = "block";

	// Legacy categories
	public const string Serving  = "serve";
	public const string Hitting  = "hit";
	public const string Passing  = "pass";
	public const string Blocking = "block";
	public const string Setting  = "set";
	public const string Defense  = "defense";
	public const string Free	 = "free";

	// Category keys
	public static readonly string[] CategoryKeys =
	[
		Serving,
		Hitting,
		Passing,
		Blocking,
		Setting,
		Defense
	];

	// Total number categories
	public static readonly int CategoryCount = CategoryKeys.Length;

	// Auto Set
	public const string AutoSetOff  = "off";
	public const string AutoSetSemi = "semi";
	public const string AutoSetFull = "full";

	// Serve Format
	public const string ServeAAE = "aae";
	public const string Serve04  = "04";

	// Available edit modes
	public enum EditMode
	{
		Off,
		Lineup,
		Sub
	};

	/* Properties */
	public int Subs => sm.Subs1;

	// Court/Smart locations
	public LegacyCourt Court { get; private set; }

	// Edit mode shortcuts
	public bool IsEditOff => editMode == EditMode.Off;
	public bool IsEditOn => editMode != EditMode.Off;

	public bool IsEditLineup => editMode == EditMode.Lineup;
	public bool IsEditSub => editMode == EditMode.Sub;

	/* Fields */

	// External refs
	private readonly LegacyState sm;
	private readonly LegacyScore score;

	private readonly PlayerGrid grid;

	// Static roster, current lineup
	private IList<Player> roster;
	private RecordLineup lineup;

	// Lineup support
	private LineupMenu lineupMenu;
	private LineupMenu subMenu;

	// Editing state control
	private EditMode editMode;

	// AutoFocus support
	private ButtonKey lastTap;

	// Stats for primary team only
	private Team team;

	/* Methods */
	public Legacy( LegacyState sm, LegacyScore score, PlayerGrid grid )
	{
		this.sm = sm;
		this.score = score;
		this.grid = grid;

		// Court/smart locations
		Court = new LegacyCourt( grid );

		// Defaults
		editMode = EditMode.Off;
	}

	// Post construction initialization
	public void Init( IList<Player> players )
	{
		roster = players;
	}

	// Called at start of each set
	public void Start()
	{
		// Used for all action stats
		team = sm.Team1;
	}

	// Called when ready for next rally
	public void InitRally()
	{
		Court.StartRally( false );

		// No SmartLocations until rally starts
		UpdateCourt();

		// Move to serve/receive
		AdvanceState( false, null );
		HandleAutoFocus();

		// MUST be last
		ResetLastTap();
	}

	// Called at start of rally, either by ball contact or undo/restore
	public void StartRally( bool smart = false )
	{
		Court.StartRally( true );

		// Force end to any in-progress timeout
		score.EndTimeout( null );

		// SmartLocations may have moved server/passer
		if ( smart && Shell.Settings.LegacySmart )
		{
			// OPTIMIZE: Avoid redundant redraws
			//HandleAutoFocus();

			// Rearrange in smart order
			if ( sm.IsRallyInProgress )
			{
				Court.UpdateSmart();
			}

			// Must update AFTER SmartLocations
			UpdateLastTap();
		}
	}

	/* UI Control */

	// Turns lineup editing mode on/off
	public void SetEditMode( EditMode mode, bool force, Action callback = null )
	{
		// Only update if necessary
		if ( (mode != editMode) || force )
		{
			bool turningOff = (mode == EditMode.Off);
			bool valid = true;

			// Persist when editing done
			if ( turningOff )
			{
				valid = SaveReplace( callback );
			}

			// Only proceed if valid
			if ( valid )
			{
				// Full Auto requires setter on court
				if ( turningOff && (Shell.Settings.LegacySet == AutoSetFull) )
				{
					LineupEntry setter = sm.GetSetter( true );

					if ( setter == null )
					{
						DXAlert.ShowOk( "alert.warn", "record.err.setter" );
					}
				}

				editMode = mode;

				// Update UI
				score.UpdateEditMode();
				grid.UpdateEditMode();
			}
		}
	}

	/* Lineup Control */

	// Returns list of all players currently on court plus libero(s)
	public List<Player> GetPlayers()
	{
		// Court
		List<Player> players = grid.GetPlayers();

		// Libero(s)
		players.AddRange( score.GetLiberos() );

		return players;
	}

	// Returns deep copied lineup entry for specified zone
	public LineupEntry GetEntry( int zone )
	{
		return new LineupEntry( lineup.Entries[ zone - 1 ] );
	}

	// Returns deep copy of current lineup entries
	public List<LineupEntry> GetEntries()
	{
		return lineup.GetEntries();
	}

	// Returns lineup entries from both court and libero(s)
	public List<LineupEntry> GetCourtEntries()
	{
		return
		[
			..grid.GetEntries(), 
			
			score.GetLiberoEntry( 1 ),
			score.GetLiberoEntry( 2 )
		];
	}

	// Sets initial court lineup at start of set (or following restore)
	public void SetLineup( RecordLineup recordLineup, int rotation )
	{
		lineup = recordLineup;

		// Update court players
		Court.SetEntries( lineup.Entries, rotation );

		// Update libero(s)
		score.SetLiberoEntries( lineup.Entries );
	}

	// Sets lineup entry for specified zone on court
	public void SetEntry( int zone, LineupEntry entry )
	{
		LineupEntry dest = lineup.Entries[ zone - 1 ];

		dest.Position = entry?.Position;
		dest.IsAltNumber = entry is { IsAltNumber: true };

		dest.Player = entry?.Player;
		dest.PlayerId = entry?.PlayerId;
	}

	// Returns zone (1-6) from lineup for specified player
	public int ZoneForPlayer( Player player )
	{
		for ( int zone = 1; zone <= lineup.EntryCount; zone++ )
		{
			LineupEntry entry = lineup.Entries[ zone - 1 ];

			// Lineup NOT court zone
			if ( !entry.IsEmpty && entry.Player.Equals( player ) )
			{
				return zone;
			}
		}

		return 0;
	}

	// Updates all players displayed on court
	public void UpdateCourt()
	{
		Court.SetEntries( lineup.Entries, sm.Rotation1 );
	}

	/* Sub/Swap */

	// Persists substitution
	public void HandleSub( LineupEntry entry )
	{
		// Persist
		sm.SubIn( entry );

		// Update UI
		score.UpdateSubs( Subs );
		UpdateCourt();
	}

	// Provides access to reusable substitution menu
	public LineupMenu GetSubMenu( int zone )
	{
		// Get all players on court plus libero(s)
		List<Player> activePlayers = GetPlayers();

		// Do not count empty slots
		int activeCount = activePlayers.Count( player => player != null );
		int count = (roster.Count - activeCount);

		// MUST have available players to sub
		if ( count < 1 )
		{
			return null;
		}

		// Lazily create menu
		subMenu ??= new LineupMenu( count, false )
		{
			MenuColor = team.Color
		};

		List<Player> subPlayers = new( count );
		List<LineupMenuRow.RowState> subStates = new( count );

		// Sub menu only includes players not already on court
		foreach ( Player player in roster )
		{
            if ( !activePlayers.Contains( player ) )
			{
                subPlayers.Add( player );

				// Highlight valid/invalid subs
				subStates.Add( sm.GetSubState( zone, player ) );
			}
		}

		subMenu.SetPlayers( subPlayers, subStates );

		return subMenu;
	}

	/* Replace */

	// Used internally to access lineup menu
	public LineupMenu GetLineupMenu()
	{
		// Lazily create
		if ( lineupMenu == null )
		{
			lineupMenu = new LineupMenu( roster.Count, true )
			{
				MenuColor = team.Color
			};

			lineupMenu.SetPlayers( roster );
		}

		return lineupMenu;
	}

	// Saves lineup change event
	public bool SaveReplace( Action callback = null )
	{
		// Snapshot before change
		List<LineupEntry> preLineup = GetEntries();

		// Get current court entries
		List<LineupEntry> entries = GetCourtEntries();

		// Create snapshot after change (lineup order)
		List<LineupEntry> postLineup = [ ..entries ];
		
		for ( int i = 0; i < Lineup.BaseEntries; i++ )
		{
			int index = Lineup.GetLineupIndex( i, sm.Rotation1 );

			postLineup[ index ] = entries[i];
		}

		// Include both liberos
		postLineup[ Lineup.LiberoZone1 - 1 ] = score.GetLiberoEntry( 1 );
		postLineup[ Lineup.LiberoZone2 - 1 ] = score.GetLiberoEntry( 2 );

		// Only persist if valid
		if ( RecordLineup.Validate( postLineup, true, callback ) )
		{
			// Only persist if there were changes
			if ( !lineup.Equals( postLineup ) )
			{
				sm.ChangeLineup( sm.Team1OnSideA, preLineup, postLineup );

				lineup.FromList( postLineup );
			}

			return true;
		}

		return false;
	}

	/* Action Handling */

	// Handles all stat button taps, main entry into Legacy state machine
	public async Task HandleKey( ButtonKey key )
	{
		bool error = key.IsError();
		bool earned = key.IsPoint();

		// Always primary team
		string teamId = team.UniqueId;

		// Create Legacy compatible event
		RecordData data = new()
		{
			Legacy = true,

			Player = key.Player,
			Position = key.Position,
            Number = key.Number,

            Action = GetAction( key ),
			Selector = GetSelector( key ),
			Rating = key.GetRating(),

			CourtPosition = key.Zone,
			CourtRow = key.GetCourtRow(),

			Result = key.GetResult(),
			IsError = error
		};

		// Handle BHEs
		ValidateBHE( data );

		// Track first serves
		HandleFirstServe( data );

		// Credit AutoSet attempt/assist (must occur FIRST)
		await HandleAutoSet( data );

        // Track pass conversions (must be AFTER AutoSet)
        HandleConversion( data, teamId );

        bool point = (error || earned);

		// Handle second block assist (becomes point)
		point |= HandleBlockAssist( key );

		// Support show last tap (must be BEFORE advance and point)
		ResetLastTap( key );

		// State machine
		AdvanceState( point, key );

		// Persist event
		await sm.ProcessAction( data, teamId );

		// Persist rally ending point
		if ( point )
		{
			await sm.ProcessPoint( data, team );
		}

		// Must be AFTER point
		HandleAutoFocus();

		// Mobile only
		if ( DXDevice.IsMobile )
		{
			grid.ClosePopup( key );
		}
	}

	// Maps Legacy category to RallyFlow Action
	private string GetAction( ButtonKey key )
	{
		return key.Category switch
		{
			// Most categories map directly to action
			Serving => Stats.ServeKey,
			Setting => Stats.SecondKey,
			Defense => Stats.DefenseKey,
			Blocking => Stats.BlockKey,
			
			// Differentiate normal attack/free from overpass
			Hitting or Free => (sm.State == DefenseState) ? Stats.OverpassKey : Stats.ThirdKey,
			
			// Serve receive or first ball pass?
			Passing => (sm.State == ReceiveState) ? Stats.ReceiveKey : Stats.FreeballKey,
			
			// Should never happen
			_ => null
		};
	}

	// Maps Legacy category to RallyFlow Selector
	private static string GetSelector( ButtonKey key )
	{
		return key.Category switch
		{
			Hitting => Stats.AttackKey,
			Free => Stats.FreeKey,
			Setting => Stats.SetKey,

			_ => null
		};
	}

	// Determines if event is BHE
	private static void ValidateBHE( RecordData data )
	{
		string error = null;

		// Legacy accuracy limited vs RallyFlow
		if ( (data.Result == Stats.ErrorKey) && !data.IsPoint )
		{
			error = data.Action switch
			{
				Stats.FreeKey or 
				Stats.FreeballKey or 
				Stats.SecondKey or 
				Stats.DefenseKey => Stats.BHEKey,
				
				_ => null
			};
		}

		data.Error = error;
	}

	// Handles all AutoFocus state transitions
	public void AdvanceState( bool point, ButtonKey key )
	{
		switch ( sm.State )
		{
			// Start -> Serve/Receive
			case StartState:
			{
				sm.State = sm.Team1Serving() ? ServeState : ReceiveState;
				break;
			}
			// Serve -> Defense
			case ServeState:
			{
				if ( !point )
				{
					sm.State = DefenseState;
				}

				StartRally( true );
				break;
			}
			// Receive -> Second
			case ReceiveState:
			{
				if ( !point )
				{
					sm.State = SecondState;
					StartRally( true );
				}

				break;
			}
			// Defense
			case DefenseState:
			{
				switch ( key.Category )
				{
					// -> Block
					case Blocking:
					{
						// First half of double block
						if ( key.Key == Stats.BlockAssistKey )
						{
							sm.State = BlockState;
						}

						break;
					}
					// -> Second
					default:
					{
						switch ( key.Category )
						{
							case Defense:
							case Passing:
							{
								sm.State = SecondState;
								break;
							}
						}

						break;
					}
					// Still on Defense
				}

				break;
			}
			// 2nd/3rd Ball
			case SecondState:
			case ThirdState:
			{
				switch ( key.Category )
				{
					// -> Block (first half of double block)
					case Blocking:
					{
						if ( key.Key == Stats.BlockAssistKey )
						{
							sm.State = BlockState;
						}

						break;
					}
					// -> Second (first ball overpassed)
					case Defense:
					case Passing:
					{
						sm.State = SecondState;
						break;
					}
					// -> Third
					case Setting:
					{
						sm.State = ThirdState;
						break;
					}
					// -> Defense
					default:
					{
						sm.State = DefenseState;
						break;
					}
				}

				break;
			}
		}
	}

	// Enables/disables all buttons for current AutoFocus state
	public void HandleAutoFocus()
	{
		string state = sm.State;

		if ( Shell.Settings.LegacyFocus )
		{
			switch ( state )
			{
				// Serve
				case ServeState:
				{
					grid.DisableAll();
					grid.DisableAddAll();

					// Serving only
					grid.EnableCategory( 1, Serving );

					grid.EnableAdd( 1 );
					break;
				}
				// Receive
				case ReceiveState:
				{
					grid.DisableAll();

					// Passing always on
					grid.EnableCategory( Passing );

					grid.EnableAddAll();
					break;
				}
				// Defense
				case DefenseState:
				{
					// No serving, setting
					grid.DisableCategory( Serving );
					grid.DisableCategory( Setting );

					// Can hit, block, defend
					grid.EnableCategory( Hitting );
					grid.EnableCategory( Blocking, true );
					grid.EnableCategory( Defense );

					// Can optionally pass
					HandlePass();

					grid.EnableAddAll();
					break;
				}
				// 2nd Ball
				case SecondState:
				{
					// No serving
					grid.DisableCategory( Serving );

					// Can hit, block, defend
					grid.EnableCategory( Hitting );
					grid.EnableCategory( Blocking, true );
					grid.EnableCategory( Defense );

					// Can optionally pass
					HandlePass();

					// AutoSet
					grid.EnableSetting();

					grid.EnableAddAll();
					break;
				}
				// 3rd Ball
				case ThirdState:
				{
					// No serving or setting
					grid.DisableCategory( Serving );
					grid.DisableCategory( Setting );

					// Can hit, block, defend
					grid.EnableCategory( Hitting );
					grid.EnableCategory( Blocking, true );
					grid.EnableCategory( Defense );

					// Can optionally pass
					HandlePass();

					grid.EnableAddAll();
					break;
				}
				// Block Assist
				case BlockState:
				{
					grid.DisableAll();

					// Frontrow block assist only
					grid.Enable( Blocking, Stats.BlockAssistKey, true );

					grid.EnableAddAll();
					break;
				}
			}
		}
	}

	// Enables/disables Passing category buttons based on current setting
	private void HandlePass()
	{
		// Pass on serve receive only
		if ( Shell.Settings.LegacyRecv )
		{
			grid.DisableCategory( Passing );
		}
		// Always on
		else
		{
			grid.EnableCategory( Passing );
		}
	}

	// Determines if last ball contact was overpass (does NOT cover all cases)
	private bool IsOverpass()
	{
		Stat stat = sm.PreviousStat();

		return !stat.IsPoint && (stat.IsServe || (stat.Action == Stats.ThirdKey));
	}

	// Automatically credits set assists and/or attempts
	private async Task HandleAutoSet( RecordData data )
	{
		// Only applicable for Attack action (and NOT overpass)
		if ( (data.Selector == Stats.AttackKey) && (sm.State != DefenseState) )
		{
			string autoSet = Shell.Settings.LegacySet;

			// With AutoSet ON
			if ( autoSet != AutoSetOff )
			{
				// No AutoSet on overpass kill
				if ( !IsOverpass() )
				{
					LineupEntry setter = sm.GetSetter( true );

					bool isSetter = (setter != null) && setter.Equals( data.Player );
					bool isKill = (data.Result == Stats.KillKey);

					switch ( autoSet )
					{
						// Attempt + Assist
						case AutoSetFull:
						{
							Stat prev = sm.PreviousStat();

							// Setter kill or any kill after non-setter set
							if ( isSetter || prev is { Selector: Stats.SetKey } )
							{
								if ( isKill )
								{
									// 'Auto' updated in db
									data.PrevResult = Stats.AssistKey;

									// Undo Auto Assist available
									score.UpdateUndoAuto( true );
								}
							}
							// Create new event for setter
							else
							{
								if ( setter != null )
								{
									RecordData prevData = new()
									{
										Legacy = true,

										Player = setter.Player,
										Position = setter.Position,
                                        Number = setter.Number,

                                        Action = Stats.SecondKey,
										Selector = Stats.SetKey,

										CourtPosition = setter.Zone,
										CourtRow = (int) setter.GetRow(),

										Result = isKill ? Stats.AssistKey : Stats.AttemptKey,
										IsError = false,

										// Auto generated
										Auto = true
									};

									// Undo Assist may be available
									score.UpdateUndoAuto( isKill );

									// Always primary team
									await sm.ProcessAction( prevData, team.UniqueId );
								}

								data.Action = Stats.ThirdKey;
							}

							break;
						}
						// Assist only (update existing event)
						case AutoSetSemi:
						{
							if ( isKill )
							{
                                Stat prev = sm.PreviousStat();

								// Only award assist if ATT was entered
								if ( (prev.Selector == Stats.SetKey) && (prev.Result == Stats.AttemptKey) )
								{
									data.PrevResult = Stats.AssistKey;
								}
							}

							break;
						}
					}
				}
			}
		}
	}

	// Deletes previous set assist from db
	public async Task UndoAutoAssist()
	{
		Stat stat = sm.PreviousStat( Stats.SecondKey );

		// Only delete if assist
		if ( stat.Result == Stats.AssistKey )
		{
			await stat.Delete( true );
		}
	}

	// Converts second block assist into point
	private bool HandleBlockAssist( ButtonKey key )
	{
		// Consecutive block assists
		return ((sm.State == BlockState) && (key.Key == Stats.BlockAssistKey));
	}

	// Handles special tracking for first serve stats
	private void HandleFirstServe( RecordData data )
	{
		if ( data.Action == Stats.ServeKey )
		{
			Stat prev = sm.PreviousStat();
			Stat prevServe = sm.PreviousStat( Stats.ServeKey );

			// First Serve if first stat, first serve, or first serve following sideout
			data.Value = (((prev == null) || (prevServe == null) || prev.IsSideout) ? 1 : 0);
		}
	}

	// Handles special tracking for pass-to-kill conversion
	private void HandleConversion( RecordData data, string teamId )
	{
		if ( data.Result == Stats.KillKey )
		{
            Stat stat1 = sm.PreviousStat( 1 );

            // 2nd ball pass-to-kill conversion
            if ( (stat1 != null) && (stat1.TeamId == teamId) && stat1.IsPassing() )
            {
                data.PrevValue = 1;
            }
			else
			{
				Stat stat2 = sm.PreviousStat( 2 );

				// 3rd ball pass-to-kill conversion
				if ( (stat2 != null) && (stat2.TeamId == teamId) && stat2.IsPassing() )
				{
					data.Prev2Value = 1;
				}
			}
        }
    }

	/* Tap State */

	// Resets sticky state of last tapped key
	public void ResetLastTap( ButtonKey key = null )
	{
		if ( lastTap != null )
		{
			grid.ResetTap( lastTap );
		}

		lastTap = key;
	}

	// Moves last tap key (SmartLocations)
	private void UpdateLastTap()
	{
		if ( lastTap != null )
		{
			// Find where last contacting player moved
			int zone = grid.ZoneForPlayer( lastTap.Player );

			// Reset last tap
			ButtonKey tap = new( lastTap )
			{
				Zone = zone
			};

			ResetLastTap( tap );

			// Show tap at new location
			grid.ShowTap( tap );
		}
	}
}

//
