/* 
 * 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.Gestures;
using DXLib.UI.Container;

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

using DXLib.Data;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Draws the player info area of a PlayerCell. The area contains the player avatar, first/last name, jersey number,
 * position, and sub button.
 */
public class PlayerArea : DXGridGestures
{
	/* Constants */
	private const double PopupWd = 250;
	private const double PopupHt = 345;

	/* Events */
	public Action AreaTapped { get; set; }

	/* Properties */
	public Player Player { get; private set; }
	public string Position { get; private set; }
	public bool IsAltNumber { get; private set; }

	public bool IsEmpty => Player == null;
	public bool IsLibero => Position is Lineup.LiberoKey;
	public bool IsSetter => Position is Lineup.SetterKey;

	private bool HasAltNumber => Player is { HasAltNumber: true };

	// Used for mobile AutoFocus
	public bool IsAddDisabled { set => addBtn?.IsDisabled = value; }

	/* Fields */
	private DXLabel zoneLbl;
	private DXImageArea avatarImg;

	private DXLabel firstLbl;
	private DXLabel lastLbl;

	private DXNumberButton numberBtn;

	private DXButton positionBtn;
	private DXLabel positionLbl;

	private DXButton subBtn;
	private DXIconButton addBtn;

	// Mobile only
	private DXPopup popup;

	// Court zone (1-6)
	private readonly int zone;

	// State control
	private bool inProgress;

	// External ref
	private Legacy engine;

	/* Methods */
	public PlayerArea( int zone )
	{
		this.zone = zone;

		BackgroundColor = DXColors.Accent1;

		// Register for events
		Tapped += OnTapped;
		Pressed = OnPressed;
	}

	// Post construction initialization
	public void Init( Legacy legacy, ButtonArea buttonArea )
	{
		engine = legacy;

		bool ios = DXDevice.IsIOS;
		
		Padding = 0;
		RowSpacing = 0;
		ColumnSpacing = 0;

		// Zone
		zoneLbl = new DXLabel
		{
			Text = zone.ToString(),
			TextColor = DXColors.Light4,
			Font = DXFonts.Oswald
		};

		// Avatar
		avatarImg = new DXImageArea
		{
			DefaultIcon = "player",
			IconColor = DXColors.Light4
		};

		// First
		firstLbl = new DXLabel
		{
			TextColor = DXColors.Dark1,
			Font = DXFonts.RobotoBold,
			LineBreakMode = LineBreakMode.TailTruncation
		};

		// Last
		lastLbl = new DXLabel
		{
			TextColor = DXColors.Dark1,
			Font = DXFonts.RobotoBold,
			LineBreakMode = LineBreakMode.TailTruncation
		};

		// Number
		numberBtn = new DXNumberButton
		{
			NumberColor = DXColors.Light4,
			DisabledOpacity = 1.0,

			IsCircle = true,
			IsSticky = true,
			
			TextAdjustY = ios ? 0 : -3,
			
			ButtonTapped = OnNumberTapped
		};

		numberBtn.Init();
		
		// Position (non-libero)
		positionBtn = new DXButton
		{
			ButtonColor = DXColors.Positive,
			DisabledOpacity = 1.0,
			
			IsSticky = true,
			ButtonTapped = OnPositionTapped
		};

		positionBtn.Init();
		
		// Position (libero)
		positionLbl = new DXLabel
		{
			TextColor = DXColors.Dark2,
			Font = DXFonts.RobotoBold,
			LineBreakMode = LineBreakMode.NoWrap
		};

		// Sub
		subBtn = new DXButton
		{
			Resource = "teambar.sub",
			Type = DXButton.ButtonType.Action,

			IsSticky = true,
			ButtonTapped = OnSubTapped
		};

		subBtn.Init();
		
		// + (mobile only)
		if ( DXDevice.IsMobile )
		{
			addBtn = new DXIconButton
			{
				Resource = "add",
				Color = DXColors.Positive,

				IconScale = 0.80f,
				IconColor = DXColors.Light4,

				HasShadow = ios,
				IsSticky = true,
				
				ButtonTapped = OnAddTapped
			};
			
			addBtn.Init();
		}

		// Mobile only
		if ( DXDevice.IsMobile )
		{
			popup = new DXPopup( buttonArea )
			{
				ViewWidth = PopupWd,
				ViewHeight = PopupHt,

				PopupClosed = OnPopupClosed
			};
		}

		// Not initially editable
		UpdateEditMode();
	}

	// Disables buttons while rally in-progress
	public void StartRally( bool start )
	{
		inProgress = start;

		subBtn.IsVisible = !start && !IsEmpty;

		UpdateMobile();
	}

	/* Lineup */

	// Returns lineup entry populated with info from this area
	public LineupEntry GetEntry()
	{
		return IsEmpty? null : new LineupEntry
		{
			Zone = zone,

            Position = Position,
            IsAltNumber = IsAltNumber,

            Player = Player,
			PlayerId = Player.UniqueId
		};
	}

	// Populates area from lineup entry
	public void SetEntry( LineupEntry entry )
	{
		Populate( entry?.Player, entry?.Position, entry is { IsAltNumber: true } );
	}

	// Forces fields to be updated with info from specified player
	private void UpdateEntry( Player player )
	{
		Player = player;

		// Position must be set externally
		UpdateEntry();
	}

	// Forces fields to be updated via parent engine
	private void UpdateEntry()
	{
		LineupEntry entry = new()
		{
			Player = Player,
			PlayerId = Player?.UniqueId,

			Position = Position,
			IsAltNumber = IsAltNumber
		};

		engine.Court.SetEntry( zone, entry );
	}

	// Populates fields with specified player info
	private void Populate( Player player, string position, bool altNumber )
	{
		Player = player;
		Position = position;
		IsAltNumber = altNumber;

		bool libero = position is Lineup.LiberoKey;

		// Darker color for libero
		BackgroundColor = libero ? DXColors.Dark4 : DXColors.Light1;

		Color color = (player == null) ? Organization.DefaultColor : player.Season.Color;

		// Avatar
		avatarImg.IsVisible = !IsEmpty;
		avatarImg.Color = color;
		avatarImg.SetImage( player?.ImageUrl );

		// Name
		firstLbl.Text = player?.FirstName!;
		lastLbl.Text = player?.LastName!;

		// Number
		numberBtn.IsVisible = !IsEmpty;
		numberBtn.IsDisabled = engine.IsEditOff || !HasAltNumber;
		numberBtn.Text = player?.GetNumber( altNumber );

		if ( player != null )
		{
			numberBtn.Color = player.Season.Color;
		}

		// Position
		if ( libero )
		{
			positionBtn.IsVisible = false;
			positionLbl.IsVisible = !IsEmpty;

			subBtn.IsVisible = false;
		}
		else
		{
			positionBtn.IsVisible = !IsEmpty;
			positionLbl.IsVisible = false;

			positionBtn.Text = position?.ToUpper();
			positionBtn.IsDisabled = engine.IsEditOff;

			subBtn.IsVisible = engine.IsEditSub && !IsEmpty && !inProgress;
		}

		// Mobile
		if ( DXDevice.IsMobile )
		{
			UpdatePopup();
			UpdateLayout();
		}
	}

	/* Sub/Swap/Replace */

	// Displays menu for selecting substitution player
	private void ShowSubMenu()
	{
		string prefix = DXString.GetUpper( "lineup.rot" );

		LineupMenu menu = engine.GetSubMenu( zone );

		// Config popup
		menu.Header = $"{prefix} {zone}";
		menu.PlayerSelected = OnSubSelected;
		menu.Cancelled = OnSubCancelled;

		menu.ShowFromView( subBtn, 20, 90 );
	}

	// Displays menu for selecting replacement player
	public void ShowLineupMenu()
	{
		LineupMenu menu = engine.GetLineupMenu();

		string prefix = DXString.GetUpper( "lineup.rot" );
		int zoneAdj = IsLibero ? (zone - Lineup.BaseEntries) : zone;

		// Update popup menu
		menu.Header = $"{prefix} {zoneAdj}";
		menu.HasClear = !IsEmpty;

		menu.SetReduced( engine.GetPlayers() );
		menu.SetDisabled( [ Player ] );

		menu.PlayerSelected = OnLineupSelected;

		menu.ShowFromView( this, 0, -(Height / 2) );
	}

	/* UI Control */

	// Turns lineup editing mode on/off
	public void UpdateEditMode()
	{
		numberBtn.IsDisabled = (engine.IsEditOff || !HasAltNumber);
		positionBtn.IsDisabled = engine.IsEditOff;

		subBtn.IsVisible = engine.IsEditSub && !IsEmpty;

		UpdateMobile();
	}

	/* Mobile */

	// Updates button states specific to mobile
	private void UpdateMobile()
	{
		if ( DXDevice.IsMobile )
		{
			subBtn.IsVisible = engine.IsEditSub && !IsEmpty;
			addBtn.IsVisible = engine.IsEditOff && !IsEmpty;
		}
	}

	// Sets button popup title header
	private void UpdatePopup()
	{
		if ( (popup != null) && (Player != null) )
		{
			string number = Player.GetNumber( IsAltNumber );

			popup.Title = $"{Player.FullName}  #{number}";
		}
	}

	// Closes button popup
	public void ClosePopup()
	{
		popup.Hide();
	}

	// Resets '+' button
	public void Reset()
	{
		addBtn.Reset();
	}

	// User replacing player in edit mode
	private void HandleReplace( Point point )
	{
		// Exclude internal buttons
		if ( !subBtn.Contains( point ) && !numberBtn.Contains( point ) && !positionBtn.Contains( point ) )
		{
			AreaTapped?.Invoke();
		}
	}

	/* Event Callbacks */

	// LINEUP

	// User tapped anywhere in player area
	private void OnTapped( object sender, MR.Gestures.TapEventArgs args )
	{
		// Single tap only during blank edit mode
		if ( engine.IsEditLineup )
		{
			HandleReplace( args.Touches[0] );
		}
	}

	// User long pressed anywhere in player area
	private void OnPressed( Point point )
	{
		// Long press always works when editing
		if ( engine.IsEditOn )
		{
			HandleReplace( point );
		}
	}

	// User selected replacement player (persisted later)
	private void OnLineupSelected( Player player, bool cancel )
	{
		// Use roster position if only 1, otherwise '?'
		Position = Lineup.GetNewPosition( player, Position );

		UpdateEntry( player );
	}

	// SUB

	// Handles substitution request, validates subs, shows sub menu
	private void OnSubTapped( object sender )
	{
		LineupMenu menu = engine.GetSubMenu( zone );

		// Must have valid players to sub
		if ( menu == null )
		{
			DXAlert.ShowError( "teambar.sub.title", "teambar.sub.roster" );
		}
		// Should have subs remaining (user can override)
		else if ( (engine.Subs < 1) && !Shell.Settings.IsUnlimitedSubs )
		{
			DXAlert.ShowOkCancel( "teambar.sub.title", "teambar.sub.none", OnSubConfirmed, OnSubCancelled );
		}
		// Normal sub
		else
		{
			ShowSubMenu();
		}
	}

	// User confirmed sub warning
	private void OnSubConfirmed()
	{
		ShowSubMenu();
	}

	// User selected player to sub-in
	private void OnSubSelected( Player player, bool cancel )
	{
		if ( !cancel )
		{
			// Use roster position if only 1, otherwise inherit existing
			Position = Lineup.GetNewPosition( player, Position );

			// Create new entry
			LineupEntry entry = new( player )
			{
				Zone = zone,
				Position = Position
			};

			// Persist
			engine.HandleSub( entry );

			// Update UI
			UpdateEntry( player );

			positionBtn.IsDisabled = false;
			subBtn.Reset();

			int subs = engine.Subs;

			// 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 );
			}
		}
	}

	// User cancelled sub warning
	private void OnSubCancelled()
	{
		subBtn.Reset();
	}

	// NUMBER

	// User tapped jersey number
	private void OnNumberTapped( object sender )
	{
		// Only valid if multiple numbers
		if ( HasAltNumber )
		{
			string num = Player.Number;
			string alt = Player.AltNumber;

			// Build number list
			List<DXItem> list =
			[
				new( LineupEntry.NumberKey, num ),
				new( LineupEntry.AltNumberKey, alt )
			];

			// Show menu (highlight current value)
			DXMenu numMenu = new()
			{
				Title = "lineup.number",
				MenuWd = 100,

				ItemSelected = OnNumberSelected,
				Cancelled = OnNumberCancelled
			};

            string selected = Player.GetNumber( IsAltNumber );

            numMenu.SetItems( list, selected );
			numMenu.ShowFromView( numberBtn );
		}
	}

	// User selected number from popup menu
	private void OnNumberSelected( DXItem item )
	{
		// User may have cancelled
		if ( item != null )
		{
            IsAltNumber = (item.Key == LineupEntry.AltNumberKey);

            UpdateEntry();

			// Change after sub must be persisted separately
			if ( engine.IsEditSub )
			{
				engine.SaveReplace();
			}

			numberBtn.Text = item.Value;
		}

		numberBtn.Reset();
	}

	// User cancelled number selection
	private void OnNumberCancelled()
	{
		numberBtn.Reset();
	}

	// POSITION

	// User tapped position
	private void OnPositionTapped( DXButton button )
	{
		// Show menu (highlight current value)
		DXMenu posMenu = new()
		{
			Title = "lineup.position",
			MenuWd = 225,

			ItemSelected = OnPositionSelected,
			Cancelled = OnPositionCancelled
		};

		posMenu.SetItems( Lineup.GetPositions(), Position );
		posMenu.ShowFromView( positionBtn );
	}

	// User selected position from popup menu
	private void OnPositionSelected( DXItem item )
	{
		// User may have cancelled
		if ( item != null )
		{
			Position = item.Key;

			UpdateEntry();

			// Change after sub must be persisted separately
			if ( engine.IsEditSub )
			{
				engine.SaveReplace();
			}

			positionBtn.Text = item.Key.ToUpper();
		}

		positionBtn.Reset();
	}

	// User cancelled position selection
	private void OnPositionCancelled()
	{
		positionBtn.Reset();
	}

	// MOBILE

	// User tapped '+' button on mobile, shows popup
	private void OnAddTapped()
	{
		bool ios = DXDevice.IsIOS;
		bool forceLeft = (DXDevice.IsIOS && DXDevice.IsLandscapeRight() && (zone < 3));

		Thickness safeArea = DXDevice.SafeArea();
		
		double adjustX = forceLeft ? -addBtn.Width : addBtn.Width;
		double adjustY = (PopupHt / 2) - (ios ? 0 : safeArea.VerticalThickness);
		
		// WORKAROUND for landscape-right notch
		popup.ShowFromView( addBtn, adjustX, adjustY, forceLeft );
	}

	// Resets '+' button after mobile popup closes
	private void OnPopupClosed()
	{
		addBtn.Reset();
	}

	/* Layout */

	// Redraws entire player info area
	public override void UpdateLayout( LayoutType type )
	{
		ClearAll();

		base.UpdateLayout( type );
	}

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

		double wd = DXDevice.GetScreenWd();

		// Dynamic sizing
		double pad = (wd * (wide ? 0.005 : 0.008));
		double halfPad = (pad / 2);

		double avatarSize = (wd * (wide ? 0.070 : 0.075));
		double avatarHt = avatarSize + (pad * 2);

		double badgeSize = (wd * (ios ? 0.034 : (wide ? 0.032 : 0.035)));
		double fontSize = (wd * (wide ? 0.016 : 0.018));
		
		double zoneHt = (wd * (wide ? 0.030 : 0.040));
		double zoneFont = (wd * (wide ? 0.020 : 0.028));

		double labelHt = (fontSize * 1.25);

		double buttonWd = (wd * 0.050);
		double buttonHt = (wd * 0.026);
		double buttonFont = (wd * 0.015);

		double badgeHt = badgeSize + (ios ? pad : (wide ? 8 : 9));
		double subHt = (buttonHt + pad);

		Color zoneColor = PlayerCell.DividerColor;

		// 7 rows
		AddFixedRow( zoneHt );					// 0: zone
		AddFixedRow( avatarHt );                // 1: avatar
		AddFixedRow( labelHt );					// 2: first
		AddStarRow();							// 3: last
		AddFixedRow( badgeHt );					// 4: number, pos
		AddFixedRow( 1 );						// 5: divider
		AddFixedRow( subHt );					// 6: sub

		// 2 columns
		AddFixedColumn( badgeSize + pad );		// 0: badge
		AddStarColumn();						// 1: position

		/* Controls */

		// Zone
		zoneLbl.FontSize = zoneFont;
		zoneLbl.SetMargin( 0, (ios ? -4 : (wide ? -7 : -9)), 0, 0 );
		zoneLbl.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Fill( zoneColor, 0, 0, 2, 1 );
		Add( zoneLbl, 0, 0, 2, 1 );

		// Avatar
		avatarImg.SetSize( avatarSize, avatarSize, 0.80 );
		avatarImg.SetMargin( pad, pad, pad, pad );
		avatarImg.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( avatarImg, 0, 1, 2, 1 );

		// First
		firstLbl.FontSize = fontSize;
		firstLbl.SetMargin( pad, -2, halfPad, 0 );

		Add( firstLbl, 0, 2, 2, 1 );

		// Last
		lastLbl.FontSize = fontSize;
		lastLbl.SetMargin( pad, 2, halfPad, 0 );

		Add( lastLbl, 0, 3, 2, 1 );

		// Number
		numberBtn.NumberSize = fontSize + (ios ? 1 : -1);
		numberBtn.Size = badgeSize;
		
		numberBtn.SetMargin( pad, 0, 0, (ios ? 0 : (wide ? 0 : 4)) );
		numberBtn.SetPosition( LayoutOptions.Center, (wide ? LayoutOptions.End : LayoutOptions.Center) );

		Add( numberBtn, 0, 4 );

		// Position (button)
		positionBtn.FontSize = buttonFont;

		positionBtn.SetSize( buttonWd, buttonHt );
		positionBtn.SetMargin( 2, 0, 0, (ios ? 0 : (wide ? 0 : 4)) );
		positionBtn.SetPosition( LayoutOptions.Center, (wide ? LayoutOptions.End : LayoutOptions.Center) );

		Add( positionBtn, 1, 4 );

		// Position (label)
		positionLbl.Text = DXString.GetUpper( "teambar.libero" );
		positionLbl.FontSize = (fontSize * 0.75);

		positionLbl.SetMargin( 0, (wide ? -6 : -2), 0, 0 );
		positionLbl.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( positionLbl, 1, 4 );

		// Divider
		Fill( zoneColor, 0, 5, 2, 1 );

		// Sub
		subBtn.FontSize = buttonFont;

		subBtn.SetSize( buttonWd, buttonHt );
		subBtn.SetMargin( 0, 0, 0, (ios ? 1 : 2) );
		subBtn.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( subBtn, 0, 6, 2, 1 );
	}

	// Widescreen Landscape (16:10)
	protected override void WideLandscape()
	{
		Landscape();
	}

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

		double ht = DXDevice.GetScreenHt();

		// Dynamic sizing
		double pad = (ht * 0.005);
		double zoneHt = (ht * 0.031);

		double playerHt = PlayerCell.PlayerHt();
		double avatarSize = (playerHt - zoneHt - (wide ? (pad * 4): (pad * 2)));

		double col1Wd = (avatarSize + (pad * 2));

		double fontSize = (ht * (wide ? 0.015 : 0.019));
		double numFontSize = (ht * (ios ? 0.017 : 0.016));
		double badgeSize = (ht * 0.032);

		double labelHt = (ht * (wide ? 0.022 : 0.023));
		double badgeHt = badgeSize + (wide ? pad : (ios ? 0 : 3));

		double buttonWd = (ht * 0.052);
		double buttonHt = (ht * 0.027);

		// 4 rows
		AddFixedRow( zoneHt );					// 0: zone
		AddFixedRow( labelHt );					// 1: first
		AddStarRow();							// 2: last
		AddFixedRow( badgeHt );                 // 3: number, pos, sub

		// 2 columns
		AddFixedColumn( col1Wd );               // 0: zone, avatar
		AddStarColumn();                        // 1: others

		/* Controls */

		// Zone
		zoneLbl.FontSize = (ht * 0.025);
		zoneLbl.SetMargin( 0, (ios ? -3 : (wide ? -12 : -8)), 0, 0 );
		zoneLbl.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Fill( PlayerCell.DividerColor, 0, 0, 2, 1 );
		Add( zoneLbl, 0, 0, 2, 1 );

		// Avatar
		avatarImg.SetSize( avatarSize, avatarSize, 0.80 );
		avatarImg.SetMargin( pad, (pad + (ios ? 0 : 1)), pad, pad );
		avatarImg.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( avatarImg, 0, 1, 1, 3 );

		// First
		firstLbl.FontSize = fontSize;
		firstLbl.SetMargin( pad, (ios ? 3 : 0), pad, 0 );

		Add( firstLbl, 1, 1 );

		// Last
		lastLbl.FontSize = fontSize;
		lastLbl.SetMargin( pad, (ios ? 1 : (wide ? 0 : -1)), pad, 0 );

		Add( lastLbl, 1, 2 );

		// Number
		numberBtn.NumberSize = wide ? (numFontSize + 2) : numFontSize;
		numberBtn.Size = badgeSize;
		
		numberBtn.SetMargin( (pad - (wide ? 3 : 0)), -2, 0, pad );
		numberBtn.SetPosition( LayoutOptions.Start, LayoutOptions.Center );

		Add( numberBtn, 1, 3 );

		double posX = (badgeSize + (pad * 2));

		// Position (button)
		positionBtn.FontSize = (ht * 0.014);

		positionBtn.SetSize( buttonWd, buttonHt );
		positionBtn.SetMargin( posX, (ios ? 1 : 3), 0, pad );
		positionBtn.SetPosition( LayoutOptions.Start, LayoutOptions.Center );

		Add( positionBtn, 1, 3 );

		// Position (label)
		positionLbl.Text = DXString.GetUpper( "teambar.libero" );
		positionLbl.FontSize = (fontSize * 0.75);

		positionLbl.SetMargin( posX, (ios ? -4 : (wide ? 0 : 3)), 0, (wide ? pad : 0) );
		positionLbl.SetPosition( LayoutOptions.Start, LayoutOptions.Center );

		Add( positionLbl, 1, 3 );

		// Sub
		subBtn.SetSize( buttonWd, buttonHt );
		subBtn.SetMargin( (wide ? -1 : 0), (ios ? 1 : (wide ? 5 : 4)), 3, (wide ? pad : 0) );
		subBtn.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( subBtn, 1, (wide ? 0 : 3) );
	}

	// Widescreen Portrait (16:10)
	protected override void WidePortrait()
	{
		Portrait();
	}

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

		// Dynamic sizing
		double ht = PlayerCell.PlayerHt();
		const double btnHt = 35;

		double pad = (ht * 0.05);

		double avatarSize = (ht * (ios ? 0.58 : 0.48));
		double badgeSize = (ht * (ios ? 0.30 : 0.28));
		double addSize = (ht * 0.38);

		double nameFont = (ht * 0.15);
		double numberFont = (ht * (ios ? 0.16 : 0.14));

		// 4 rows
		AddStarRow( 20 );				        // 0: zone
		AddStarRow( 34 );						// 1: avatar, number
		AddStarRow( ios ? 21 : 16 );			// 2: avatar, pos
		AddStarRow( ios ? 25 : 30 );			// 3: first/last

		// 4 columns
		AddStarColumn( safeArea ? 30 : 35 );	// 0: avatar
		AddStarColumn( safeArea ? 40 : 35 );	// 1: number, pos
		AddFixedColumn( 1 );					// 2: divider
		AddStarColumn( 30 );					// 3: add

		/* Controls */

		// Zone
		zoneLbl.FontSize = (nameFont * 1.1);
		zoneLbl.SetMargin( 0, (ios ? -3 : -7), 0, 0 );
		zoneLbl.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Fill( PlayerCell.DividerColor, 0, 0, 4, 1 );
		Add( zoneLbl, 0, 0, 4, 1 );

		// Avatar
		avatarImg.SetSize( avatarSize, avatarSize, 0.80 );
		avatarImg.SetMargin( pad, pad, 0, 0 );
		avatarImg.SetPosition( LayoutOptions.Start, LayoutOptions.Start );

		Add( avatarImg, 0, 1, 1, 2 );

		// Number
		numberBtn.NumberSize = numberFont;

		numberBtn.SetSize( badgeSize, true );
		numberBtn.SetMargin( 0, 0, 0, 0 );
		numberBtn.SetPosition( LayoutOptions.Start, LayoutOptions.Center );

		Add( numberBtn, 1, 1 );

		const double posWd = 48;
		const double posHt = (btnHt * 0.65);

		// Position (button)
		positionBtn.SetSize( posWd, posHt );
		positionBtn.SetMargin( 0, 0, 0, 0 );
		positionBtn.SetPosition( LayoutOptions.Start, LayoutOptions.Start );

		Add( positionBtn, 1, 2 );

		// Position (label)
		positionLbl.Text = DXString.GetUpper( "teambar.lib" );
		positionLbl.FontSize = (nameFont - 3);

		positionLbl.SetMargin( (pad / 2), 0, 0, 0 );
		positionLbl.SetPosition( LayoutOptions.Start, LayoutOptions.End );

		Add( positionLbl, 1, 2 );

		// First/Last
		firstLbl.Text = Player?.FullName!;
		firstLbl.FontSize = nameFont;

		firstLbl.SetPosition( LayoutOptions.Start, LayoutOptions.Center );
		firstLbl.SetMargin( pad, (pad / 2), 0, 0 );

		Add( firstLbl, 0, 3, 2, 1 );

		// Divider
		Fill( PlayerCell.DividerColor, 2, 0, 1, 4 );

		// Sub
		subBtn.SetSize( 50, btnHt );
		subBtn.SetMargin( 0, 0, 0, 0 );
		subBtn.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Fill( DXColors.Light4, 3, 1, 1, 3 );
		Add( subBtn, 3, 1, 1, 3 );

		// Add
		addBtn.SetSize( addSize, true );
		addBtn.SetMargin( 0, 0, 0, 0 );
		addBtn.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( addBtn, 3, 1, 1, 3 );
	}

	// Mobile Portrait
	protected override void MobilePortrait()
	{
		bool ios = DXDevice.IsIOS;

		// Dynamic sizing
		double wd = (DXDevice.GetScreenWd() / Lineup.RowEntries) - PlayerGrid.Pad;
		const double btnHt = 30;

		double zoneFont = (wd * (ios ? 0.19 : 0.17));
		double nameFont = Math.Min( (wd * 0.20), 20 );
		double numberFont = (nameFont * 0.9);

		double avatarSize = (wd * 0.55);
		double badgeSize = (wd * 0.28);
		double addSize = (wd * 0.36);

		double pad = (wd * 0.040);

		// 7 rows
		AddStarRow( 12 );       // 0: zone
		AddStarRow( 20 );       // 1: avatar, number
		AddStarRow( 15 );       // 2: avatar, pos
		AddStarRow( 14 );       // 3: first
		AddStarRow( 14 );       // 4: last
		AddFixedRow( 1 );		// 5: divider
		AddStarRow( 24 );       // 6: add

		// 2 columns
		AddStarColumn( 60 );	// 0: avatar
		AddStarColumn( 40 );	// 1: number, pos

		/* Controls */

		// Zone
		zoneLbl.FontSize = zoneFont;
		zoneLbl.SetMargin( 0, (ios ? -3 : -10), 0, 0 );
		zoneLbl.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Fill( PlayerCell.DividerColor, 0, 0, 3, 1 );
		Add( zoneLbl, 0, 0, 3, 1 );

		// Avatar
		avatarImg.SetSize( avatarSize, avatarSize, 0.80 );
		avatarImg.SetMargin( pad, pad, 0, 0 );
		avatarImg.SetPosition( LayoutOptions.Start, LayoutOptions.Center );

		Add( avatarImg, 0, 1, 1, 2 );

		// First
		firstLbl.Text = Player?.FirstName!;
		firstLbl.FontSize = nameFont;

		double rightPad = ios ? 0 : 3;

		firstLbl.SetMargin( pad, 0, rightPad, 0 );
		firstLbl.SetPosition( LayoutOptions.Start, LayoutOptions.Start );

		Add( firstLbl, 0, 3, 3, 1 );

		// Last
		lastLbl.FontSize = nameFont;
		lastLbl.SetMargin( pad, -pad, rightPad, 0 );
		lastLbl.SetPosition( LayoutOptions.Start, LayoutOptions.Start );

		Add( lastLbl, 0, 4, 3, 1 );

		// Number
		numberBtn.NumberSize = numberFont;

		numberBtn.SetSize( badgeSize, true );
		numberBtn.SetMargin( 0, pad, 0, 0 );
		numberBtn.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( numberBtn, 1, 1 );

		// Position (button)
		positionBtn.SetSize( (badgeSize + 4), btnHt );
		positionBtn.SetMargin( 0, 0, 0, 0 );
		positionBtn.SetPosition( LayoutOptions.Center, LayoutOptions.Start );

		Add( positionBtn, 1, 2 );

		// Position (label)
		positionLbl.Text = DXString.GetUpper( "teambar.lib" );
		positionLbl.FontSize = (nameFont * 0.80);

		positionLbl.SetMargin( 0, 0, 0, 0 );
		positionLbl.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( positionLbl, 1, 2 );

		// Divider
		Fill( PlayerCell.DividerColor, 0, 5, 2, 1 );
		Fill( DXColors.Light4, 0, 6, 2, 1 );

		// Add
		addBtn.SetSize( addSize, true );
		addBtn.SetMargin( 0, 0, 0, 0 );
		addBtn.SetPosition( LayoutOptions.Center, LayoutOptions.Center ); 
		
		Add( addBtn, 0, 6, 2, 1 );

		// Sub
		subBtn.SetSize( 50, btnHt );
		subBtn.SetMargin( 0, 0, 0, 0 );
		subBtn.SetPosition( LayoutOptions.Center, LayoutOptions.Center );

		Add( subBtn, 0, 6, 2, 1 );
	}
}

//
