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

using DXLib.UI;
using DXLib.UI.Layout;
using DXLib.UI.Container;

using DXLib.UI.Form;
using DXLib.UI.Form.Control;

using DXLib.UI.Control;
using DXLib.UI.Control.Button;

using DXLib.Data;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Displays a list of tree select controls, each allowing the user to select a combination of options for a filter
 * component (team, action, etc). The combined list of selections from all controls can be used with LogFilter to filter
 * event log display.
 * 
 * FilterView can be displayed either as an embedded view or from within a popup.
 */
public class FilterView : DXGridLayout
{
	/* Events */
	public Action Filtered { get; set; }
	public Action Cleared { get; set; }

	/* Properties */

	// Optionally limit filtering to 1 player
	public string PlayerId { get; set; }

	/* Fields */
	private DXGridLayout layout;
	private DXScroll scroll;

	// Header
	private DXGridLayout header;
	private DXLabel setLbl;

	private DXButton clearBtn;
	private DXButton filterBtn;

	// Controls
	private readonly List<DXTreeSelect> trees;

	/* Fields */
	private Set filterSet;

	/* Methods */
	public FilterView()
	{
		BackgroundColor = DXColors.Light4;

		Padding = 0;
		RowSpacing = 0;
		ColumnSpacing = 0;

		Horizontal = LayoutOptions.Fill;
		Vertical = LayoutOptions.Fill;

		IgnoreSafeArea = true;
		
		// Allocate container
		trees = [];
	}

	// Post-construction initialization
	public void Init( Set set )
	{
		filterSet = set;

		// Header
		CreateHeader();

		// Control layout
		layout = new DXGridLayout
		{
			BackgroundColor = DXColors.Light4,
			
			Horizontal = LayoutOptions.Fill,
			Vertical = LayoutOptions.Fill,

			IgnoreSafeArea = true
		};

		// Scrollable control area
		scroll = new DXScroll
		{
			BackgroundColor = DXColors.Light4,
			Orientation = ScrollOrientation.Vertical,

			Content = layout
		};

		// 2 rows
		AddFixedRow( 50 );				// 0: header
		AddStarRow();					// 1: trees

		Add( header, 0, 0 );
		Add( scroll, 0, 1 );

		// Add underlying controls
		CreateControls();

		// Update initial state
		UpdateTrees();
	}

	// Creates button header UI
	private void CreateHeader()
	{
		// Button layout
		header = new DXGridLayout
		{
			BackgroundColor = DXColors.Light2,

			Padding = new Thickness( 20, 4, 15, 4 ),
			RowSpacing = 0,
			ColumnSpacing = 5
		};

		string text = $"{DXString.Get( "set.singular" )} {filterSet.Number}";

		// Set X
		setLbl = new DXLabel
		{
			Text = text,
			TextColor = DXColors.Dark1,

			Font = DXFonts.RobotoBold,
			FontSize = 26,

			VAlign = TextAlignment.Center,
			Horizontal = LayoutOptions.Start,
			Vertical = LayoutOptions.Center
		};

		const double buttonWd = 69;

		// Clear
		clearBtn = new DXButton
		{
			Resource = "filter.clear",
			Type = DXButton.ButtonType.Neutral,

			Margin = 0,
			IsDisabled = true,

			ButtonTapped = OnClearTapped
		};

		clearBtn.Init();
		
		// Filter
		filterBtn = new DXButton
		{
			Resource = "filter.filter",
			Type = DXButton.ButtonType.Action,

			Margin = 0,
			IsDisabled = true,

			ButtonTapped = OnFilterTapped
		};

		filterBtn.Init();
		
		// 3 columns
		header.AddStarColumn();					// 0: set
		header.AddFixedColumn( buttonWd );		// 1: delete
		header.AddFixedColumn( buttonWd );		// 2: filter

		// Add components
		header.Add( setLbl, 0, 0 );
		header.Add( clearBtn, 1, 0 );
		header.Add( filterBtn, 2, 0 );
	}

	/* Controls */

	// Adds all filter controls
	private void CreateControls()
	{
		AddTeam();
		AddRotation();
		AddPlayer();

		AddAction();
		AddSelector();
		AddModifier();

		AddResult();
		AddError();
		AddFault();

		AddRating();
		AddStartArea();
		AddEndArea();
	}

	// Adds control for filtering by Team
	private void AddTeam()
	{
		Match match = filterSet.Match;

		string opponentId = match.Team2Id ?? Opponent.AnonOpponent;

		List<DXItem> items =
		[
			new ( match.Team1Id, match.Team1Name ),
			new ( opponentId, match.Team2Name )
		];

		AddTree( "team", "team.plural", items, OnTeamSelected );
	}

	// Adds control for filtering by Rotation
	private void AddRotation()
	{
		AddZone( "rotation", "filter.rotation", "filter.ro" );
	}

	// Adds control for filtering by Player
	private void AddPlayer()
	{
		List<DXItem> items = [];

		// Add players from primary team roster
		foreach ( Player player in filterSet.Match.Season.Players )
		{
			// May be restricted to single player
			if ( (PlayerId == null) || (PlayerId == player.UniqueId) )
			{
				items.Add( new DXItem( player.UniqueId, player.FullName ) );
			}
		}

		AddTree( "player", "player.plural", items );
	}

	// Adds control for filtering by Action
	private void AddAction()
	{
		List<DXItem> items = DXString.GetLookupList( "action" );

		// Add special actions
		items.Add( new DXItem( Stats.PointKey, DXString.Get( "filter.point" ) ) );
		items.Add( new DXItem( Stats.LineupKey, DXString.Get( "filter.lineup" ) ) );
		items.Add( new DXItem( Stats.UpdateKey, DXString.Get( "filter.update" ) ) );

		AddTree( "action", "filter.action", items, OnActionSelected );
	}

	// Adds control for filtering by Selector
	private void AddSelector()
	{
		AddTree( "selector", "filter.selector", [], OnSelectorSelected );

		UpdateSelector();
	}

	// Adds control for filtering by Modifier
	private void AddModifier()
	{
		AddTree( "modifier", "filter.modifier", [] );

		UpdateModifier();
	}

	// Adds control for filtering by Result
	private void AddResult()
	{
		List<DXItem> items = DXString.GetLookupList( "history.result" );

		AddTree( "result", "filter.result", items, OnResultSelected );

		UpdateResult();
	}

	// Adds control for filtering by Error
	private void AddError()
	{
		List<DXItem> items = DXString.GetLookupList( "history.error" );
		List<DXItem> deduped = DXItem.DistinctByValue( items );

		AddTree( "error", "filter.error", deduped, OnErrorSelected );
	}

	// Adds control for filtering by Fault
	private void AddFault()
	{
		AddTree( "fault", "filter.fault", [] );

		UpdateFault();
	}

	// Adds control for filtering by Rating
	private void AddRating()
	{
		AddTree( "rating", "filter.rating", [] );

		UpdateRating();
	}

	// Adds control for filtering by Start Area
	private void AddStartArea()
	{
		AddZone( "start", "filter.start", "filter.area" );
	}

	// Adds control for filtering by End Area
	private void AddEndArea()
	{
		AddZone( "end", "filter.end", "filter.area" );
	}

	// Adds control for filtering by court zone (1-6)
	private void AddZone( string key, string title, string prefix )
	{
		const int count = Lineup.BaseEntries;
		List<DXItem> items = new( count );

		string prefixStr = DXString.Get( prefix );

		// Add Foo1 - Foo6
		for ( int zone = 1; zone <= count; zone++ )
		{
			items.Add( new DXItem( zone.ToString(), $"{prefixStr} {zone}" ) );
		}

		AddTree( key, title, items );
	}

	// Creates and adds individual tree control
	private void AddTree( string key, string title, List<DXItem> items, Action action = null )
	{
		string titleStr = DXString.Get( title );

		// Create
		DXTreeSelect tree = new()
		{
			Key = key,

			IsRequired = true,
			Title = title,
			Label = titleStr,

			Selected = action ?? OnSelected,
		};

		tree.Init();
		tree.SetRoot( key, titleStr );

		trees.Add( tree );

		// Populate
		PopulateTree( tree, items );
	}

	// Populates and configures specified tree with given data
	private static void PopulateTree( DXTreeSelect tree, List<DXItem> items )
	{
		// Populate
		tree.SetChildItems( items );

		// Config
		tree.SelectAll();
		tree.SetState( DXFormControl.ControlState.Normal );
	}

	/* Filter */

	// Returns filter object populated will all current selections
	public Filter GetFilter()
	{
		return new Filter
		{
			Match = filterSet.Match,

			Teams = new FilterDimension( TeamTree ),
			Rotations = new FilterDimension( RotationTree, (PlayerId != null) ),
			Players = new FilterDimension( PlayerTree ),

			Actions = new FilterDimension( ActionTree ),
			Selectors = new FilterDimension( SelectorTree ),
			Modifiers = new FilterDimension( ModifierTree ),
			
			Results = new FilterDimension( ResultTree ),
			Errors = new FilterDimension( ErrorTree ),
			Faults = new FilterDimension( FaultTree ),

			Ratings = new FilterDimension( RatingTree ),
			StartAreas = new FilterDimension( StartTree ),
			EndAreas = new FilterDimension( EndTree )
		};
	}

	// Determines if specified user has full filter permissions
	private static bool CanFilter( User user )
	{
		return user.Level switch
		{
			// Only coach/director have full access
			User.LevelType.Coach or User.LevelType.Director => true,
			
			// No-one else does
			_ => false
		};
	}

	/* Convenience Accessors */

	// Get Tree
	private DXTreeSelect TeamTree => GetTree( "team" );
	private DXTreeSelect RotationTree => GetTree( "rotation" );
	private DXTreeSelect PlayerTree => GetTree( "player" );

	private DXTreeSelect ActionTree => GetTree( "action" );
	private DXTreeSelect SelectorTree => GetTree( "selector" );
	private DXTreeSelect ModifierTree => GetTree( "modifier" );

	private DXTreeSelect ResultTree => GetTree( "result" );
	private DXTreeSelect ErrorTree => GetTree( "error" );
	private DXTreeSelect FaultTree => GetTree( "fault" );

	private DXTreeSelect RatingTree => GetTree( "rating" );
	private DXTreeSelect StartTree => GetTree( "start" );
	private DXTreeSelect EndTree => GetTree( "end" );

	// Get Keys
	private List<string> TeamKeys => TeamTree.SelectedKeys;

	private List<string> ActionKeys => ActionTree.SelectedKeys;
	private List<string> SelectorKeys => SelectorTree.SelectedKeys;

	private List<string> ResultKeys => ResultTree.SelectedKeys;
	private List<string> ErrorKeys => ErrorTree.SelectedKeys;

	// Returns tree control matching specified key
	private DXTreeSelect GetTree( string key )
	{
		return trees.FirstOrDefault( tree => tree.Key == key );
	}

	/* Update */

	// Updates all trees dependent on other selections
	private void UpdateTrees()
	{
		UpdateRotation();
		UpdatePlayer();

		UpdateSelector();
		UpdateModifier();

		UpdateResult();
		UpdateError();
		UpdateFault();

		UpdateRating();
	}

	// Updates Rotation tree based on permissions
	private void UpdateRotation()
	{
		RotationTree.IsDisabled = !CanFilter( Shell.CurrentUser );
	}

	// Updates Player tree based on team selection
	private void UpdatePlayer()
	{
		// Only Team 2 selected?
		bool team2Only = (TeamKeys.Count == 1) && (TeamKeys[0] == filterSet.Match.Team2Id);

		// Player tree NA for opponent
		PlayerTree.IsDisabled = team2Only;
	}

	// Updates Selector tree based on action selection
	private void UpdateSelector()
	{
		List<DXItem> items = [];

		// Add all selectors corresponding to selected actions
		foreach ( string key in ActionKeys )
		{
			List<DXItem> selectors = DXString.GetLookupList( $"selector.{key}" );

			if ( selectors.Count > 0 )
			{
				items.AddRange( selectors );
			}
		}

		// Remove duplicates
		List<DXItem> distinct = items.Distinct().ToList();

		// Populate
		SelectorTree.IsDisabled = (distinct.Count == 0);
		PopulateTree( SelectorTree, distinct );
	}

	// Updates Modifier tree based on action selection
	private void UpdateModifier()
	{
		List<DXItem> items = [];

		// Add all modifiers corresponding to selected actions
		foreach ( string key in ActionKeys )
		{
			switch ( key )
			{
				// 1:1 mappings
				case Stats.ServeKey:
				case Stats.DefenseKey:
				case Stats.OverpassKey:
				case Stats.BlockKey:
				{
					items.AddRange( DXString.GetLookupList( $"modifier.{key}" ) );
					break;
				}
				// Passing
				case Stats.ReceiveKey:
				case Stats.FirstKey:
				case Stats.FreeballKey:
				case Stats.PutbackKey:
				{
					items.AddRange( DXString.GetLookupList( "modifier.pass" ) );
					break;
				}
				// Second Ball
				case Stats.SecondKey:
				{
					if ( SelectorKeys.Contains( Stats.SetKey ) )
					{
						items.AddRange( DXString.GetLookupList( "modifier.set" ) );
					}
					if ( SelectorKeys.Contains( Stats.AttackKey ) )
					{
						items.AddRange( DXString.GetLookupList( "modifier.attack2" ) );
					}
					
					break;
				}
				// Third Ball
				case Stats.ThirdKey:
				{
					if ( SelectorKeys.Contains( Stats.AttackKey ) )
					{
						items.AddRange( DXString.GetLookupList( "modifier.attack3" ) );
					}
					if ( SelectorKeys.Contains( Stats.FreeKey ) )
					{
						items.AddRange( DXString.GetLookupList( "modifier.free" ) );
					}
					
					break;
				}
			}
		}

		// Remove duplicates
		List<DXItem> distinct = items.Distinct().ToList();

		// Populate
		ModifierTree.IsDisabled = (distinct.Count == 0);
		PopulateTree( ModifierTree, distinct );
	}

	// Updates Result tree based on action selection
	private void UpdateResult()
	{
		// All actions have at least Attempt/Error
		List<DXItem> items =
		[
			GetResult( Stats.AttemptKey ),
			GetResult( Stats.ErrorKey )
		];

		// Add all results corresponding to selection actions
		foreach ( string action in ActionKeys )
		{
			switch ( action )
			{
				case Stats.ServeKey: items.Add( GetResult( Stats.AceKey ) ); break;
				case Stats.SecondKey: items.Add( GetResult( Stats.AssistKey ) ); break;
				case Stats.DefenseKey: items.Add( GetResult( Stats.DigKey ) ); break;
				case Stats.BlockKey:
				{
					string block = DXString.Get( "filter.block" );

					items.Add( GetResult( Stats.BlockKey, block ) );
					items.Add( GetResult( Stats.BlockAssistKey, block ) );
					break;
				}
			}

			// Most actions also have Kill result
			if ( (action != Stats.ServeKey) && (action != Stats.BlockKey) )
			{
				items.Add( GetResult( Stats.KillKey ) );
			}
		}

		// Remove duplicates
		List<DXItem> distinct = items.Distinct().ToList();

		// Populate
		PopulateTree( ResultTree, distinct );
	}

	// Dynamically builds result display label
	private static DXItem GetResult( string key, string prefix = null )
	{
		DXItem item = DXString.GetLookupItem( "history.result", key );

		// 'Block Solo'
		if ( prefix != null )
		{
			item.Value = $"{prefix} {item.Value}";
		}

		return item;
	}

	// Error tree only valid if error result selected
	private void UpdateError()
	{
		ErrorTree.IsDisabled = !ResultKeys.Contains( Stats.ErrorKey );
	}

	// Updates Fault tree based on action selection
	private void UpdateFault()
	{
		// Only valid for fault error
		bool enabled = !ErrorTree.IsDisabled && ErrorKeys.Contains( Stats.FaultKey );

		FaultTree.IsDisabled = !enabled;

		if ( enabled )
		{
			List<DXItem> items = new List<DXItem>();

			// Add all faults corresponding to selected actions
			foreach ( string key in ActionKeys )
			{
				switch ( key )
				{
					// 1:1 mappings
					case Stats.ServeKey:
					case Stats.ReceiveKey:
					case Stats.DefenseKey:
					{
						items.AddRange( DXString.GetLookupList( $"fault.{key}" ) );
						break;
					}
					// Passing
					case Stats.FirstKey:
					case Stats.FreeballKey:
					case Stats.PutbackKey:
					{
						items.AddRange( DXString.GetLookupList( "fault.pass" ) );
						break;
					}
					// Blocking
					case Stats.OverpassKey:
					case Stats.BlockKey:
					{
						items.AddRange( DXString.GetLookupList( "fault.block" ) );
						break;
					}
					// Second Ball
					case Stats.SecondKey:
					{
						if ( SelectorKeys.Contains( Stats.SetKey ) )
						{
							items.AddRange( DXString.GetLookupList( "fault.set" ) );
						}
						if ( SelectorKeys.Contains( Stats.AttackKey ) )
						{
							items.AddRange( DXString.GetLookupList( "fault.attack2" ) );
						}
						
						break;
					}
					// Third Ball
					case Stats.ThirdKey:
					{
						if ( SelectorKeys.Contains( Stats.AttackKey ) )
						{
							items.AddRange( DXString.GetLookupList( "fault.attack3" ) );
						}
						if ( SelectorKeys.Contains( Stats.FreeKey ) )
						{
							items.AddRange( DXString.GetLookupList( "fault.free" ) );
						}
						
						break;
					}
				}
			}

			// Remove duplicates
			List<DXItem> distinct = items.Distinct().ToList();

			// Populate
			FaultTree.IsDisabled = (distinct.Count == 0);
			PopulateTree( FaultTree, distinct );
		}
	}

	// Updates Rating tree based on action selection
	private void UpdateRating()
	{
		List<DXItem> items = [];

		// Add all ratings corresponding to selected actions
		foreach ( string key in ActionKeys )
		{
			switch ( key )
			{
				// Serve always 0-4
				case Stats.ServeKey:
				{
					items.AddRange( GetRatings( 4 ) );
					break;
				}
				// Set always 0-4
				case Stats.SecondKey:
				{
					if ( SelectorKeys.Contains( Stats.SetKey ) )
					{		
						items.AddRange( GetRatings( 4 ) );
					}

					break;
				}
				// Passing either 0-3 or 0-4
				case Stats.ReceiveKey:
				case Stats.DefenseKey:
				case Stats.FirstKey:
				case Stats.FreeballKey:
				case Stats.PutbackKey:
				{
					int max = Shell.Settings.IsPass03 ? 3 : 4;

					items.AddRange( GetRatings( max ) );
					break;
				}
			}
		}

		// Remove duplicates
		List<DXItem> distinct = items.Distinct().ToList();

		// Populate
		RatingTree.IsDisabled = (distinct.Count == 0);
		PopulateTree( RatingTree, distinct );
	}

	// Returns list of O-N rating items
	private static List<DXItem> GetRatings( int max )
	{
		List<DXItem> ratings = [];

		// 0-3 or 0-4
		for ( int rating = 0; rating <= max; rating++ )
		{
			string ratingStr = rating.ToString();
			ratings.Add( new DXItem( ratingStr, ratingStr ) );
		}

		return ratings;
	}

	// Resets clear/filter to valid
	private void Reset()
	{
		clearBtn.IsDisabled = false;
		filterBtn.IsDisabled = false;
	}

	/* Event Callbacks */

	// User wants to clear all filter selections
	private void OnClearTapped( object sender )
	{
		// Default to everything selected
		foreach ( DXTreeSelect tree in trees )
		{
			tree.SelectAll();
		}

		UpdateTrees();

		// Buttons not valid
		clearBtn.IsDisabled = true;
		filterBtn.IsDisabled = true;

		// Callback listener
		Cleared?.Invoke();
	}

	// User tapped filter
	private void OnFilterTapped( object sender )
	{
		// Filter no longer valid until modified
		filterBtn.IsDisabled = true;

		// Callback listener
		Filtered?.Invoke();
	}

	// Control selection changed
	private void OnSelected()
	{
		Reset();
	}

	// Updates dependent controls when Team selection changes
	private void OnTeamSelected()
	{
		UpdatePlayer();

		Reset();
	}

	// Updates dependent controls when Action selection changes
	private void OnActionSelected()
	{
		UpdateSelector();
		UpdateModifier();

		UpdateResult();
		UpdateError();
		UpdateFault();

		UpdateRating();

		Reset();
	}

	// Updates dependent controls when Selector selection changes
	private void OnSelectorSelected()
	{
		UpdateModifier();
		UpdateFault();
		UpdateRating();

		Reset();
	}

	// Updates dependent controls when Result selection changes
	private void OnResultSelected()
	{
		UpdateError();
		UpdateFault();

		Reset();
	}

	// Updates dependent controls when Error selection changes
	private void OnErrorSelected()
	{
		UpdateFault();

		Reset();
	}

	/* Layout */

	// Landscape (4:3)
	protected override void Landscape()
	{
		Update( 3, 3 );
	}

	// Portrait (3:4)
	protected override void Portrait()
	{
		Update( 2, 2 );
	}

	// Mobile Portrait
	protected override void MobilePortrait()
	{
		Update( 2, 2 );
	}

	// Mobile Landscape
	protected override void MobileLandscape()
	{
		Update( 2, 2 );		
	}
	
	// Redraws components with orientation specific number of columns
	public void Update( int columns, int maxDisplay )
	{
		layout.ClearAll();

		// Padding
		layout.Padding = 20;
		layout.RowSpacing = 10;
		layout.ColumnSpacing = 20;

		// Determine number rows
		int count = trees.Count;
		int rows = (count / columns) + 1;

		layout.AddStarColumns( columns );
		layout.AddFixedRows( rows, 70 );

		// Layout grid
		for ( int i = 0; i < count; i++ )
		{
			int row = (i / columns);
			int col = (i % columns);

			DXTreeSelect tree = trees[i];

			tree.MaxDisplay = maxDisplay;
			tree.UpdateField();

			layout.Add( tree, col, row );
		}
	}
}

//
