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

using DXLib.Log;
using DXLib.Utils;

using Side = iStatVball3.RecordCourt.Side;

namespace iStatVball3;

/*
 * Base class for both the RallyFlow and Legacy engine scoreboards. Responsible for all UI layout of the scoreboard area
 * including scorecards, control buttons, and undo stack.
 */
public abstract class RecordScore : DXAbsoluteLayout
{
	/* Constants */
	private const double LogDrawerWd = 400;
	private const double ReportDrawerWd = 700;
	
	/* Events */
	public Action UndoTapped { get; set; }
	public Action UndoPressed { get; set; }

	public Action PauseTapped { get; set; }

	/* Properties */

	// Button control
	public bool IsUndoDisabled { set => DisableUndo( value ); }
	public virtual bool IsFaultDisabled { get; set; }

	// Scores converted from side A/B to team 1/2
	public int Score1 => sm.Team1OnSideA ? scoreABtn.Score : scoreBBtn.Score;
	public int Score2 => sm.Team1OnSideA ? scoreBBtn.Score : scoreABtn.Score;

	// External access
	public ScoreUndo UndoStack { get; }
	public RecordState StateMachine { get => sm; set => sm = value; }

	// Fault handling
	public virtual Rect FaultBounds => Rect.Zero;

	// Report/Log drawer open?
	public bool IsDrawerOpen => (reportOpen || logOpen);

	/* Fields */

	// External references
	protected readonly RecordEngine parent;
	protected RecordState sm;

	// Recording for this set
	private Set recordSet;

	// UI controls
	protected readonly ScoreLogo logoBar;
	protected readonly ToolBar toolBar;

	protected readonly DrawerIcon pauseBtn;
	protected readonly DrawerIcon endBtn;
	protected readonly DrawerIcon switchBtn;

	protected readonly DrawerButton timeoutsABtn;
	protected readonly ScoreButton scoreABtn;

	protected readonly DrawerPanel setsABtn;
	protected readonly DXIconButton possABtn;

	protected readonly DrawerPanel setBtn;

	protected readonly DXIconButton possBBtn;
	protected readonly DrawerPanel setsBBtn;

	protected readonly ScoreButton scoreBBtn;
	protected readonly DrawerButton timeoutsBBtn;

	protected readonly DrawerIcon undoBtn;

	// Timeout control
	private bool timeoutInProgress;
	
	// Log/report drawer
	private LogDrawer logDrawer;
	private ReportDrawer reportDrawer;

	private bool logOpen;
	private bool reportOpen;

	/* Methods */

	// Creates all shared controls, dynamic layout occurs later
	protected RecordScore( RecordEngine parent )
	{
		this.parent = parent;

		IgnoreSafeArea = true;
		
		Horizontal = LayoutOptions.Fill;
		Vertical = LayoutOptions.Fill;
		
		BackgroundColor = DXColors.Dark2;

		/* Controls */

		// Logo
		logoBar = new ScoreLogo();
		Add( logoBar );

		// Toolbar (with custom report/log buttons)
		toolBar = new ToolBar( false )
		{
			Custom1Tapped = OnLogTapped,
			Custom2Tapped = OnReportTapped
		};

		toolBar.AddCustom( 1, "list", "drawer.log" );
		toolBar.AddCustom( 2, "analyze", "drawer.report" );

		Add( toolBar );

		// Pause
		pauseBtn = new DrawerIcon( this )
		{
			Title = "score.pause",
			Resource = "pause",
			Color = DXColors.Warn,
			IsCircle = true,

			ButtonTapped = OnPauseTapped
		};
		
		pauseBtn.Init();
		
		// End
		endBtn = new DrawerIcon( this )
		{
			Title = "score.end",
			Resource = "end",
			Color = DXColors.Negative,
			IsCircle = true,
			
			IsSticky = true,
			ButtonTapped = OnEndTapped
		};

		endBtn.Init();
		
		// Switch
		switchBtn = new DrawerIcon( this )
		{
			Title = "score.switch",
			Color = DXColors.Positive,
			IsCircle = true,
			
			IsSticky = true,
			ButtonTapped = OnSwitchTapped
		};

		switchBtn.Init();
		
		// Timeout A
		timeoutsABtn = new DrawerButton( this )
		{
			Title = "score.timeout",
			Color = DXColors.Action,

			IsSticky = true,
			ButtonTapped = OnTimeoutATapped
		};

		timeoutsABtn.Init();
		
		// Score A
		scoreABtn = new ScoreButton( this )
		{
			ScoreTapped = OnScoreATapped,
			ScorePressed = OnScoreAPressed
		};

		// Sets A
		setsABtn = new DrawerPanel( this )
		{
			Title = "score.sets"
		};

		// Possession A
		possABtn = new DXIconButton
		{
			IconScale = 0.90f,
			HasShadow = true,
			IsSticky = true,

			ButtonTapped = OnPossTapped
		};

		Add( possABtn );

		// Set Number
		setBtn = new DrawerPanel( this )
		{
			IsCircle = true
		};

		// Possession B
		possBBtn = new DXIconButton
		{
			IconScale = 0.95f,
			HasShadow = true,
			IsSticky = true,

			ButtonTapped = OnPossTapped
		};

		Add( possBBtn );

		// Sets B
		setsBBtn = new DrawerPanel( this )
		{
			Title = "score.sets",
		};

		// Score B
		scoreBBtn = new ScoreButton( this )
		{
			ScoreTapped = OnScoreBTapped,
			ScorePressed = OnScoreBPressed
		};

		// Timeout B
		timeoutsBBtn = new DrawerButton( this )
		{
			Title = "score.timeout",
			Color = DXColors.Action,

			IsSticky = true,
			ButtonTapped = OnTimeoutBTapped
		};

		timeoutsBBtn.Init();
		
		// Undo
		undoBtn = new DrawerIcon( this )
		{
			Title = "score.undo",
			Resource = "undo",
			Color = DXColors.Positive,
			IsCircle = true,

			IsDisabled = true,				// Start disabled
			IsSticky = false,
			
			ButtonTapped = OnUndoTapped,
			ButtonPressed = OnUndoPressed
		};

		undoBtn.Init();
		
		// Undo Stack
		UndoStack = new ScoreUndo( this );
		Add( UndoStack );
	}
	
	// Initializes scoreboard for start of new set
	public async Task Init( Set set )
	{
		recordSet = set;

		// Set number
		setBtn.Number = set.Number;

		// Undo
		UndoStack.Init( set );
		
		// Log/report drawers
		InitLogDrawer();
		await InitReportDrawer();
	}

	// Pre-creates event log drawer
	private void InitLogDrawer()
	{
		// Create drawer
		DXDrawer drawer = new()
		{
			DrawerWd = LogDrawerWd
		};
		
		// Create underlying content
		logDrawer = new LogDrawer( drawer )
		{
			EventSelected = OnEventSelected,
			Closed = OnLogClosed
		};
		
		logDrawer.Init( recordSet );
		
		drawer.SetTitle( "drawer.log" );
		drawer.SetView( logDrawer );
	}
	
	// Pre-creates live stat report drawer
	private async Task InitReportDrawer()
	{
		// Create drawer
		DXDrawer drawer = new()
		{
			DrawerWd = ReportDrawerWd,
		};
		
		// Create underlying content
		reportDrawer = new ReportDrawer( drawer )
		{
			Closed = OnReportClosed
		};
		
		await reportDrawer.Init( recordSet );
		
		drawer.SetTitle( "drawer.report" );
		drawer.SetView( reportDrawer );
	}
	
	/* Update */

	// Updates points for both teams at end of rally (or side switch)
	public virtual void UpdateScore( int scoreA, int scoreB )
	{
		scoreABtn.Score = scoreA;
		scoreBBtn.Score = scoreB;
	}

	// Updates possession after change of serve
	public void UpdatePossession( Side side )
	{
		possABtn.IsVisible = (side == Side.SideA);
		possBBtn.IsVisible = !possABtn.IsVisible;
	}

	// Updates timeouts remaining
	public void UpdateTimeouts( int timeoutsA, int timeoutsB )
	{
		timeoutsABtn.Number = timeoutsA;
		timeoutsBBtn.Number = timeoutsB;

		timeoutsABtn.IsDisabled = !timeoutInProgress && (timeoutsA == 0);
		timeoutsBBtn.IsDisabled = !timeoutInProgress && (timeoutsB == 0);

		timeoutsABtn.IsSticky = true;
		timeoutsBBtn.IsSticky = true;
	}

	// Update sets won totals
	public void UpdateSets( int setsA, int setsB )
	{
		setsABtn.Number = setsA;
		setsBBtn.Number = setsB;
	}

	// Updates team names on each side of court
	public virtual void UpdateTeams( string teamA, string teamB )
	{
		scoreABtn.Team = teamA;
		scoreBBtn.Team = teamB;
	}

	// Updates scorecard color for team on each side of court
	public virtual void UpdateTeamColors( Color colorA, Color colorB )
	{
		scoreABtn.BarColor = colorA;
		scoreBBtn.BarColor = colorB;
	}

	/* Timeouts */

	// Begins timeout countdown timer for specified team
	private void StartTimeout( bool teamA )
	{
		timeoutInProgress = true;

		// User defined duration (sec)
		int seconds = Shell.Settings.TimeoutSec;
		
		// 0 = OFF
		if ( seconds > 0 )
		{
			DrawerButton btn = teamA ? timeoutsABtn : timeoutsBBtn;
			DrawerButton otherBtn = teamA ? timeoutsBBtn : timeoutsABtn;

			// Set initial state
			btn.IsDisabled = false;
			otherBtn.IsDisabled = true;

			btn.IsSticky = false;
			btn.Number = seconds;
			btn.Color = DXColors.Warn;

			// Start countdown
			DXTimer timer = new()
			{ 
				Interval = 1000,
				IsRepeating = true,
				Callback = () => TickTimeout( btn, seconds ),
			};
			
			timer.Start();
		}
	}

	// Handles each 1sec tick of timeout countdown
	private void TickTimeout( DrawerButton button, int seconds )
	{
		// Decrement counter
		if ( timeoutInProgress && (seconds > 0) )
		{
			button.Number = --seconds;
		}
		// Timeout over
		else
		{
			EndTimeout( button );
		}
	}
	
	// Handles functionality when timeout ends
	public void EndTimeout( DrawerButton btn )
	{
		if ( timeoutInProgress )
		{
			timeoutInProgress = false;

			// Reset button(s)
			if ( btn == null )
			{
				timeoutsABtn.Color = DXColors.Action;
				timeoutsBBtn.Color = DXColors.Action;
			}
			else
			{
				btn.Color = DXColors.Action;
			}

            // Reset state
            UpdateTimeouts( sm.TimeoutsA, sm.TimeoutsB );
		}
	}

	/* Disable */

	// Disables relevant buttons based on rally state
	public virtual void DisableButtons( bool disabled )
	{
		// Sample recording cannot end set/match
		endBtn.IsDisabled = recordSet.IsNew || disabled || (recordSet.IsSample && !Shell.CurrentUser.IsAdmin);
		switchBtn.IsDisabled = disabled;

		possABtn.IsDisabled = disabled;
		possBBtn.IsDisabled = disabled;

		timeoutsABtn.IsDisabled = !timeoutInProgress && (disabled || (sm.TimeoutsA == 0));
		timeoutsBBtn.IsDisabled = !timeoutInProgress && (disabled || (sm.TimeoutsB == 0));
	}

	// Disables manual scorecard buttons
	public virtual void DisableScore( bool disabled )
	{
		scoreABtn.IsDisabled = disabled;
		scoreBBtn.IsDisabled = disabled;
	}

	// Enables/disables undo button
	protected virtual void DisableUndo( bool disabled )
	{
		if ( undoBtn != null )
		{
			undoBtn.IsDisabled = disabled;
		}
	}

	/* Event Callbacks */

	/* Pause */

	// Tap
	protected virtual void OnPauseTapped()
	{
		// Optional listener overrides default functionality
		if ( PauseTapped == null )
		{
			Shell.Instance.HideRecordForm( recordSet );
		}
		else
		{
			PauseTapped.Invoke();
		}
	}

	/* End */

	// Tap
	private void OnEndTapped()
	{
		int points1 = Score1;
		int points2 = Score2;

		string msg1;

		// Set ended in tie
		if ( points1 == points2 )
		{
			msg1 = DXString.Get( "set.end.tie", recordSet.Number.ToString() );
		}
		// Winner
		else
		{
			string winner = (points1 > points2) ? recordSet.Match.Team1Name : recordSet.Match.Team2Name;

			msg1 = DXString.Get( "set.end.win", recordSet.Number, winner );
		}

		int won = Math.Max( points1, points2 );
		int lost = Math.Min( points1, points2 );

		// 'End Set X with TeamFoo Winner (Y-X)?'
		string msg2 = DXString.Get( "set.end.result", won.ToString(), lost.ToString() );
		string msg = msg1 + msg2;

		DXAlert.ShowNegativeCancelRaw( DXString.Get( "set.end" ), msg, "alert.end", OnEndConfirmed, endBtn.Reset );
	}

	// Confirm
	protected virtual void OnEndConfirmed()
	{
		endBtn.Reset();
	}

	/* Switch Sides */

	// Tap
	private void OnSwitchTapped()
	{
		DXAlert.ShowOkCancel( "score.sides", "score.sides.msg", OnSwitchConfirmed, OnSwitchCancelled );
	}

	// Confirm
	private void OnSwitchConfirmed()
	{
		sm.SwitchSides();

		switchBtn.Reset();
	}

	// Cancel
	private void OnSwitchCancelled()
	{
		switchBtn.Reset();
	}

	/* Change Possession */

	// Tap
	private void OnPossTapped()
	{
		string name = sm.NameForTeam( (Equals( sm.ServeTeam, sm.TeamA )) ? sm.TeamB : sm.TeamA );

		// 'Change serve possession to <FooTeam>?'
		DXAlert.ShowOkCancel( "score.poss", "score.poss.msg", name, OnPossConfirmed, OnPossCancelled );
	}

	// Confirm
	protected virtual void OnPossConfirmed()
	{
		sm.ChangePossession();

		possABtn.Reset();
		possBBtn.Reset();
	}

	// Cancel
	private void OnPossCancelled()
	{
		possABtn.Reset();
		possBBtn.Reset();
	}

	/* Timeout A */

	// Tap
	private void OnTimeoutATapped( object sender )
	{
		// Ending timeout early
		if ( timeoutInProgress )
		{
			EndTimeout( timeoutsABtn );
		}
		// Confirm timeout
		else
		{
			string name = sm.NameForTeam( sm.TeamA );

			DXAlert.ShowOkCancel( "score.call", "score.call.msg", name, OnTimeoutAConfirmed, OnTimeoutACancelled );
		}
	}

	// Confirm
	private void OnTimeoutAConfirmed()
	{
		sm.CallTimeout( Side.SideA );

		timeoutsABtn.Reset();

		// Start countdown timer
		StartTimeout( true );
	}

	// Cancel
	private void OnTimeoutACancelled()
	{
		timeoutsABtn.Reset();
	}

	/* Timeout B */

	// Tap
	private void OnTimeoutBTapped( object sender )
	{
		// Ending timeout early
		if ( timeoutInProgress )
		{
			EndTimeout( timeoutsBBtn );
		}
		// Confirm timeout
		else
		{
			string name = sm.NameForTeam( sm.TeamB );

			DXAlert.ShowOkCancel( "score.call", "score.call.msg", name, OnTimeoutBConfirmed, OnTimeoutBCancelled );
		}
	}

	// Confirm
	private void OnTimeoutBConfirmed()
	{
		sm.CallTimeout( Side.SideB );

		timeoutsBBtn.Reset();

		// Start countdown timer
		StartTimeout( false );
	}

	// Cancel
	private void OnTimeoutBCancelled()
	{
		timeoutsBBtn.Reset();
	}

	/* Score A */

	// Tap
	public void OnScoreATapped()
	{
		OnScoreAConfirmed();
	}

	// Confirm
	private void OnScoreAConfirmed()
	{
		sm.ForcePoint( Side.SideA );

		scoreABtn.Reset();
	}

	// Cancel
	private void OnScoreACancelled()
	{
		scoreABtn.Reset();
	}

	// Press
	private void OnScoreAPressed()
	{
		DXNumericEditor editor = new()
		{
			MinValue = 0,
			MaxValue = 99,

			ShouldPad = true,
			ShouldWrap = false,

			Number = sm.ScoreA,

			Selected = OnScoreASelected,
			Cancelled = OnScoreACancelled
		};

		editor.Show( scoreABtn, 0, ((DXDevice.IsMobile && DXDevice.IsLandscape()) ? -RecordEngine.ScoreboardHt : 0) );
	}

	// Selected
	private void OnScoreASelected( int score )
	{
		sm.ChangeScore( true, score );

		scoreABtn.Reset();
	}

	/* Score B */

	// Tap
	public void OnScoreBTapped()
	{
		OnScoreBConfirmed();
	}

	// Confirm
	private void OnScoreBConfirmed()
	{
		sm.ForcePoint( Side.SideB );

		scoreBBtn.Reset();
	}

	// Cancel
	private void OnScoreBCancelled()
	{
		scoreBBtn.Reset();
	}

	// Press
	private void OnScoreBPressed()
	{
		DXNumericEditor editor = new()
		{
			MinValue = 0,
			MaxValue = 99,

			ShouldPad = true,
			ShouldWrap = false,

			Number = sm.ScoreB,

			Selected = OnScoreBSelected,
			Cancelled = OnScoreBCancelled
		};

		editor.Show( scoreBBtn, 0, ((DXDevice.IsMobile && DXDevice.IsLandscape()) ? -RecordEngine.ScoreboardHt : 0) );
	}

	// Selected 
	private void OnScoreBSelected( int score )
	{
		sm.ChangeScore( false, (int)score );

		scoreBBtn.Reset();
	}

	/* Undo */

	// Tap
	public void OnUndoTapped()
	{
		UndoTapped?.Invoke();
	}

	/* Undo Rally */

	// Press
	public void OnUndoPressed()
	{
		if ( sm.StatCount > 0 )
		{
			DXAlert.ShowOkCancel( "score.rally", "score.rally.msg", OnUndoConfirmed, OnUndoCancelled );
		}
	}

	// Confirm
	private void OnUndoConfirmed()
	{
		UndoPressed?.Invoke();

		undoBtn.Reset();
	}

	private void OnUndoCancelled()
	{
		undoBtn.Reset();
	}

	/* Log */

	// User tapped to open log drawer
	private void OnLogTapped()
	{
		logOpen = true;
		
		// Open drawer, load stats
		logDrawer.Show();
		logDrawer.Load( recordSet.StatCache );
	}
	
	// User selected event from log, show editing interface
	private void OnEventSelected( object data )
	{
		// Can NOT edit during rally
		if ( !sm.IsRallyInProgress )
		{
			LogEvent evt = data as LogEvent;

			// Create UI
			EditLogPopup popup = new( logDrawer );

			// Populate with event
			popup.Init( recordSet );
			popup.Show( evt );
		}
	}

	// User closed log drawer
	private void OnLogClosed()
	{
		logDrawer.Hide();
		logOpen = false;
		
		toolBar.ResetCustom();
	}

	/* Report */

	// User tapped to open report drawer
	private async void OnReportTapped()
	{
		reportOpen = true;

		// Open drawer, load report
		reportDrawer.Show();
		await reportDrawer.Load();
	}

	// User closed report drawer
	private void OnReportClosed()
	{
		reportDrawer.Hide();
		reportOpen = false;
		
		toolBar.ResetCustom();
	}

	/* Layout */

	// Scoreboard specific layout
	public override void UpdateLayout( LayoutType type )
	{
		DXLog.Debug( "Scoreboard" );
		
		base.UpdateLayout( type );

		// Update children
		logoBar.UpdateLayout( type );
		toolBar.UpdateLayout( type );

		UndoStack.UpdateLayout( type );
	}
}

//
