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

using DXLib.Log;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Provides static utility methods for filtering stats for an analysis query. Only stats matching the relevant action,
 * team, and dimensions will be included.
 */
public static class DataFilter
{
	/* Methods */

	// Creates list of filtered stats based on specified configuration
	public static async Task<DataStats> Filter( DataConfig config )
	{
		#if DEBUG
			DXProfiler.Start( true );
		#endif

		DataStats filtered = new();

		// Build list of all stats in scope
		DataStats rawStats = await Aggregate( config );

		// Filter each stat
		foreach ( Stat stat in rawStats )
		{
			switch ( config.DataSet )
			{
				// Playing Time
				case DataConfig.PlayingData:
				{
					if ( FilterPlaying( config, stat ) )
					{
						filtered.Add( stat );
					}

					break;
				}
				// Scoring
				case DataConfig.ScoringData:
				{
					if ( FilterScoring( config, stat ) )
					{
						filtered.Add( stat );
					}

					break;
				}
				// Sideout, Skills
				default:
				{
					if ( Filter( config, stat ) )
					{
						filtered.Add( stat );
					}

					break;
				}
			}
		}
		
		// Must save reference to raw list
		filtered.RawStats = rawStats;

		filtered.AddSets( rawStats.SetCount );
		filtered.AddRally( rawStats.RallyCount );

		#if DEBUG
			DXProfiler.Mark( "filter", true );
		#endif

		return filtered;
	}

	// Returns combined list of all roster players for specified scope
	public static List<Player> GetPlayers( DataConfig config )
	{
		// Primary team only
		if ( config.IsTeam1 )
		{
			switch ( config.Scope )
			{
				case DataConfig.OrganizationScope: return (config.ScopeObject as Organization)?.GetPlayers();
				case DataConfig.TeamScope: return (config.ScopeObject as Team)?.GetAllPlayers();
				case DataConfig.SeasonScope: return (config.ScopeObject as Season)?.Players;

				case DataConfig.TournamentScope: return (config.ScopeObject as Tournament)?.Season.Players;
				case DataConfig.TournamentDayScope: return (config.ScopeObject as TournamentDay)?.Tournament.Season.Players;

				case DataConfig.MatchScope: return (config.ScopeObject as Match)?.Season.Players;
				case DataConfig.SetScope: return (config.ScopeObject as Set)?.Match.Season.Players;

				case DataConfig.LineupScope: return (config.ScopeObject as Lineup)?.Season.Players;
				case DataConfig.StatisticianScope: return (config.ScopeObject as Statistician)?.Season.Players;
				case DataConfig.OpponentScope: return (config.ScopeObject as Opponent)?.Season.Players;
				case DataConfig.VenueScope: return (config.ScopeObject as Venue)?.Season.Players;
			}
		}

		return null;
	}

	// Creates list of all stats within specified scope
	private static async Task<DataStats> Aggregate( DataConfig config )
	{
		object obj = config.ScopeObject;

		// Force reload for remote in-progress set/match?
		bool force = config.ShouldForce();

		return config.Scope switch
		{
			DataConfig.SetScope   => await ((Set)obj).Aggregate( force ),
			DataConfig.MatchScope => await ((Match)obj).Aggregate( force ),

			DataConfig.TournamentDayScope => await ((TournamentDay)obj).Aggregate(),
			DataConfig.TournamentScope	  => await ((Tournament)obj).Aggregate(),
			DataConfig.SeasonScope		  => await ((Season)obj).Aggregate( config.TagFilter ),
			DataConfig.TeamScope		  => await ((Team)obj).Aggregate(),
			DataConfig.OrganizationScope  => await ((Organization)obj).Aggregate(),

			// Tags
			DataConfig.LineupScope		 => await ((Lineup)obj).Aggregate(),
			DataConfig.StatisticianScope => await ((Statistician)obj).Aggregate(),
			DataConfig.OpponentScope	 => await ((Opponent)obj).Aggregate(),
			DataConfig.VenueScope		 => await ((Venue)obj).Aggregate(),

			_ => null,
		};
	}

	/* Filter */

	// Determines if specified stat should be included in Playing Time filter
	private static bool FilterPlaying( DataConfig config, Stat stat )
	{
		return stat.IsPoint || (stat.IsAction && (stat.TeamId == config.TeamId));
	}

	// Determines if specified stat should be included in Scoring filter
	private static bool FilterScoring( DataConfig config, Stat stat )
	{
		return stat.IsPoint || stat.IsUpdate || stat.IsLineup || (stat.IsAction && (stat.TeamId == config.TeamId));
	}

	// Determines if specified stat should be included in filter
	private static bool Filter( DataConfig config, Stat stat )
	{
		// Action
		if ( FilterAction( config, stat ) )
		{
			// Team
			if ( FilterTeam( config, stat ) )
			{
				return true;
			}
		}

		return false;
	}

	// Filters stat against specified action (ONLY for shot/heat)
	private static bool FilterAction( DataConfig config, Stat stat )
	{
		string action = config.Action;

		// Not filtering on action
		if ( action == null )
		{
			return true;
		}

		// Exact match (serve, receive, defense, freeball)
		if ( stat.Action == action )
		{
			return true;
		}

		// Attack (do NOT include non-attack kills)
		if ( action == Stats.AttackKey )
		{
			if ( (stat.Action == Stats.SecondKey) || (stat.Action == Stats.ThirdKey) )
			{
				if ( stat.Selector == Stats.AttackKey )
				{
					return true;
				}
			}
		}

        // Free (offense)
        if ( action == Stats.FreeKey )
        {
            if ( (stat.Action == Stats.ThirdKey) && (stat.Selector == Stats.FreeKey) )
            {
                return true;
            }
        }

        // Set (do NOT include non-set assists)
        if ( action == Stats.SetKey )
		{
			// Normal set
			if ( (stat.Action == Stats.SecondKey) && (stat.Selector == Stats.SetKey) )
			{
				return true;
			}
		}

        // Other passing (first, putback)
        if ( action == Stats.PassingKey )
		{
			switch ( stat.Action )
			{
				case Stats.FirstKey:
				case Stats.PutbackKey:
				{
					return true;
				}
			}
		}

		// Filtered out
		return false;
	}

	// Filters stat against specified team
	private static bool FilterTeam( DataConfig config, Stat stat )
	{
		bool team1 = config.IsTeam1;

		// Organization scope includes all teams within org
		if ( team1 && (config.Scope == DataConfig.OrganizationScope) )
		{
			return true;
		}

		// Some data sets always use both teams
		bool bothTeams = (config.DataSet == DataConfig.SideoutData);

		// Matching team always included
		bool matching = (stat.TeamId == config.TeamId);

		// Larger than match scope for opponent includes all opponents
		bool aggregateOpp = !team1 && ((stat.TeamId == null) || (config.Team1 == null) || (stat.TeamId != config.Team1.UniqueId));

		return bothTeams || matching || aggregateOpp;
	}

	// Filters stat against specified dimension and filter list
	public static bool FilterDimension( DataDimension.Level level, DataConfig config, Stat stat, int playerNum )
	{
		bool outer = (level == DataDimension.Level.Outer);

		// Filtering outer or inner dimension?
		string dimension = outer ? config.OuterDim : config.InnerDim;
		List<object> filter = outer ? config.OuterFilter : config.InnerFilter;

		// Filter against dimension
		return KeyDimension.Filter( dimension, config, filter, stat, playerNum );
	}

	// Populates dimension keys for specified stat (GRID ONLY)
	public static string BuildDimension( DataConfig config, Stat stat, int playerNum, DataMetrics metrics )
	{
		string dataSet = config.DataSet;

		// Skill metrics only require action stats
		bool actionOnly = (dataSet == DataConfig.SkillsData);

		// Do NOT include dimension if access restricted
		if ( config.Filter( stat, actionOnly, playerNum ) )
		{
			DataDimension dimension = config.Dimension;

			string outer = config.OuterDim;
			string inner = config.InnerDim;

			bool nested = config.IsNested;
			bool sideout = (dataSet == DataConfig.SideoutData);

			// Type always innermost dimension
			string type = nested ? inner : outer;

			string outerKey = KeyDimension.CreateKey( outer, stat, playerNum, sideout );
			string key = null;

			// Must have at least outer key
			if ( outerKey != null )
			{
				string innerKey = KeyDimension.CreateKey( inner, stat, playerNum, sideout );

				// Dimension specific lookup key (player ID, rotation #, etc)
				key = nested ? $"{outerKey}|{innerKey}" : outerKey;

				int maxLen = config.MaxLabelLen;

				// Dimension specific display labels (player name, rotation #, etc)
				string outerLabel = KeyDimension.CreateLabel( outer, stat, playerNum, sideout, maxLen );
				string innerLabel = KeyDimension.CreateLabel( inner, stat, playerNum, sideout, maxLen );

				// Add metrics for specific dimension value (Josh, Ro5, etc)
				if ( !dimension.Metrics.ContainsKey( key ) )
				{
					// Populate dimensional info
					metrics.Type = type;
					metrics.TypeKey = nested ? outerKey : null;
					metrics.TypeLabel = nested ? outerLabel : null;

					// Object associated with dimension
					metrics.TypeObject = KeyDimension.GetObject( type, stat );

					metrics.Key = key;
					metrics.Label = nested ? innerLabel : outerLabel;

					// Add to list
					dimension.Metrics.Add( key, metrics );

					List<string> outerKeys = dimension.OuterKeys;

					// Outer/inner dimensions are flat, must remember outer
					if ( nested && !outerKeys.Contains( outerKey ) )
					{
						outerKeys.Add( outerKey );
					}
				}
			}
			else
			{
				DXLog.Debug( "OUTER action:{0} player:{1}", stat.Action, stat.Player?.FullName );
			}

			return key;
		}

		return null;
	}
}

//
