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

using Plugin.Firebase.Firestore;

using DXLib.Data;
using DXLib.Data.Model;

using DXLib.Utils;

namespace iStatVball3;

/*
 * A container for the 6 players in the starting lineup for a volleyball set. The lineup also contains two slots for
 * optional liberos. Each player in the lineup has a corresponding position and jersey number. The lineup can also
 * include entries for automatic substitutions and libero swaps.
 */
public class Lineup : DXModel
{
	/* Constants */
	public const string CollectionKey = "Lineups";

	public const int BaseEntries = 6;
	public const int MaxEntries = 8;
	public const int MaxLiberos = 2;

	// Front/backrow
	public const int RowEntries = (BaseEntries / 2);
	public const int LastBackrowZone = 5;

	// Libero
	public const int LiberoZoneOut = 4;

	public const int LiberoZone1 = 7;
	public const int LiberoZone2 = 8;

	// Error
	public const int InvalidZone = -1;

	// Positions
	public const string SetterKey = "s";
	public const string OppositeKey = "opp";
	public const string OutsideKey = "oh";
	public const string MiddleKey = "mb";
	public const string LiberoKey = "lib";
	public const string SpecialistKey = "ds";

	public static readonly string EmptyPosition = DXString.Get( "lineup.empty" );

	/* Properties */

	// Required
	[FirestoreProperty("Name")] public string Name { get; set; }
	[FirestoreProperty("Description")] public string Description { get; set; }

	// Optional
	[FirestoreProperty("Notes")] public string Notes { get; set; }

	// Starters (array of maps)
	[FirestoreProperty("Entries")] public IList<LineupEntry> Entries { get; set; }

	// Substitutions (array of maps)
	[FirestoreProperty("Subs")] public IList<LineupEntryReplace> Subs { get; set; }

	// Libero swaps (array of maps)
	[FirestoreProperty("Swaps")] public IList<LineupEntryReplace> Swaps { get; set; }

	// Parent (FK)
	[FirestoreProperty("SeasonId")] public string SeasonId { get; set; }

	/* Ignored */
	public override string ObjectName => Name;

	// Parent
	public Season Season { get; set; }

	// Ignored

	/* Methods */
	public Lineup()
	{
		BaseCollectionKey = CollectionKey;

		// Allocate containers
		Entries = new List<LineupEntry>();
		Subs = new List<LineupEntryReplace>();
		Swaps = new List<LineupEntryReplace>();
	}

	// Tests equality based on unique ID
	public override bool Equals( object obj )
	{
		return (obj is Lineup lineup) && lineup.UniqueId.Equals( UniqueId );
	}

	// Generates unique hash code, required for Equals
	public override int GetHashCode()
	{
		return UniqueId.GetHashCode();
	}

	// Determines if at least 1 player in lineup has photo defined
	public bool HasPhoto()
	{
		return GetPlayers().Any( player => player is { HasPhoto: true } );
	}

	// Copies persisted lineup entries from source to destination list
	public static void CopyEntries( IList<LineupEntry> source, IList<LineupEntry> destination )
	{
		if ( (source != null) && (destination != null) )
		{
			destination.Clear();

			foreach ( LineupEntry entry in source )
			{
				destination.Add( entry );
			}
		}
	}

	/* Player */

    // Determines if specified lineup entries contain given player
    public static bool HasPlayer( IList<LineupEntry> entries, Player player )
    {
        return (entries != null) && entries.Any( entry => entry.Equals( player ) );
    }

    // Returns all players from underlying lineup entry list
    public List<Player> GetPlayers()
    {
        List<Player> list = new( MaxEntries );
        
        // Build list
        list.AddRange( from entry in Entries select entry.Player );
        
        return list;
    }

    /* Position */

    // Returns lineup position for specified player
    public string GetPosition( Player player )
	{
		if ( player != null )
		{
			foreach ( LineupEntry entry in Entries )
			{
				if ( !entry.IsEmpty && entry.Player.Equals( player ) )
				{
					return entry.Position;
				}
			}
		}

		return null;
	}

	// Returns list of all positions (optionally excluding libero)
	public static List<DXItem> GetPositions( bool libero = false )
	{
		List<DXItem> list = DXString.GetLookupList( "player.position" );

		// Optionally exclude libero
		if ( !libero )
		{
			DXItem lib = list.FirstOrDefault( item => item.Key.Equals( LiberoKey ) );

			list.Remove( lib );
		}

		return list;
	}

	// Returns new position for specified incoming player
	public static string GetNewPosition( Player inPlayer, string outPos )
	{
		string position = null;

		if ( inPlayer != null )
		{
			string rosterPos = null;

			// Use incoming roster position (if only 1)
			if ( inPlayer.Positions.Count == 1 )
			{
				string firstPos = inPlayer.Positions[0];

				// Can NOT be libero
				rosterPos = (firstPos == LiberoKey) ? null : firstPos;
			}					

			// Otherwise inherit position from outgoing player
			position = rosterPos ?? (IsPositionEmpty( outPos ) ? EmptyPosition : outPos);
		}

		return position;
	}

	// Determines if specified position has a value
	public static bool IsPositionEmpty( string position )
	{
		return (position == null) || (position == EmptyPosition);
	}

	/* Utilities */

	// Converts from lineup to rotation index
	public static int GetRotationIndex( int index, int rotation )
	{
		return (BaseEntries - ((rotation - 1) - index)) % BaseEntries;
	}

	// Converts from rotation to lineup index
	public static int GetLineupIndex( int index, int rotation )
	{
		return ((rotation - 1) + index) % BaseEntries;
	}

	// Determines if specified zone represents a libero
	public static bool IsLiberoZone( int zone )
	{
		return zone is LiberoZone1 or LiberoZone2;
	}

	/* Populate */

	// Populates all in-memory reference objects (no db)
	public void Populate()
	{
		// Starters
		foreach ( LineupEntry entry in Entries )
		{
			entry.Populate( Season );
		}

		// Subs
		if ( Subs != null )
		{
			foreach ( LineupEntryReplace entry in Subs )
			{
				entry.Populate( Season );
			}
		}

		// Swaps
		if ( Swaps != null )
		{
			foreach ( LineupEntryReplace entry in Swaps )
			{
				entry.Populate( Season );
			}
		}
	}

	/* Permissions */

	// Determines if user has permission to create Lineups
	public static bool CanCreate( Season season, User user )
	{
		return user.Level switch
		{
			// Director/coach/stat can
			User.LevelType.Director or 
			User.LevelType.Coach or 
			User.LevelType.Statistician => !season.IsSample || user.IsAdmin,
			
			// No-one else can
			_ => false
		};
	}

	// Determines if user has permission to edit Lineups
	public static bool CanEdit( User user )
	{
		return user.Level switch
		{
			// Director/coach/stat always can
			User.LevelType.Director or 
			User.LevelType.Coach or 
			User.LevelType.Statistician => true,
			
			// No-one else can
			_ => false
		};
	}

	// Determines if user has permission to analyze Lineup stats
	public bool CanAnalyze( User user )
	{
		return user.Level switch
		{
			// Director/Coach can
			User.LevelType.Director or 
			User.LevelType.Coach => true,
			
			// No-one else
			_ => Season.IsSample
		};
	}

	/* Analyze */

	// Aggregates all data for analyzing scope of this Lineup
	public async Task<DataStats> Aggregate()
	{
		DataStats stats = new();
		List<Match> matches = Season.GetMatches();
	
		// Append stats from all sets using this lineup
		foreach ( Match match in matches )
		{
			foreach ( Set set in match.Sets )
			{
				if ( (set.Lineup1 != null) && set.Lineup1.Equals( this ) )
				{
					stats.Add( await set.Aggregate() );
				}
			}
		}
	
		return stats;
	}
	
	// Aggregates all raw summary data for scope of this Lineup
	public StatSummary AggregateRaw()
	{
		StatSummary summary = new();
		List<Match> matches = Season.GetMatches();
	
		// Aggregate summary data for all matches using this lineup
		foreach ( Match match in matches )
		{
			foreach ( Set set in match.Sets )
			{
				if ( (set.Lineup1 != null) && set.Lineup1.Equals( this ) )
				{
					summary.Add( set.StatSummary );
				}
			}
		}
	
		return summary;
	}

	/* CRUD */

	// Performs cascading delete on this Lineup
	public override async Task Delete( bool remove = true )
	{
		// No children to delete (maps auto deleted)

		// Remove from parent
		if ( remove )
		{
			Season.Lineups.Remove( this );
		}

		// Delete self
		await base.Delete( remove );
	}
}

//
