/*
 * 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.Alert;
using DXLib.UI.Layout;
using DXLib.UI.Gestures;
using DXLib.UI.Control.Button;

using DXLib.Log;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Responsible for all layout of one of the two team bars in the recording UI. Each bar contains controls for managing
 * lineup, rotation, libero swaps, and substitutions. If the associated team is an anonymous opponent, the team bar is
 * not applicable and all controls will be hidden. If the team is using team-only or full roster mode, then controls
 * will be visible but disabled.
 */
public class TeamBar : DXAbsoluteLayout
{
	/* Constants */
	private const int MaxDividers = 5;

	/* Properties */
	public bool IsSideA { get; set; }
	public bool IsActive { get; private set; }

	public IList<Player> Roster { get; set; }

	// Custom color
	public Color Color => (barTeam == null) ? Opponent.DefaultColor : barTeam.Color;

	// Overlay currently open?
	public bool IsOpen => overlay.IsOpen;

	// Open and blocking court interaction?
	public bool IsModalOpen => overlay.IsOpen && !overlay.IsPreview;

	// Convenience external refs
	public RallyState StateMachine => (RallyState)engine.StateMachine;

	public new int Rotation => IsSideA ? StateMachine.RotationA : StateMachine.RotationB;
	public int Subs => IsSideA ? StateMachine.SubsA : StateMachine.SubsB;

	// Special embedded sync mode?
	public bool IsSyncMode => engine.IsSyncMode;

	// Should lineup/sub/swap return to preview mode after close?
	private bool ReturnToPreview => Shell.Settings.RallyPreview && !StateMachine.IsRallyInProgress;

	/* Fields */
	private DrawerIcon lineupBtn;
	private DrawerButton rotationBtn;
	private DrawerPanel serverBtn;
	private DrawerPanel liberoBtn;
	private DrawerButton subsBtn;

	private TeamLiberoSwitch liberoSwitch;

	private TeamButton swapBtn;
	private TeamButton subBtn;

	private List<DrawerDivider> dividers;

	// Court/libero overlays
	private readonly TeamOverlay overlay;

	// External references
	private readonly RallyEngine engine;

	private Team barTeam;
	private RecordLineup barLineup;

	// State control
	private bool overlayOpen;
	private bool hasLibero;

	/* Methods */
	public TeamBar( RallyEngine engine, DXAbsoluteGestures layout )
	{
		this.engine = engine;

		BackgroundColor = DXColors.Dark2;

		// REQUIRED for mobile
		IgnoreSafeArea = true;
		IsClippedToBounds = true;
		
		Horizontal = LayoutOptions.Fill;
		Vertical = LayoutOptions.Fill;
		
		// Create overlays (empty initially)
		overlay = new TeamOverlay( this, layout )
		{
			Substituted = OnSubstituted,
			Swapped = OnSwapped
		};
	}

	// Performs all post-construction initialization
	public void Init( Team team, IList<Player> roster )
	{
		barTeam = team;

		IsActive = true;

		// MUST reset here
		Clear();

		// Lineup
		lineupBtn = new DrawerIcon( this )
		{
			Title = "teambar.lineup",
			Resource = "player",
			Color = DXColors.Action,

			IsSticky = true,
			ButtonTapped = OnLineupTapped,
			ButtonPressed = OnLineupPressed
		};

		lineupBtn.Init();
		
		// Rotation
		rotationBtn = new DrawerButton( this )
		{
			Title = "teambar.rotation",

			Color = DXColors.Light4,
			NumberColor = DXColors.Dark1,

			IsSticky = true,
			ButtonPressed = OnRotationPressed
		};

		rotationBtn.Init();
		
		// Server
		serverBtn = new DrawerPanel( this )
		{
			Title = "teambar.server"
		};

		serverBtn.Init();
		
		// Liberos 1/2
		liberoSwitch = new TeamLiberoSwitch
		{
			Switched = OnLiberoSwitched
		};

		Add( liberoSwitch );

		// Libero
		liberoBtn = new DrawerPanel( this )
		{
			Title = "teambar.libero"
		};

		liberoBtn.Init();
		
		// Swap
		swapBtn = new TeamButton( this )
		{
			Resource = "teambar.swapin",
			Type = DXButton.ButtonType.Positive,

			ButtonTapped = OnSwapTapped
		};

		swapBtn.Init();
		
		// Subs
		subsBtn = new DrawerButton( this )
		{
			Title = "teambar.subs",
			Color = DXColors.Light4,
			NumberColor = DXColors.Dark1,

			IsSticky = true,
			ButtonPressed = OnSubsPressed
		};

		subsBtn.Init();
		
		// Sub
		subBtn = new TeamButton( this )
		{
			Resource = "teambar.sub",
			Type = DXButton.ButtonType.Action,

			ButtonTapped = OnSubTapped
		};

		subBtn.Init();
		
		// Dividers
		dividers = new List<DrawerDivider>( MaxDividers );

		for ( int i = 0; i < MaxDividers; i++ )
		{
			dividers.Add( new DrawerDivider( this ) );
		}

		// Init overlays
		overlay.Init( roster );
	}

	// Deep copies specified lineup into overlay
	public void SetLineup( RecordLineup lineup )
	{
		if ( IsActive )
		{
			barLineup = lineup;

			// Update UI
			UpdateOverlay();
			UpdateServer( false );
			UpdateLibero();
		}
	}

	// Swaps libero off court back to libero area
	public void SwapOut( int zone, bool auto )
	{
		if ( IsActive )
		{
			// Swap on court
			overlay.SwapLibero( zone, StateMachine.Libero1 );

			// Save event
			SaveSubSwap( Stats.SwapOutKey, zone, auto );

			// Update UI
			UpdateLibero();
			UpdateServer();
		}
	}

	// Determines correct label to use for swap button
	private string GetSwapResource()
	{
		// NA if no liberos defined
		if ( overlay.IsLiberoEmpty )
		{
			return "teambar.na";
		}
		
		// 'SWAP IN' or 'SWAP OUT'
		bool swapIn = overlay.IsLiberoOut;

		// Shortened labels for mobile
		if ( DXDevice.IsMobile )
		{
			return swapIn ? "teambar.swpin" : "teambar.swpout";
		}

		// And widescreen landscape
		if ( DXDevice.IsTabletWide && DXDevice.IsLandscape() )
		{
			return swapIn ? "teambar.swpin" : "teambar.swpout";
		}

		// All others use full length
		return swapIn ? "teambar.swapin" : "teambar.swapout";
	}

	// Determines correct color to use for swap button
	private Color GetSwapColor()
	{
		// Empty is disabled
		if ( overlay.IsLiberoEmpty )
		{
			return DXColors.Dark3;
		}
		
		// In/Out
		return overlay.IsLiberoIn ? DXColors.Negative : DXColors.Positive;
	}

	// Find zone of libero on court, or 0 if NA
	public int FindLibero()
	{
		return IsActive ? overlay.FindLibero() : 0;
	}

	/* Lineup */

	// Returns current server (zone 1)
	public LineupEntry GetServer()
	{
		return overlay.GetServer();
	}

	// Returns current setter (backrow first)
	public LineupEntry GetSetter()
	{
		return overlay.GetSetter();
	}

	// Returns list of all players currently on court (rotation order)
	public List<LineupEntry> GetTeam()
	{
		return overlay.GetTeam();
	}

	// Returns list of players currently in frontrow (zones 2-4)
	public List<LineupEntry> GetFrontrow()
	{
		return overlay.GetFrontrow();
	}

	// Returns list of all entries from court and libero area (rotation order)
	public List<LineupEntry> GetEntries()
	{
		return overlay.GetEntries( Rotation );
	}

	/* Update */

	// Disables buttons in team-only or roster mode
	public void UpdateType( string type )
	{
		if ( IsActive )
		{
			if ( type is "team" or "roster" )
			{
				Update( true );
			}
		}
	}

	// Updates substitutions remaining for this bar
	public void UpdateSubs( int subs )
	{
		if ( IsActive )
		{
			// NA for unlimited subs
			if ( Shell.Settings.IsUnlimitedSubs )
			{
				subsBtn.Text = DXString.Get( "teambar.na" );
			}
			// Update
			else
			{
				subsBtn.Number = subs;
			}
		}
	}

	// Updates rotation number display for this bar
	public void UpdateRotation( int rotation )
	{
		if ( IsActive )
		{
			rotationBtn.Number = rotation;
		}
	}

	// Updates server display based on current court lineup
	public void UpdateServer( bool update = true )
	{
		if ( IsActive )
		{
			bool serveA = (StateMachine.ServeSide == RecordCourt.Side.SideA);
			bool serving = (serveA && IsSideA) || (!serveA && !IsSideA);

			// Might need to refresh first
			if ( update )
			{
				UpdateOverlay();
			}

			LineupEntry server = overlay.GetServer();

			serverBtn.Text = serving ? server?.Number : DXString.Get( "teambar.na" );
		}
	}

	// Updates libero display based on current court lineup
	public void UpdateLibero()
	{
		int count = overlay.GetLiberoCount();
		hasLibero = (count > 0);

		// Update button states
		liberoSwitch.IsDisabled = (overlay.IsLiberoIn || !overlay.HasTwoLiberos);
		liberoSwitch.LiberoCount = count;

		liberoBtn.IsDisabled = !hasLibero;
		swapBtn.IsDisabled = !hasLibero;

		string title, text;

		// At least 1 libero
		if ( hasLibero )
		{
			liberoSwitch.Active = StateMachine.Libero1;
			LineupEntry libero = overlay.GetLibero( StateMachine.Libero1 );

			// Default to libero 1
			if ( libero == null )
			{
				liberoSwitch.Active = 1;
				libero = overlay.GetLibero( 1 );
			}

			bool isLibero = libero.IsLibero;

			// Update jersey number
			title = isLibero ? "teambar.libero" : "teambar.player";
			text = libero.IsEmpty ? DXString.Get( "teambar.na" ) : libero.Number;
		}
		// Empty
		else
		{
			title = "teambar.libero";
			text = DXString.Get( "teambar.na" );
		}

		liberoBtn.Title = title;
		liberoBtn.Text = text;

		ResetSwapBtn();
	}

	// Updates subs remaining display
	private void UpdateSubs()
	{
		if ( IsActive )
		{
			subsBtn.Number = Subs;
		}
	}

	/* Overlay */

	// Refreshes team overlay with current lineup in rotation order
	public void UpdateOverlay()
	{
		overlay.SetEntries( barLineup.Entries, Rotation );

		// Also update action overlay
		StateMachine.Populate();
	}

	// Opens court/libero overlays for lineup editing
	public void ShowOverlay( TeamOverlay.LineupMode mode )
	{
		if ( !overlayOpen || overlay.IsPreview )
		{
			overlay.Mode = mode;

			bool preview = overlay.IsPreview;

			// Court still active in preview mode
			engine.IsCourtDisabled = !preview;
			StateMachine.IsUndoDisabled = !preview;

			// Disable all other input
			Update( !overlay.IsPreview );

			// Workaround for court 10ft line z-order issue
			StateMachine.UpdateLines( !preview );

			// Show overlays
			overlay.Show( engine.CourtBounds, engine.TeambarSize );

			// Initial blank lineup editing mode, show 'X' close button
			if ( overlay.IsBlank )
			{
				UpdateLineupBtn( true );
			}

			overlayOpen = true;
		}
	}

	// Closes court without persisting changes
	public void HideOverlay()
	{
		// Remove overlay
		if ( overlayOpen )
		{
			overlay.Hide();

			// Both court 10ft lines valid
			StateMachine.UpdateLines( true );

			// Re-enable input
			Update();

			engine.IsCourtDisabled = false;
			StateMachine.IsUndoDisabled = false;

			ResetButtons();

			overlayOpen = false;
		}
	}

	// Determines if specified point within open overlay
	public bool OverlayContains( Point pt )
	{
		return IsOpen && overlay.Contains( pt );
	}

	// Opens both team drawers (MOBILE ONLY)
	public async Task OpenTeams()
	{
		await engine.OpenTeams();
	}

	/* Persist */

	// Saves lineup change event
	private bool SaveReplace()
	{
		// Get current court selections in lineup order
		List<LineupEntry> entries = overlay.GetEntries( Rotation );

		// Only persist if valid
		if ( RecordLineup.Validate( entries ) )
		{
			// Only persist if there were changes
			if ( !barLineup.Equals( entries ) )
			{
				engine.StateMachine.ChangeLineup( IsSideA, barLineup.Entries, entries );

				barLineup.FromList( entries );
			}

			return true;
		}

		lineupBtn.Reset();

		return false;
	}

	// Saves a substitution or libero swap event
	private void SaveSubSwap( string selector, int zone, bool auto = false )
	{
		// Get lineup before and after subs
		List<LineupEntry> pre = barLineup.Entries;
		List<LineupEntry> post = overlay.GetEntries( Rotation );

		// Must convert from rotation to lineup order
		int index = Lineup.GetLineupIndex( (zone - 1), Rotation );

		LineupEntry entryOut = pre[ index ];
		LineupEntry entryIn = post[ index ];

		// Persist event
		engine.StateMachine.SubSwap( selector, IsSideA, entryOut, entryIn, pre, post, auto );

		barLineup.FromList( post );
	}

	/* UI */

	// Allows child court to be inserted at proper level in parent layout
	public void AddCourt()
	{
		overlay.AddCourt();
	}

	// Allows child libero area to be inserted at proper level in parent layout
	public void AddLibero()
	{
		overlay.AddLibero();
	}

	// Updates UI to correct swap in/out state for libero
	private void ResetSwapBtn()
	{
		swapBtn.Resource = GetSwapResource();
		swapBtn.Color = GetSwapColor();

		swapBtn.Redraw();
	}

	// Updates UI following sub mode overlay close
	private void ResetSubBtn()
	{
		subBtn.Resource = "teambar.sub";
		subBtn.Color = DXColors.Action;
	}

	// Resets state for lineup/swap/sub buttons
	private void ResetButtons()
	{
		UpdateLineupBtn( false );

		ResetSwapBtn();
		ResetSubBtn();
	}

	// Enables/disables teambar controls based on rally state
	public void Update( bool forceDisable = false )
	{
		if ( IsActive )
		{
			Settings settings = Shell.Settings;

			bool disabled = forceDisable || engine.StateMachine.IsRallyInProgress;
			bool low = settings.IsLowLevel;

			// Lineup always available except during sub/swap
			lineupBtn.IsDisabled = low || overlay.IsSwap || overlay.IsSub;

			bool swapDisabled = low || overlay.IsReplace || overlay.IsSub || !hasLibero;

			// Swap always available except during lineup/sub
			swapBtn.IsDisabled = swapDisabled;

			bool subDisabled = low || overlay.IsReplace || overlay.IsSwap || !barLineup.IsValid();

			// Sub always available except during lineup/swap
			subsBtn.IsDisabled = subDisabled || settings.IsUnlimitedSubs;
			subBtn.IsDisabled = subDisabled;

			// Rotation not available during rally
			rotationBtn.IsDisabled = disabled;

			// Server/libero only disabled when everything off
			serverBtn.IsDisabled = forceDisable;
			liberoBtn.IsDisabled = forceDisable || !hasLibero;

			// Libero 1/2
			liberoSwitch.IsDisabled = (forceDisable || low || overlay.IsLiberoIn || !overlay.HasTwoLiberos);
		}
	}

	// Updates lineup button open/close state
	private void UpdateLineupBtn( bool close )
	{
		lineupBtn.Resource = close ? "close" : "player";
		lineupBtn.Color = close ? DXColors.Negative : DXColors.Action;
	}

	/* Event Callbacks */

	/* Lineup */

	// Toggle court overlay
	private void OnLineupTapped()
	{
		// Close
		if ( overlayOpen && !overlay.IsPreview )
		{
			// Only close if valid
			if ( SaveReplace() )
			{
				HideOverlay();

				UpdateOverlay();
				UpdateServer();
				UpdateLibero();
				UpdateLineupBtn( false );

				// Optionally return to preview mode
				if ( ReturnToPreview )
				{
					ShowOverlay( TeamOverlay.LineupMode.Preview );
				}
			}
		}
		// Open, button becomes 'X'
		else
		{
			TeamOverlay.LineupMode mode = barLineup.IsValid() ? TeamOverlay.LineupMode.Replace : TeamOverlay.LineupMode.Blank;

			UpdateOverlay();
			ShowOverlay( mode );
			UpdateLineupBtn( true );
		}
	}

	// User dynamically creating new lineup
	private void OnLineupPressed()
	{
		if ( overlay.IsOpen && (overlay.IsBlank || overlay.IsReplace) )
		{
			List<LineupEntry> entries = GetEntries();

			// Prompt user, persist
			engine.SaveLineup( entries, OnLineupDone );
		}
		else
		{
			lineupBtn.Reset();
		}
	}

	// User completed lineup creation
	private void OnLineupDone()
	{
		lineupBtn.Reset();
	}

	/* Rotation */

	// User long pressed to edit rotation
	private void OnRotationPressed( object sender )
	{
		DXNumericEditor editor = new()
		{
			MinValue = 1,
			MaxValue = 6,
			ShouldWrap = true,

			Number = Rotation,

			Selected = OnRotationSelected,
			Cancelled = OnRotationCancelled
		};

		editor.Show( rotationBtn.View );
	}

	// Rotation changed
	private void OnRotationSelected( int rotation )
	{
		// Persist event
		engine.StateMachine.ChangeRotation( IsSideA, rotation );

		// Update UI
		overlay.SetEntries( barLineup.Entries, rotation );
		UpdateServer();

		rotationBtn.Reset();

		int zone = FindLibero();

		// Check for libero auto swap
		if ( LineupEntry.ZoneIsFrontRow( zone ) )
		{
			SwapOut( zone, true );
		}
	}

	// Rotation cancelled, no change
	private void OnRotationCancelled()
	{
		rotationBtn.Reset();
	}

	/* Libero */

	// User switched between libero 1/2
	private void OnLiberoSwitched( int number )
	{
		StateMachine.ChangeLibero( true, number );

		UpdateLibero();
	}

	// User swapping libero in/out
	private void OnSwapTapped( object sender )
	{
		// Swap In
		if ( overlay.IsLiberoOut )
		{
			// Cancel
			if ( overlayOpen && !overlay.IsPreview )
			{
				HideOverlay();
				UpdateLibero();

				// Optionally return to preview
				if ( ReturnToPreview )
				{
					ShowOverlay( TeamOverlay.LineupMode.Preview );
				}
			}
			// Open overlay
			else
			{
				swapBtn.Resource = "teambar.cancel";
				swapBtn.Color = DXColors.Negative;

				UpdateOverlay();
				ShowOverlay( TeamOverlay.LineupMode.Swap );
			}
		}
		// Swap Out
		else
		{
			SwapOut( FindLibero(), false );
		}
	}

	// User tapped destination zone for swap-in
	private void OnSwapped( int zone )
	{
		if ( overlay.IsLiberoOut )
		{
			overlay.Select( zone );

			// Briefly show selection
			DXTimer.Delay( 500, OnSwapComplete );
			void OnSwapComplete()
			{
				HideOverlay();

				// Swap on court
				overlay.SwapLibero( zone, StateMachine.Libero1 );

				// Persist event
				SaveSubSwap( Stats.SwapInKey, zone );

				// Update UI
				UpdateOverlay();
				UpdateLibero();
				UpdateServer();

				UpdateLayout();

				// Optionally return to preview mode
				if ( ReturnToPreview )
				{
					ShowOverlay( TeamOverlay.LineupMode.Preview );
				}
			}
		}
	}

	/* Subs Remaining */

	// User long pressed to edit subs remaining
	private void OnSubsPressed( object sender )
	{
		int max = Shell.Settings.SubsMax;

		if ( max > 0 )
		{
			DXNumericEditor editor = new()
			{
				MinValue = 0,
				MaxValue = max,
				ShouldWrap = false,

				Number = Subs,

				Selected = OnSubsSelected,
				Cancelled = OnSubsCancelled
			};

			editor.Show( subsBtn.View );
		}
	}

	// Subs remaining changed
	private void OnSubsSelected( int subs )
	{
		// Persist event
		engine.StateMachine.ChangeSubs( IsSideA, subs );

		// Update UI
		UpdateSubs();
		subsBtn.Reset();
	}

	// Subs remaining cancelled, no change
	private void OnSubsCancelled()
	{
		subsBtn.Reset();
	}

	/* Substitution */

	// Toggle sub overlay
	private void OnSubTapped( object sender )
	{
		// Close (button returns to 'Sub')
		if ( overlayOpen && !overlay.IsPreview )
		{
			SaveReplace();

			HideOverlay();
			ResetSubBtn();

			// Optional low sub warning
			if ( (Subs > 0) && (Subs == Shell.Settings.SubsWarn) )
			{
				string msg = (Subs == 1) ? "teambar.sub.warn1" : DXString.Get( "teambar.sub.warn", Subs );

				DXAlert.ShowOkRaw( DXString.Get( "teambar.sub.title" ), msg );
			}

			// Optionally return to preview mode
			if ( ReturnToPreview )
			{
				ShowOverlay( TeamOverlay.LineupMode.Preview );
			}
		}
		// Open (button changes to 'Done')
		else
		{
			subBtn.Resource = "teambar.done";
			subBtn.Color = DXColors.Negative;

			UpdateOverlay();
			ShowOverlay( TeamOverlay.LineupMode.Sub );
		}
	}

	// User substituted one player on court, persist event
	private void OnSubstituted( int zone )
	{
		SaveSubSwap( Stats.SubKey, zone );

		// Update UI
		UpdateSubs();
		UpdateServer();
	}

	/* Layout */

	// Close court on rotate (no persistence)
	public override void Rotate()
	{
		if ( IsActive )
		{
			HideOverlay();
			
			UpdateLineupBtn( false );
			ResetSubBtn();
		}
	}

	// Returns RallyFlow specific layout orientation
	public LayoutType GetLayoutType()
	{
		return engine.GetLayoutType();
	}

	// Redraws for RallyFlow specific orientation
	public override void UpdateLayout()
	{
		UpdateLayout( GetLayoutType() );
	}

	// Redraw for specified device/orientation
	public override void UpdateLayout( LayoutType type )
	{
		if ( IsActive )
		{
			DXLog.Debug( "Teambar:{0}", IsSideA );

			base.UpdateLayout( type );

			// Update children
			overlay.UpdateLayout( type );
			liberoSwitch.UpdateLayout( type );

			// Update button states
			HideOverlay();
			Update();
		}
	}

	// Landscape (4:3) [dynamic]
	protected override void Landscape()
	{
		bool sync = IsSyncMode;
		bool ios = DXDevice.IsIOS;

		Thickness safeArea = DXDevice.SafeArea();

		double wd = LayoutWidth;
		double pad = (wd * 0.005);

		Padding = new Thickness( 0, pad, 0, (pad + safeArea.Bottom) );

		// Layout constants
		double btnY = (wd * 0.005);
		double btnWd = (wd * 0.086);

		double spaceWd = (wd * 0.021);
		double numSize = (wd * 0.051);

		const double dividerY = 0;
		const double dividerWd = DrawerDivider.Thickness;
		double dividerHt = LayoutHeight;

		double teamBtnY = btnY + (wd * (sync ? 0.037 : 0.034));
		double colWd = (btnWd + spaceWd - (wd * 0.008));

		double libWd = (btnWd * 0.5);
		double libHt = (btnWd * 1.35);

		double swapWd = (wd * 0.176);
		double subWd = (wd * 0.115);

		double titleFont = (wd * 0.025);
		double buttonFont = (wd * 0.030);

		// Left-to-Right
		if ( IsSideA )
		{
			double x1 = spaceWd;

			// Lineup
			lineupBtn.TitleSize = titleFont;
			lineupBtn.Layout( x1, btnY, btnWd );

			double x2 = (x1 + colWd);

			// Divider
			dividers[0].Layout( x2, dividerY, dividerWd, dividerHt );

			double x3 = (x2 + dividerWd + spaceWd);

			// Rotation
			rotationBtn.AdjustX = ios ? -3 : -4;
			rotationBtn.TitleSize = titleFont;
			rotationBtn.NumberSize = numSize;

			rotationBtn.Layout( x3, btnY, btnWd );

			double x4 = (x3 + colWd);

			// Server
			serverBtn.TitleSize = titleFont;
			serverBtn.NumberSize = numSize;
			serverBtn.Layout( x4, btnY, btnWd );

			double x5 = (x4 + colWd);

			// Divider
			dividers[1].Layout( x5, dividerY, dividerWd, dividerHt );

			double x6 = (x5 + dividerWd + spaceWd);
			
			// Liberos 1/2
			liberoSwitch.SetSwitchSize( libWd, libHt );
			liberoSwitch.NumberSize = sync ? 15 : 20;

			SetBounds( liberoSwitch, x6, (btnY + 3), libWd, libHt );

			double x7 = (x6 + libWd + spaceWd);

			// Libero
			liberoBtn.TitleSize = titleFont;
			liberoBtn.NumberSize = numSize;
			liberoBtn.Layout( x7, btnY, btnWd );

			double x8 = (x7 + colWd);

			// Swap
			swapBtn.FontSize = buttonFont;
			swapBtn.Layout( x8, teamBtnY, swapWd, btnWd );

			double x9 = (x8 + swapWd + spaceWd);

			// Divider
			dividers[2].Layout( x9, dividerY, dividerWd, dividerHt );

			double x10 = (x9 + dividerWd + spaceWd);

			// Subs
			subsBtn.TitleSize = titleFont;
			subsBtn.NumberSize = numSize;
			subsBtn.Layout( x10, btnY, btnWd );

			double x11 = (x10 + colWd);

			// Sub
			subBtn.FontSize = buttonFont;
			subBtn.Layout( x11, teamBtnY, subWd, btnWd );

			double x12 = (x11 + subWd + spaceWd - dividerWd);

			// Divider
			dividers[3].Layout( x12, dividerY, dividerWd, dividerHt );
		}
		// Right-to-Left
		else
		{
			const double x1 = 0;

			dividers[0].Layout( x1, dividerY, dividerWd, dividerHt );

			double x2 = (x1 + dividerWd + spaceWd);

			// Sub
			subsBtn.TitleSize = titleFont;
			subBtn.FontSize = buttonFont;

			subBtn.Layout( x2, teamBtnY, subWd, btnWd );

			double x3 = (x2 + subWd + spaceWd);

			// Subs
			subsBtn.NumberSize = numSize;
			subsBtn.Layout( x3, btnY, btnWd );

			double x4 = (x3 + colWd);

			// Divider
			dividers[1].Layout( x4, dividerY, dividerWd, dividerHt );

			double x5 = (x4 + dividerWd + spaceWd);

			// Swap
			swapBtn.FontSize = buttonFont;
			swapBtn.Layout( x5, teamBtnY, swapWd, btnWd );

			double x6 = (x5 + swapWd + spaceWd);

			// Libero
			liberoBtn.TitleSize = titleFont;
			liberoBtn.NumberSize = numSize;
			liberoBtn.Layout( x6, btnY, btnWd );

			double x7 = (x6 + colWd + (spaceWd / 2.0));

			// Liberos 1/2
			liberoSwitch.SetSwitchSize( libWd, libHt );
			liberoSwitch.NumberSize = sync ? 15 : 20;

			SetBounds( liberoSwitch, x7, (btnY + 3), libWd, libHt );

			double x8 = (x7 + libWd + spaceWd);

			// Divider
			dividers[2].Layout( x8, dividerY, dividerWd, dividerHt );

			double x9 = (x8 + dividerWd + spaceWd);

			// Server
			serverBtn.TitleSize = titleFont;
			serverBtn.NumberSize = numSize;
			serverBtn.Layout( x9, btnY, btnWd );

			double x10 = (x9 + colWd);

			// Rotation
			rotationBtn.AdjustX = ios ? -3 : -4;
			rotationBtn.TitleSize = titleFont;
			rotationBtn.NumberSize = numSize;

			rotationBtn.Layout( x10, btnY, btnWd );

			double x11 = (x10 + colWd);

			// Divider
			dividers[3].Layout( x11, dividerY, dividerWd, dividerHt );

			double x12 = (x11 + dividerWd + spaceWd);

			// Lineup
			lineupBtn.TitleSize = titleFont;
			lineupBtn.Layout( x12, btnY, btnWd );
		}

		ShowDividers( 4 );
	}

	// Widescreen Landscape (16:10)
	protected override void WideLandscape()
	{
		Padding = 0;

		double wd = LayoutWidth;
		double ht = LayoutHeight;

		// Layout constants
		double btnSize = (ht / 10.5);
		double btnX = ((wd - btnSize) / 2) + 3;

		const double teamBtnX = 8;
		double teamBtnWd = (wd - (teamBtnX * 2));

		double spaceHt = (ht / 60) - 1;
		double numSize = (btnSize * 0.50);

		const double dividerX = 0;
		double dividerWd = wd;
		const double dividerHt = DrawerDivider.Thickness;

		double rowHt = (btnSize + lineupBtn.ChildY + spaceHt);

		double libWd = teamBtnWd;
		double libHt = (btnSize * 0.68);

		// Always top-to-bottom
		const double y1 = 0;

		// Divider
		dividers[0].Layout( dividerX, y1, dividerWd, dividerHt );

		double y2 = (y1 + dividerHt + spaceHt);

		// Lineup
		lineupBtn.Layout( btnX, y2, btnSize );

		double y3 = (y2 + rowHt);

		// Divider
		dividers[1].Layout( dividerX, y3, dividerWd, dividerHt );

		double y4 = (y3 + dividerHt + spaceHt);

		// Rotation
		rotationBtn.AdjustX = -3;
		rotationBtn.NumberSize = numSize;
		rotationBtn.Layout( btnX, y4, btnSize );

		double y5 = (y4 + rowHt);

		// Server
		serverBtn.NumberSize = numSize;
		serverBtn.Layout( btnX, y5, btnSize );

		double y6 = (y5 + rowHt);

		// Divider
		dividers[2].Layout( dividerX, y6, dividerWd, dividerHt );

		double y7 = (y6 + dividerHt + spaceHt);

		// Liberos 1/2
		liberoSwitch.SetSwitchSize( libWd, libHt );
		liberoSwitch.NumberSize = 20;
		
		SetBounds( liberoSwitch, teamBtnX, (y7 + 2), libWd, libHt );

		double y8 = (y7 + libHt + spaceHt);

		// Libero
		liberoBtn.NumberSize = numSize;
		liberoBtn.Layout( btnX, y8, btnSize );

		double y9 = (y8 + rowHt);

		// Swap
		swapBtn.Resource = GetSwapResource();
		swapBtn.Layout( teamBtnX, y9, teamBtnWd, btnSize );

		double y10 = (y9 + btnSize + spaceHt);

		// Divider
		dividers[3].Layout( dividerX, y10, dividerWd, dividerHt );

		double y11 = (y10 + dividerHt + spaceHt);

		// Subs
		subsBtn.NumberSize = numSize;
		subsBtn.Layout( btnX, y11, btnSize );

		double y12 = (y11 + rowHt);

		// Sub
		subBtn.Layout( teamBtnX, y12, teamBtnWd, btnSize );

		double div4Y = (ht - dividerHt);

		// Divider
		dividers[4].Layout( dividerX, div4Y, dividerWd, dividerHt );

		ShowDividers( 5 );
	}

	// Portrait (3:4) [dynamic]
	protected override void Portrait()
	{
		bool ios = DXDevice.IsIOS;

		Padding = 0;

		// Layout constants
		double wd = LayoutWidth;
		double ht = (DXDevice.GetScreenHt() - DXDevice.GetSafeTop());

		double btnWd = (ht * 0.038);
		double btnHt = (ht * 0.056);

		double spaceWd = (ht * 0.009);
		double spaceHt = (ht * 0.007);

		double dividerWd = wd;
		const double dividerHt = 1;

		double colWd = (btnWd + spaceWd);
		double rowHt = (btnHt + spaceHt - 2);

		double x1 = (ht * 0.012);
		double x2 = (x1 + colWd);
		double x3 = (x2 + colWd);

		double libWd = btnWd;
		double libHt = btnWd;

		double swapWd = (ht * 0.089);
		double subWd = swapWd;

		double numSize = (ht * 0.025);
		double libSize = (ht * 0.016);
		double btnSize = (ht * 0.015);
		double titleSize = (ht * 0.012);

		// Shared

		// Lineup
		lineupBtn.TitleSize = titleSize;
		lineupBtn.AdjustX = ios ? 0 : -3;

		double teamBtnY = lineupBtn.ChildY;

		// Rotation
		rotationBtn.TitleSize = titleSize;
		rotationBtn.NumberSize = numSize;
		rotationBtn.AdjustX = ios ? -5 : -6;

		// Server
		serverBtn.TitleSize = titleSize;
		serverBtn.NumberSize = numSize;

		// Liberos 1/2
		liberoSwitch.SetSwitchSize( libWd, libHt );
		liberoSwitch.NumberSize = libSize;

		// Libero
		liberoBtn.TitleSize = titleSize;
		liberoBtn.NumberSize = numSize;

		// Swap
		swapBtn.FontSize = btnSize;

		// Subs
		subsBtn.TitleSize = titleSize;
		subsBtn.NumberSize = numSize;

		// Sub
		subBtn.FontSize = btnSize;

		// Top-to-Bottom
		if ( IsSideA )
		{
			double y1 = spaceHt;

			// Lineup
			lineupBtn.Layout( x1, y1, btnWd );

			// Rotation
			rotationBtn.Layout( x2, y1, btnWd );

			// Server
			serverBtn.Layout( x3, y1, btnWd );

			double y2 = (y1 + rowHt);

			// Divider
			dividers[0].Layout( 0, y2, dividerWd, dividerHt );

			double y3 = (y2 + dividerHt + spaceHt);

			// Liberos 1/2
			SetBounds( liberoSwitch, x1, (y3 + titleSize + 4), libWd, libHt );

			// Libero
			liberoBtn.Layout( x2, y3, btnWd );

			// Swap
			swapBtn.Layout( x3, (y3 + teamBtnY), swapWd, btnWd );

			double y4 = (y3 + rowHt);

			// Divider
			dividers[1].Layout( 0, y4, dividerWd, dividerHt );

			double y5 = (y4 + dividerHt + spaceHt);

			// Subs
			subsBtn.Layout( x1, y5, btnWd );

			// Sub
			subBtn.Layout( x2, (y5 + teamBtnY), subWd, btnWd );

			// Not all dividers used
			ShowDividers( 2 );
		}
		// Bottom-to-top
		else
		{
			double y1 = 0;

			// Subs
			subsBtn.Layout( x1, y1, btnWd );

			// Sub
			subBtn.Layout( x2, (y1 + teamBtnY), subWd, btnWd );

			double y2 = (y1 + rowHt);

			// Divider
			dividers[0].Layout( 0, y2, dividerWd, dividerHt );

			double y3 = (y2 + dividerHt + spaceHt);

			// Liberos 1/2
			SetBounds( liberoSwitch, x1, (y3 + titleSize + 4), libWd, libHt );

			// Libero
			liberoBtn.Layout( x2, y3, btnWd );

			// Swap
			swapBtn.Layout( x3, (y3 + teamBtnY), swapWd, btnWd );

			double y4 = (y3 + rowHt);

			// Divider
			dividers[1].Layout( 0, y4, dividerWd, dividerHt );

			double y5 = (y4 + dividerHt + spaceHt);

			// Lineup
			lineupBtn.Layout( x1, y5, btnWd );

			// Rotation
			rotationBtn.Layout( x2, y5, btnWd );

			// Server
			serverBtn.Layout( x3, y5, btnWd );

			// Not all dividers used
			ShowDividers( 2 );
		}
	}

	// Widescreen Portrait (10:16)
	protected override void WidePortrait()
	{
		Padding = 3;

		double padY = Padding.VerticalThickness;

		double wd = DXDevice.GetScreenWd();
		double ht = LayoutHeight;

		// Layout constants
		double teamBtnY = (lineupBtn.ChildY + 1);

		const double btnY = 1;
		double btnSize = Math.Min( (wd / 13.5), (ht - teamBtnY - padY) );

		double spaceWd = (wd / 40);
		double numSize = (btnSize * 0.55);

		double dividerY = -Padding.Top;
		const double dividerWd = DrawerDivider.Thickness;
		double dividerHt = ht;

		double colWd = (btnSize + spaceWd - 5);

		double libWd = btnSize;
		double libHt = (ht - padY);

		double swapWd = (btnSize * 2.5);
		double subWd = (btnSize * 1.5);

		// Always left-to-right
		double x1 = spaceWd;

		// Lineup
		lineupBtn.Layout( x1, btnY, btnSize );

		double x2 = (x1 + colWd);

		// Divider
		dividers[0].Layout( x2, dividerY, dividerWd, dividerHt );

		double x3 = (x2 + dividerWd + spaceWd);

		// Rotation
		rotationBtn.AdjustX = -3;
		rotationBtn.NumberSize = numSize;
		rotationBtn.Layout( x3, btnY, btnSize );

		double x4 = (x3 + colWd);

		// Server
		serverBtn.NumberSize = numSize;
		serverBtn.Layout( x4, btnY, btnSize );

		double x5 = (x4 + colWd);

		// Divider
		dividers[1].Layout( x5, dividerY, dividerWd, dividerHt );

		double x6 = (x5 + dividerWd + spaceWd);

		// Liberos 1/2
		liberoSwitch.NumberSize = 20;
		SetBounds( liberoSwitch, x6, btnY, libWd, libHt );

		double x7 = (x6 + libWd + spaceWd);

		// Libero
		liberoBtn.NumberSize = numSize;
		liberoBtn.Layout( x7, btnY, btnSize );

		double x8 = (x7 + colWd);

		// Swap
		swapBtn.Resource = GetSwapResource();
		swapBtn.Layout( x8, teamBtnY, swapWd, btnSize );

		double x9 = (x8 + swapWd + spaceWd);

		// Divider
		dividers[2].Layout( x9, dividerY, dividerWd, dividerHt );

		double x10 = (x9 + dividerWd + spaceWd);

		// Subs
		subsBtn.NumberSize = numSize;
		subsBtn.Layout( x10, btnY, btnSize );

		double x11 = (x10 + colWd);

		// Sub
		subBtn.Layout( x11, teamBtnY, subWd, btnSize );

		// Do NOT need last divider
		ShowDividers( 3 );
	}

	// Mobile Landscape
	protected override void MobileLandscape()
	{
		bool ios = DXDevice.IsIOS;

		// Padding
		Padding = 0;

		double safeTop = ios ? 0 : DXDevice.GetSafeTop();

		double wd = LayoutWidth;
		double ht = (DXDevice.GetScreenHt() - DXDevice.SafeArea().Bottom - safeTop);

		// Layout constants
		double btnSize = (ht / 10.5);
		double btnX = ((wd - btnSize) / 2) + 1;

		const double teamBtnX = 6;
		double teamBtnWd = (wd - (teamBtnX * 2));

		double spaceHt = (ht * 0.009);
		double numSize = (ios ? 22 : 26);

		const double dividerX = 0;
		double dividerWd = wd;
		const double dividerHt = DrawerDivider.Thickness;

		double rowHt = (btnSize + lineupBtn.ChildY + spaceHt);

		double libWd = teamBtnWd;
		double libHt = (btnSize * 0.63);

		// Always top-to-bottom
		double y1 = 5;

		// Lineup
		lineupBtn.AdjustX = -1;
		lineupBtn.Layout( btnX, y1, btnSize );

		double y2 = (y1 + rowHt);

		// Divider
		dividers[0].Layout( dividerX, y2, dividerWd, dividerHt );

		double y3 = (y2 + dividerHt + spaceHt);

		// Rotation
		rotationBtn.AdjustX = -5;
		rotationBtn.NumberSize = numSize;
		rotationBtn.Layout( btnX, y3, btnSize );

		double y4 = (y3 + rowHt);

		// Server
		serverBtn.NumberSize = numSize;
		serverBtn.Layout( btnX, y4, btnSize );

		double y5 = (y4 + rowHt);

		// Divider
		dividers[1].Layout( dividerX, y5, dividerWd, dividerHt );

		double y6 = (y5 + dividerHt + spaceHt);

		// Liberos 1/2
		liberoSwitch.SetSwitchSize( libWd, libHt );
		liberoSwitch.NumberSize = (numSize * 0.6);
		
		SetBounds( liberoSwitch, teamBtnX, y6, libWd, libHt );

		double y7 = (y6 + libHt + spaceHt);

		// Libero
		liberoBtn.NumberSize = numSize;
		liberoBtn.Layout( btnX, y7, btnSize );

		double y8 = (y7 + rowHt);

		// Swap
		swapBtn.Resource = GetSwapResource();
		swapBtn.Layout( teamBtnX, y8, teamBtnWd, btnSize );

		double y9 = (y8 + btnSize + spaceHt);

		// Divider
		dividers[2].Layout( dividerX, y9, dividerWd, dividerHt );

		double y10 = (y9 + dividerHt + spaceHt);

		// Subs
		subsBtn.NumberSize = numSize;
		subsBtn.Layout( btnX, y10, btnSize );

		double y11 = (y10 + rowHt);

		// Sub
		subBtn.Layout( teamBtnX, y11, teamBtnWd, btnSize );

		// Do NOT need last divider
		ShowDividers( 3 );
	}

	// Mobile Portrait
	protected override void MobilePortrait()
	{
		bool ios = DXDevice.IsIOS;
		
		// Padding
		Thickness safeArea = DXDevice.SafeAreaLRB();
		Thickness basePadding = new( 3, 8, 3, 3 );

		Padding = DXUtils.AddPadding( basePadding, safeArea );

		double wd = DXDevice.GetScreenWd();
		double ht = LayoutHeight;

		// Layout constants
		const double btnY = 0;
		double btnSize = (wd * (ios ? 0.10 : 0.09));

		double spaceWd = (wd * (ios ? 0.020 : 0.022));
		double numSize = (ios ? 22 : 26);

		double dividerY = -Padding.Top;
		const double dividerWd = DrawerDivider.Thickness;
		double dividerHt = ht;

		double colWd = (btnSize + spaceWd - 5);

		double libY = (btnY + serverBtn.ChildY);
		double libWd = (btnSize * 0.6);
		double libHt = btnSize;

		double swapWd = (btnSize * 1.50);
		double subWd = btnSize;

		// Always left-to-right
		double x1 = spaceWd;

		// Lineup
		lineupBtn.Layout( x1, btnY, btnSize );

		double x2 = (x1 + colWd);

		// Divider
		dividers[0].Layout( x2, dividerY, dividerWd, dividerHt );

		double x3 = (x2 + dividerWd + spaceWd);

		// Rotation
		rotationBtn.AdjustX = -4;
		rotationBtn.NumberSize = numSize;
		rotationBtn.Layout( x3, btnY, btnSize );

		double x4 = (x3 + colWd);

		// Server
		serverBtn.NumberSize = numSize;
		serverBtn.Layout( x4, btnY, btnSize );

		double x5 = (x4 + colWd);

		// Divider
		dividers[1].Layout( x5, dividerY, dividerWd, dividerHt );

		double x6 = (x5 + dividerWd + spaceWd);

		// Liberos 1/2
		liberoSwitch.NumberSize = (numSize * 0.6);
		liberoSwitch.SetSwitchSize( libWd, libHt );
		
		SetBounds( liberoSwitch, x6, libY, libWd, libHt );

		double x7 = (x6 + libWd + spaceWd);

		// Libero
		liberoBtn.NumberSize = numSize;
		liberoBtn.Layout( x7, btnY, btnSize );

		double x8 = (x7 + colWd);
		double swapY = (btnY + subsBtn.ChildY);

		// Swap
		swapBtn.Resource = GetSwapResource();
		swapBtn.Layout( x8, swapY, swapWd, btnSize );

		double x9 = (x8 + swapWd + spaceWd);

		// Divider
		dividers[2].Layout( x9, dividerY, dividerWd, dividerHt );

		double x10 = (x9 + dividerWd + spaceWd);

		// Subs
		subsBtn.NumberSize = numSize;
		subsBtn.Layout( x10, btnY, btnSize );

		double x11 = (x10 + colWd);
		double subsY = (btnY + subsBtn.ChildY);

		// Sub
		subBtn.Layout( x11, subsY, subWd, btnSize );

		// Do NOT need last divider
		ShowDividers( 3 );
	}

	// Makes specified number dividers visible, rest are hidden
	private void ShowDividers( int count )
	{
		for ( int i = 0; i < dividers.Count; i++ )
		{
			dividers[i].IsVisible = (i < count);
		}
	}
}

//
