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

using System.Text;

using DXLib.Utils;

namespace iStatVball3;

/*
 * Creates text lines for a history of stat events. Typically used for an
 * undo stack or event log.
 */
public class RecordHistory
{
	/* Constants */

	// Action display options
	public enum ActionModes
	{
		Action,
		Selector,
		ActionSelector
	};

	/* Properties */

	// Turn on/off line components
	public bool ShowTeams { get; set; }
	public bool ShowScores { get; set; }
	public bool ShowRatings { get; set; }
	public bool ShowErrors { get; set; }

	public bool ShowModifiers { get; set; }
	public bool ShowFaults { get; set; }
	public bool ShowMultiBlock { get; set; }

	// Action only, selector only, or action + selector
	public ActionModes ActionMode { get; set; }

	// Label length options
	public bool AbbreviateTeams { get; set; }
	public bool AbbreviateNames { get; set; }
	public bool AbbreviateResults { get; set; }

	public int MaxNameLength { get; set; }

	/* Fields */

	// Cached strings
	private Dictionary<string,string> actions;
	private Dictionary<string,string> updates;
	private Dictionary<string,string> lineups;

	private Dictionary<string,string> results;
	private Dictionary<string,string> selectors;
	private Dictionary<string,string> errors;

	private Dictionary<string,Dictionary<string,string>> modifiers;
	private Dictionary<string,Dictionary<string,string>> faults;

	// External ref
	private Match recordMatch;

	/* Methods */
	public RecordHistory()
	{
		// Defaults
		ShowTeams = true;
		ShowScores = false;
		ShowRatings = false;
		ShowErrors = false;

		ShowModifiers = false;
		ShowFaults = false;
		ShowMultiBlock = false;

		ActionMode = ActionModes.Selector;

		AbbreviateTeams = true;
		AbbreviateNames = true;
		AbbreviateResults = true;

		MaxNameLength = 11;
	}

	// Post construction initialization
	public void Init( Match match )
	{
		recordMatch = match;

		// Cached string LUTs
		actions = DXString.GetLookupTable( "history.action" );
		updates = DXString.GetLookupTable( "history.update" );
		lineups = DXString.GetLookupTable( "history.lineup" );

		results = DXString.GetLookupTable( AbbreviateResults ? "history.result.abbrev" : "history.result" );
		selectors = DXString.GetLookupTable( "history.selector" );
		errors = DXString.GetLookupTable( "history.error" );

		// Nested LUTs
		if ( ShowModifiers )
		{
			modifiers = GetNestedTable( "modifier" );
		}
		if ( ShowFaults )
		{
			faults = GetNestedTable( "fault" );
		}
	}

	// Reads table of tables from external resource file
	private Dictionary<string,Dictionary<string,string>> GetNestedTable( string baseKey)
	{
		// List of keys
		var keyTable = DXString.GetLookupTable( baseKey );
		var table = new Dictionary<string,Dictionary<string,string>>( keyTable.Count );

		// Read each nested table
		foreach ( string key in keyTable.Keys )
		{
			table.Add( key, DXString.GetLookupTable( $"{baseKey}.{key}" ) );
		}

		return table;
	}

	// Create history line from fields of specified stat
	public string CreateLine( Stat stat )
	{
		StringBuilder line = new();

		// Update stats handled separately
		if ( stat.IsUpdate )
		{
			CreateUpdate( stat, line );
		}
		// Lineup stats also separate
		else if ( stat.IsLineup )
		{
			CreateLineup( stat, line );
		}
		// All other stats 'TEAM Player Action Result' format
		else
		{
			CreateAction( stat, line );
		}

		return line.ToString();
	}

	/* Create */

	// Creates history line for Update events
	private void CreateUpdate( Stat stat, StringBuilder line )
	{
		// Not all updates show team
		switch ( stat.Selector )
		{
			case "timeout":
			case "rotation":
			case "score":
			{
				if ( ShowTeams ) line.Append( $"{GetTeam( stat.TeamId )} " );
				break;
			}
		}

		string selector = stat.Selector;

		// Special end set/match event
		if ( selector == Stats.EndKey )
		{
			string scope = DXString.Get( $"{stat.Modifier}.singular" );
			string ended = updates[ selector ];

			string msg = $"{scope} {ended}";

			line.Append( msg.ToUpper() );
		}
		// Normal update
		else
		{
			line.Append( updates[ stat.Selector ] );
		}
	}

	// Creates history line for Lineup events
	private void CreateLineup( Stat stat, StringBuilder line )
	{
		// Team
		if ( ShowTeams )
		{
			line.Append( $"{GetTeam( stat.TeamId )} " );
		}

		// Lineup Change
		if ( stat.Selector == "replace" )
		{
			line.Append( lineups[ "replace" ] );
		}
		// Sub/Swap X for Y
		else
		{
			line.Append( lineups[ stat.Selector ].Replace( "[]", stat.Number2 ).Replace( "{}", stat.Number ) );
		}
	}

	// Creates history line for normal Action or Point events
	private void CreateAction( Stat stat, StringBuilder line )
	{
		bool point = stat.IsPoint;
		string teamId = point ? stat.Point?.TeamId : stat.TeamId;

		// Team
		if ( point || ShowTeams )
		{
			line.Append( $"{GetTeam( teamId )} " );
		}

		Player player = stat.Player;

		// Player stat
		if ( !point && (player != null) )
		{
			int count = 1;

			// Multi-block?
			if ( stat.Player2 != null )
			{
				count++;
			}
			if ( stat.Player3 != null )
			{
				count++;
			}

			// Special handling for multi-block
			if ( stat.IsBlock && (count > 1) )
			{
				if ( ShowMultiBlock )
				{
					// 2 blockers
					line.Append( $"{stat.Player.GetAbbrevFirst()}, " );
					line.Append( $"{stat.Player2?.GetAbbrevFirst()}" );

					// Possibly 3
					if ( count > 2 )
					{
						line.Append( $", {stat.Player3?.GetAbbrevFirst()}" );
					}
					
					line.Append( ' ' );
				}
			}
			// Single player
			else
			{
				string name = AbbreviateNames ? player.GetAbbrevFirst( MaxNameLength ) : player.GetFirstLast( MaxNameLength );

				line.Append( $"{name} " );
			}
		}

		// Action (includes modifiers)
		line.Append( $"{GetAction( stat )} " );

		// Forced point
		if ( point && (stat.Selector == Stats.ForcedKey) )
		{
			line.Append( $"({updates[ stat.Selector ]})" );
		}

		if ( point )
		{
			// Optionally append current score
			if ( ShowScores )
			{
				line.Append( $" ({stat.Point?.Score1}-{stat.Point?.Score2})" );
			}
		}
		else
		{
			// Result (0-action shows ATT)
			line.Append( $"{GetResult( stat )}" );

			// Optionally add error
			if ( ShowErrors && stat.IsError )
			{
				string key = stat.Error;

				// Legacy does not have error/fault fields
				if ( key != null )
				{
					// Fault
					if ( ShowFaults && (key == Stats.FaultKey) )
					{
						string fault = GetFault( stat );

						if ( fault != null ) line.Append( $" {fault}" );
					}

					string error = errors[ key ];

					// Error
					line.Append( $" {error}" );
				}
			}

			// Optionally add rating
			if ( ShowRatings && stat.Rating.HasValue )
			{
				line.Append( $" {GetRating( stat )}" );
			}
		}
	}

	/* Get */

	// Generates display label for specified team
	public string GetTeam( string teamId )
	{
		return recordMatch.LabelForTeam( teamId, AbbreviateTeams );
	}

	// Generates action label for specified stat
	public string GetAction( Stat stat )
	{
		string modifier = null;

		// Optionally show modifiers
		if ( ShowModifiers && !stat.IsPoint )
		{
			modifier = GetModifier( stat );
		}

		string action = actions[ stat.Action ];

		switch ( stat.Action )
		{
			// Some actions have selectors
			case Stats.SecondKey:
			case Stats.ThirdKey:
			{
				if ( stat.Selector != null )
				{
					string selector = selectors[ stat.Selector ];

					// '3rd Ball' or 'Attack' or '3rd Ball Attack'
					if ( modifier == null )
					{
						return ActionMode switch
						{
							ActionModes.Action => action,
							ActionModes.Selector => selector,
							ActionModes.ActionSelector => $"{action} {selector}",
							
							_ => null,
						};
					}
					
					// '3rd Ball Tip' or 'Attack Tip' or '3rd Ball Attack Tip'
					return ActionMode switch
					{
						ActionModes.Action => $"{action} {modifier}",
						ActionModes.Selector => $"{selector} {modifier}",
						ActionModes.ActionSelector => $"{action} {selector} {modifier}",
						
						_ => null,
					};
				}

				return action;
			}
			// Everything else just uses action
			default:
			{
				return (modifier == null) ? action : $"{modifier} {action}";
			}
		}
	}

	// Generates result label for specified stat
	public string GetResult( Stat stat )
	{
		string result = stat.Result;

		if ( result != null )
		{
			// Special handling for block assists
			if ( (stat.Action == Stats.BlockKey) && (result == Stats.BlockAssistsKey) )
			{
				return results[ stat.Legacy ? Stats.BlockAssistKey : Stats.BlockAssistsKey ];
			}
			
			// Default to attempt
			return results.TryGetValue( stat.Result, out var result1 ) ? result1 : results[ Stats.AttemptKey ];
		}

		return null;
	}

	// Generates error label for specified stat
	public string GetError( Stat stat )
	{
		string result = stat.Result;

		// Only applicable for errors
		if ( result is Stats.ErrorKey )
		{
			string error = stat.Error;

			if ( error != null )
			{
				return errors[ error ];
			}
		}

		return null;
	}

	// Generates modifier label for specified stat
	private string GetModifier( Stat stat )
	{
		string modifier = stat.Modifier;

		// Modifier always optional
		if ( modifier != null )
		{
			string key = GetModifierKey( stat.Action, stat.Selector );
			var modifierList = modifiers[ key ];

			if ( modifierList.TryGetValue( modifier, out var modifier1 ) )
			{
				return modifier1;
			}
		}

		return null;
	}

	// Returns lookup key for modifier LUT based on specified stat
	public static string GetModifierKey( Stat stat )
    {
		return GetModifierKey( stat.Action, stat.Selector );
    }

	// Returns lookup key for modifier LUT based on stat action/selector
	public static string GetModifierKey( string action, string selector )
	{
		return action switch
		{
			Stats.SecondKey => (selector == Stats.SetKey) ? Stats.SetKey : Stats.Attack2Key,
			Stats.ThirdKey => (selector == Stats.AttackKey) ? Stats.Attack3Key : Stats.FreeKey,
			Stats.ReceiveKey or Stats.FirstKey or Stats.FreeballKey or Stats.PutbackKey => Stats.PassKey,

			_ => action,
		};
	}

	// Generates rating label for specified stat
	private byte GetRating( Stat stat )
	{
		byte rating = (byte) stat.Rating!;

		// Passing
		switch ( stat.Action )
		{
			case Stats.ReceiveKey:
			case Stats.DefenseKey:
			case Stats.FirstKey:
			case Stats.FreeballKey:
			case Stats.PutbackKey:
			{
				// Map 0-4 to 0-3
				if ( Shell.Settings.IsPass03 )
				{
					return (byte)((rating > 0) ? (rating - 1) : 0);
				}

				break;
			}
		}

		// 0-4
		return rating;
	}

	// Generates fault label for specified stat
	private string GetFault( Stat stat )
	{
		if ( stat.Fault != null )
		{
			string key = GetFaultKey( stat.Action, stat.Selector );

			return faults[ key ][ stat.Fault ];
		}

		return null;
	}

	// Returns lookup key for fault LUT based on stat action/selector
	public static string GetFaultKey( string action, string selector )
	{
		return action switch
		{
			Stats.SecondKey => (selector == Stats.SetKey) ? Stats.SetKey : Stats.Attack2Key,
			Stats.ThirdKey => (selector == Stats.AttackKey) ? Stats.Attack3Key : Stats.FreeKey,
			Stats.FirstKey or Stats.FreeballKey or Stats.PutbackKey => Stats.PassKey,
			Stats.OverpassKey or Stats.BlockKey => Stats.BlockKey,

			_ => action,
		};
	}
}

//
