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

using System.Diagnostics;

using DXLib.Log;

namespace iStatVball3;

/*
 * Provides utility methods for accumulating stat totals for a Skill data set. After accumulation, the skill metrics can
 * be calculated.
 */
public static class MetricsSkillAccumulator
{
	/* Constants */
	private const string StarterSymbol = " *";

	/* Methods */

	// Accumulates all dimensional metrics from specified stats list
	public static void AccumulateDimension( DataConfig config, DataStats stats )
	{
		#if DEBUG
			Stopwatch timer = new();
			timer.Start();
		#endif

		config.Dimension = new DataDimension();
		config.Totals = new MetricsSkill( true );

		MetricsSkill totals = (MetricsSkill)config.Totals;

		// Single pass through stats list
		foreach ( Stat stat in stats )
		{
			AccumulateStat( config, totals, stat );

			// Block can have multiple players in same stat
			if ( stat.IsBlock )
			{
				AccumulateStat( config, totals, stat, 2 );
				AccumulateStat( config, totals, stat, 3 );
			}
		}

		#if DEBUG
			timer.Stop();
			DXLog.Trace( "SKILL count:{0} time:{1}ms", stats.Count, timer.ElapsedMilliseconds );
		#endif
	}

	// Accumulates individual stat within a dimension
	private static void AccumulateStat( DataConfig config, MetricsSkill totals, Stat stat, int playerNum = 1 )
	{
		// Create outer/inner keys for dimension
		string key = DataFilter.BuildDimension( config, stat, playerNum, new MetricsSkill() );

		// Accumulate all metrics for dimension value
		if ( key != null )
		{
			MetricsSkill metrics = (MetricsSkill) config.Dimension.Metrics[ key ];

			Accumulate( metrics, stat, playerNum );
		}

		// Add to totals (even if anonymous dimension)
		Accumulate( totals, stat, playerNum );
	}

	/* Metrics */

	// Accumulates all metrics from specified stat
	public static void Accumulate( DataMetrics metrics, Stat stat, int playerNum = 1 )
	{
		MetricsSkill skills = (MetricsSkill)metrics;

		switch ( stat.Action )
		{
			// Serve
			case Stats.ServeKey:
			{
				AccumulateServe( skills, stat );
				break;
			}
			// Second Ball
			case Stats.SecondKey:
			{
				switch ( stat.Selector )
				{
					// Set
					case Stats.SetKey:
					{
						AccumulateSet( skills, stat );
						break;
					}
					// Attack
					case Stats.AttackKey:
					{
						AccumulateAttack( skills, stat );
						break;
					}
					// Debug
					default:
					{
						skills.Unknown_Selector++;
						break;
					}
				}

				break;
			}
			// Third Ball
			case Stats.ThirdKey:
			{
				switch ( stat.Selector )
				{
					// Attack
					case Stats.AttackKey:
					{
						AccumulateAttack( skills, stat );
						break;
					}
					// Free (offense)
					case Stats.FreeKey:
					{
						AccumulateFree( skills, stat );
						break;
					}
					// Debug
					default:
					{
						skills.Unknown_Selector++;
						break;
					}
				}

				break;
			}
            // Receive
            case Stats.ReceiveKey:
            {
                AccumulateReceive( skills, stat );
                break;
            }
            // Defense
            case Stats.DefenseKey:
			{
				AccumulateDefense( skills, stat );
				break;
			}
			// Freeball (defense)
            case Stats.FreeballKey:
            {
                AccumulateFreeball( skills, stat );
                break;
            }
            // First Ball, Putback
            case Stats.FirstKey:
			case Stats.PutbackKey:
			{
				AccumulatePass( skills, stat );
				break;
			}
			// Block
			case Stats.BlockKey:
			{
				AccumulateBlock( skills, stat, playerNum );
				break;
			}
			// Overpass
			case Stats.OverpassKey:
			{
				AccumulateOverpass( skills, stat );
				break;
			}
			// Debug
			default:
			{
				skills.Unknown_Action++;
				break;
			}
		}

		// Non-action specific variables
		AccumulatePlayer( skills, stat, playerNum );
		AccumulateGeneral( skills, stat );
	}

	// Accumulates all player variables (used by some custom grids)
	private static void AccumulatePlayer( MetricsSkill metrics, Stat stat, int playerNum )
	{
		// NA for totals accumulation
		if ( !metrics.IsTotal )
		{
			Player player = null;
			string position = null;

			// Use appropriate player
			switch ( playerNum )
			{
				case 1: player = stat.Player;  position = stat.Position;  break;
				case 2: player = stat.Player2; position = stat.Position2; break;
				case 3: player = stat.Player3; position = stat.Position3; break;
			}

			// Build labels
			if ( player != null )
			{
				string suffix = string.Empty;
				string namePos = player.GetNamePosition( position );

				// Append '*' if in starting lineup
                if ( Lineup.HasPlayer( stat.Set.Starters, player ) )
                {
                    suffix = StarterSymbol;
                }

                metrics.Player_Number = player.Number;
				metrics.Player_NamePos = $"{namePos}{suffix}";
			}
		}
	}

	// Accumulates all raw General variables
	private static void AccumulateGeneral( MetricsSkill metrics, Stat stat )
	{
		// Number of times around rotation
		metrics.General_Rotations += (stat.IsServe && stat.IsValueTrue()) ? 1 : 0;

		// BHEs span across actions
		if ( stat.IsAction && stat.Error is Stats.BHEKey )
		{
			metrics.General_BHEs++;
		}
	}

	// Accumulates all raw Serve variables
	private static void AccumulateServe( MetricsSkill metrics, Stat stat )
	{
		// Result
		switch ( stat.Result )
		{
			case Stats.AceKey: metrics.Serve_Aces++; break;
			case Stats.ErrorKey: metrics.Serve_Errors++; break;
			default: metrics.Serve_Zeros++; break;
		}

		// Points on serve
		metrics.Serve_Points += stat.Earned;

		// Rating
		if ( stat.Rating != null )
		{
			switch ( stat.Rating )
			{
				case 0: metrics.Serve_Rating0++; break;
				case 1: metrics.Serve_Rating1++; break;
				case 2: metrics.Serve_Rating2++; break;
				case 3: metrics.Serve_Rating3++; break;
				case 4: metrics.Serve_Rating4++; break;
			}
		}

		// Error types
		if ( stat.IsError )
        {
			switch ( stat.Error )
            {
				case Stats.NetKey: metrics.Serve_Errors_Net++; break;
				case Stats.AntKey: metrics.Serve_Errors_Ant++; break;
				case Stats.OutKey: metrics.Serve_Errors_Out++; break;
				case Stats.FaultKey: metrics.Serve_Errors_Fault++; break;
				default: metrics.Serve_Errors_Other++; break;
			}
		}

		// First serve
		if ( stat.IsFirstServe )
		{
			switch ( stat.Result )
			{
				case Stats.AceKey: metrics.Serve_First_Aces++; break;
				case Stats.ErrorKey: metrics.Serve_First_Errors++; break;
				default: metrics.Serve_First_Zeroes++; break;
			}
		}
	}

	// Accumulates all raw Set variables
	private static void AccumulateSet( MetricsSkill metrics, Stat stat )
	{
		// Result
		switch ( stat.Result )
		{
			case Stats.AssistKey: metrics.Set_Assists++; break;
			case Stats.ErrorKey: metrics.Set_Errors++; break;
			default: metrics.Set_Zeros++; break;

			// Set can be kill (still an attempt)
			case Stats.KillKey:
			{
				metrics.Attack_OtherKills++;
				metrics.Set_Zeros++;
				break;
			}
		}

		// Rating
		if ( stat.Rating != null )
		{
			switch ( stat.Rating )
			{
				case 0: metrics.Set_Rating0++; break;
				case 1: metrics.Set_Rating1++; break;
				case 2: metrics.Set_Rating2++; break;
				case 3: metrics.Set_Rating3++; break;
				case 4: metrics.Set_Rating4++; break;
			}
		}
	}

	// Accumulates all raw Attack variables
	private static void AccumulateAttack( MetricsSkill metrics, Stat stat )
	{
		// Result
		switch ( stat.Result )
		{
			case Stats.KillKey: metrics.Attack_Kills++; break;
			case Stats.ErrorKey: metrics.Attack_Errors++; break;
			default: metrics.Attack_Zeros++; break;
		}

		// Error types
		if ( stat.IsError )
        {
			switch ( stat.Error )
            {
				case Stats.NetKey: metrics.Attack_Errors_Net++; break;
				case Stats.AntKey: metrics.Attack_Errors_Ant++; break;
				case Stats.OutKey: metrics.Attack_Errors_Out++; break;
				case Stats.FourKey: metrics.Attack_Errors_Four++; break;
				case Stats.BlockKey: metrics.Attack_Errors_Block++; break;
				case Stats.FaultKey: metrics.Attack_Errors_Fault++; break;
				default: metrics.Attack_Errors_Other++; break;
			}
        }
	}

    // Accumulates all raw Free (offense) variables
    private static void AccumulateFree( MetricsSkill metrics, Stat stat )
    {
        // Result
        switch ( stat.Result )
        {
            case Stats.KillKey:
			{
                metrics.Free_Kills++;
				metrics.Attack_OtherKills++;	// MUST count here as well
				break;
            }
            case Stats.ErrorKey:
			{
				metrics.Free_Errors++;
				break;
			}
            default:
			{
				metrics.Free_Zeros++;
				break;
			}
        }
    }

    // Accumulates all raw Serve Receive variables
    private static void AccumulateReceive( MetricsSkill metrics, Stat stat )
    {
        string result = stat.Result;

        // Result
        if ( result == Stats.ErrorKey )
        {
            metrics.Receive_Errors++;
        }
        else
        {
            metrics.Receive_Zeros++;

            switch ( result )
            {
	            // Receive can be kill or assist
	            case Stats.KillKey:
	            {
		            metrics.Attack_OtherKills++;
		            break;
	            }
	            case Stats.AssistKey:
	            {
		            metrics.Set_OtherAssists++;
		            break;
	            }
            }

            // Track receive-to-kill conversions
            if ( stat.IsValueTrue() )
            {
                metrics.Receive_Converts++;
            }
        }

        // TRE (already counted towards team error total)
        if ( stat.Error == Stats.TREKey )
        {
            metrics.Receive_TREs++;
        }

        // Rating
        if ( stat.Rating != null )
        {
            bool recv03 = Shell.Settings.IsPass03;

            // Display as 0-3 or 0-4
            switch ( stat.Rating )
            {
                case 0: metrics.Receive_Rating0++; break;
                case 1: if ( recv03 ) metrics.Receive_Rating0++; else metrics.Receive_Rating1++; break;
                case 2: if ( recv03 ) metrics.Receive_Rating1++; else metrics.Receive_Rating2++; break;
                case 3: if ( recv03 ) metrics.Receive_Rating2++; else metrics.Receive_Rating3++; break;
                case 4: if ( recv03 ) metrics.Receive_Rating3++; else metrics.Receive_Rating4++; break;
            }
        }
    }

    // Accumulates all raw Defense variables
    private static void AccumulateDefense( MetricsSkill metrics, Stat stat )
	{
		// Result
		switch ( stat.Result )
		{
			case Stats.AttemptKey: metrics.Defense_Zeros++; break;
			case Stats.DigKey: metrics.Defense_Digs++; break;
			case Stats.ErrorKey: metrics.Defense_Errors++; break;

			// Can result in kill or assist (both still count as dig)
			case Stats.KillKey:
			{
				metrics.Attack_OtherKills++;
				metrics.Defense_Digs++;
				break;
			}
			case Stats.AssistKey:
			{
				metrics.Set_OtherAssists++;
				metrics.Defense_Digs++;
				break;
			}
		}

		// Rating
		if ( stat.Rating != null )
		{
			bool def03 = Shell.Settings.IsPass03;

			// Display as 0-3 or 0-4
			switch ( stat.Rating )
			{
				case 0: metrics.Defense_Rating0++; break;
				case 1: if ( def03 ) metrics.Defense_Rating0++; else metrics.Defense_Rating1++; break;
				case 2: if ( def03 ) metrics.Defense_Rating1++; else metrics.Defense_Rating2++; break;
				case 3: if ( def03 ) metrics.Defense_Rating2++; else metrics.Defense_Rating3++; break;
				case 4: if ( def03 ) metrics.Defense_Rating3++; else metrics.Defense_Rating4++; break;
			}
		}

		// Dig-to-kill conversions
		if ( stat.IsValueTrue() )
		{
			metrics.Defense_Converts++;
		}
	}

    // Accumulates all Freeball (defense) variables
    private static void AccumulateFreeball( MetricsSkill metrics, Stat stat )
    {
        string result = stat.Result;

        // Result
        if ( result == Stats.ErrorKey )
        {
            metrics.Freeball_Errors++;
        }
        else
        {
            metrics.Freeball_Zeros++;

            switch ( result )
            {
	            // Freeball can be kill or assist
	            case Stats.KillKey:
	            {
		            metrics.Attack_OtherKills++;
		            break;
	            }
	            case Stats.AssistKey:
	            {
		            metrics.Set_OtherAssists++;
		            break;
	            }
            }

            // Track freeball-to-kill conversions
            if ( stat.IsValueTrue() )
            {
                metrics.Freeball_Converts++;
            }
        }

        // Rating
        if ( stat.Rating != null )
        {
            bool pass03 = Shell.Settings.IsPass03;

            // Display as 0-3 or 0-4
            switch ( stat.Rating )
            {
                case 0: metrics.Freeball_Rating0++; break;
                case 1: if ( pass03 ) metrics.Freeball_Rating0++; else metrics.Freeball_Rating1++; break;
                case 2: if ( pass03 ) metrics.Freeball_Rating1++; else metrics.Freeball_Rating2++; break;
                case 3: if ( pass03 ) metrics.Freeball_Rating2++; else metrics.Freeball_Rating3++; break;
                case 4: if ( pass03 ) metrics.Freeball_Rating3++; else metrics.Freeball_Rating4++; break;
            }
        }
    }

    // Accumulates all raw Pass (First Ball, Putback) variables
    private static void AccumulatePass( MetricsSkill metrics, Stat stat )
	{
		string result = stat.Result;

		// Result
		if ( result == Stats.ErrorKey )
		{
			metrics.Pass_Errors++;
		}
		else
		{
			metrics.Pass_Zeros++;

			switch ( result )
			{
				// Pass can be kill or assist
				case Stats.KillKey:
				{
					metrics.Attack_OtherKills++;
					break;
				}
				case Stats.AssistKey:
				{
					metrics.Set_OtherAssists++;
					break;
				}
			}

			// Track pass-to-kill conversions
			if ( stat.IsValueTrue() )
			{
				metrics.Pass_Converts++;
			}
		}

		// Rating
		if ( stat.Rating != null )
		{
			bool pass03 = Shell.Settings.IsPass03;

			// Display as 0-3 or 0-4
			switch ( stat.Rating )
			{
				case 0: metrics.Pass_Rating0++; break;
				case 1: if ( pass03 ) metrics.Pass_Rating0++; else metrics.Pass_Rating1++; break;
				case 2: if ( pass03 ) metrics.Pass_Rating1++; else metrics.Pass_Rating2++; break;
				case 3: if ( pass03 ) metrics.Pass_Rating2++; else metrics.Pass_Rating3++; break;
				case 4: if ( pass03 ) metrics.Pass_Rating3++; else metrics.Pass_Rating4++; break;
			}
		}
	}

	// Accumulates all raw Block variables
	private static void AccumulateBlock( MetricsSkill metrics, Stat stat, int playerNum )
	{
		// Block only event with multiple players
		Player extraPlayer = playerNum switch
		{
			2 => stat.Player2,
			3 => stat.Player3,
			
			_ => null
		};

		if ( (playerNum == 1) || (extraPlayer != null) )
		{
			// Result
			switch ( stat.Result )
			{
				case Stats.BlockKey: metrics.Block_Blocks++; break;
				case Stats.BlockAssistsKey: metrics.Block_Assists++; break;
				case Stats.ErrorKey: metrics.Block_Errors++; break;
				default: metrics.Block_Zeros++; break;
			}
		}
	}

	// Accumulates all raw Overpass variables
	private static void AccumulateOverpass( MetricsSkill metrics, Stat stat )
	{
		// Result (counts towards both overpass and attacks)
		switch ( stat.Result )
		{
			case Stats.KillKey: metrics.Over_Kills++; metrics.Attack_OtherKills++; break;
			case Stats.ErrorKey: metrics.Over_Errors++; metrics.Attack_OtherErrors++; break;
			default: metrics.Over_Zeros++; metrics.Attack_OtherZeros++; break;
		}
	}
}

//
