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

using System.Collections.ObjectModel;

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

using DXLib.Utils;

namespace iStatVball3;

/*
 * Displays a history of stat events as a vertical scrolling list. Events can be dynamically added, updated, and removed.
 * Events are also tappable to trigger an associated action such as editing or linking to video.
 */
public class LogView : DXContent
{
	/* Constants */
	private static readonly Color TextColor = DXColors.Light4;

	// Events scrolling live or used for select/edit?
	public enum LogMode
	{
		Live,			// Not selectable
		Select,			// Single select
		SelectAll,		// Single select (all enabled)
		Edit			// Select + edit marker
	};

	/* Events */
	public Action<LogEvent> EventSelected { get; set; }

	/* Properties */

	// Current log mode
	public LogMode Mode { get; set; }

	// Special handling for live modes
	private bool IsLive => Mode == LogMode.Live;

	// Offset to first serve (video sync)
	public int StartOffset { get; set; }

	// Currently selected line (if any)
	public LogEvent SelectedEvent { get; private set; }

	// Number of events in list
	public int Count => events.Count;

	// Styling
	public double FontSize { get; set; }
	public Thickness CellPadding { get; set; }

	public int MaxNameLength { set => history.MaxNameLength = value; }

	/* Fields */

	// List display
	private readonly RecordHistory history;
	private readonly LogListView listView;

	// Underlying event data
	private ObservableCollection<LogEvent> events;

	// External ref
	private Match recordMatch;

	/* Methods */
	public LogView( bool wide )
	{
		Padding = 0;

		Horizontal = LayoutOptions.Fill;
		Vertical = LayoutOptions.Fill;
		
		bool tablet = DXDevice.IsTablet;

		// Prep line creator
		history = new RecordHistory
		{
			ShowTeams = true,
			ShowScores = true,
			ShowRatings = true,
			ShowErrors = true,

			ShowModifiers = true,
			ShowFaults = true,
			ShowMultiBlock = tablet,

			ActionMode = (tablet || wide) ? RecordHistory.ActionModes.ActionSelector : RecordHistory.ActionModes.Selector,

			AbbreviateTeams = true,
			AbbreviateNames = !tablet,
			AbbreviateResults = !tablet,

			MaxNameLength = 16
		};

		// Create underlying control
		listView = new LogListView
		{
			EventSelected = OnEventSelected
		};

		Content = listView;

		// Defaults
		Mode = LogMode.Live;
		FontSize = DXDevice.IsIOS ? 15 : 14;
	}

	// Post construction initialization
	public void Init( Match match )
	{
		recordMatch = match;

		// Prep line creator
		history.Init( match );

		// Initialize underlying list
		listView.Init( Mode );
	}

	// Loads starting list of stats
	public void Load( List<Stat> stats, bool descending = false )
	{
		List<Stat> statList;

		// Optionally sort in descending order (sample stats do not use offset)
		if ( descending )
		{
			var sorted = recordMatch.IsSample ? stats.OrderByDescending( s => s.Timestamp ) : stats.OrderByDescending( s => s.Offset );
			statList = sorted.ToList();
		}
		else
		{
			statList = [ ..stats ];
		}

		List<LogEvent> eventList = new( statList.Count );

		// Create log events
		foreach ( Stat stat in statList )
		{
			LogEvent evt = CreateEvent( stat );

			eventList.Add( evt );
		}

		// Allocate container
		events = new ObservableCollection<LogEvent>( eventList );

		// Bind data to log list
		listView.EventSource = events;
	}

	// Create new log event from specified stat
	private LogEvent CreateEvent( Stat stat )
	{
		if ( IsLive )
		{
			LogEvent prev = events.FirstOrDefault();

			// Reset previous top of stack colors
			if ( prev != null )
			{
				prev.TextColor = GetColor( prev.Stat, TextColor );
				prev.Color = DXColors.Dark2;
			}
		}

		Color color = GetColor( stat, (IsLive ? DXColors.Action : TextColor) );

		// Add to list
		LogEvent evt = new()
		{
			Stat = stat,
			Modified = false,

			Offset = GetOffset( stat, StartOffset ),
			Line = GetLine( stat ),

			TextColor = IsLive ? TextColor : color,
			Color = IsLive ? color : DXColors.Dark2,
			Padding = CellPadding,

			Font = stat.IsEnd ? DXFonts.RobotoBold : DXFonts.Roboto,
			FontSize = FontSize,

			// Non-selectable events dimmed
			Opacity = (IsLive || (Mode == LogMode.SelectAll)) ? 1.0 : (stat.IsAction ? 1.0 : 0.4)
		};

		return evt;
	}

	// Adds new stat to top of event log
	public void Add( Stat stat )
	{
		LogEvent evt = CreateEvent( stat );

		events.Insert( 0, evt );
	}

	// Modifies existing log line for specified stat
	public void Modify( Stat stat )
	{
		LogEvent evt = GetEvent( stat );

		// '0:12  Point Foo'
		if ( evt != null )
		{
			evt.Modified = true;

			evt.Offset = GetOffset( stat, StartOffset );
			evt.Line = GetLine( stat );
		}
	}

	// Removes log line associated with specified stat
	public void Remove( Stat stat )
	{
		LogEvent evt = GetEvent( stat );

		if ( (evt != null) && events.Contains( evt ) )
		{
			events.Remove( evt );
		}
	}

	// Redraws entire log event list
	public void Refresh()
	{
		Refresh( 0, Count );
	}

	// Redraws log event at specified index
	public void Refresh( int index, int range )
	{
		listView.Refresh( index, range );
	}

	// Redraws specified log event
	public void Refresh( LogEvent evt )
	{
		listView.Refresh( GetIndex( evt ) );
	}

	// Returns event following specified event, if any
	public LogEvent GetNextEvent( LogEvent evt )
	{
		int index = GetIndex( evt );

		// Return event at next index
		if ( index >= 0 )
		{
			int nextIndex = (index + 1);

			if ( nextIndex < events.Count )
			{
				return events[ nextIndex ];
			}
		}

		return null;
	}

	// Finds index of specified event in log
	private int GetIndex( LogEvent evt )
	{
		// Required because early version had stat counter bug
		for ( int i = 0; i < events.Count; i++ )
		{
			if ( events[i].Equals( evt ) )
			{
				return i;
			}
		}

		return -1;
	}

	// Scrolls list so specified event is centered in view
	public void ScrollTo( LogEvent evt )
	{
		int index = (evt == null) ? 0 : GetIndex( evt );

		listView.ScrollTo( index );
	}

	// Removes current selection from underlying list
	public void Deselect()
	{
		listView.SelectedItem = null;				 
	}

	/* Get */

	// Builds elapsed offset string in format 'min:sec'
	public static string GetOffset( Stat stat, int startOffset )
	{
		Set set = stat.Set;
		Match match = set.Match;

		// Default to offset from first serve
		int offset = stat.Offset;

		// RallySync uses offset from start of file
		if ( set.IsRallySynced )
		{
			offset = (stat.VideoOffset == 0) ? stat.Offset : stat.VideoOffset;
		}

		// Sample stats do not have offset
		if ( match.IsSample )
		{
			int index = (set.Number - 1);
			DateTimeOffset? dateTimeOffset = match.Sets[ index ].StartTime;
			
			if ( dateTimeOffset != null )
			{
				DateTimeOffset start = (DateTimeOffset) dateTimeOffset;

				// Time between stat and start of set
				offset = (int) stat.Timestamp.Subtract( start ).TotalMilliseconds;
			}
		}
		// Show time from start of video (if applicable)
		else
		{
			offset += startOffset;
		}

		// 'min:sec'
		string offsetStr = DXUtils.FromDurationShort( offset );

		// Mark undos with '*'
		if ( stat.Restored )
		{
			offsetStr += '*';
		}

		// Mark auto-generated events with '^'
		if ( stat.Auto )
		{
			offsetStr += '^';
		}

		return offsetStr;
	}

	// Uses record history to build log line for specified stat
	private string GetLine( Stat stat )
	{
		return history.CreateLine( stat );
	}

	// Used internally to choose text/background color based on specified stat
	public static Color GetColor( Stat stat, Color normal )
	{
		Match match = stat.Set.Match;

		// Special coloring for point events
		if ( stat.IsPoint )
		{
			return match.IsTeam1( stat.Point.TeamId ) ? DXColors.Positive : DXColors.Accent3;
		}

		// And end set/match
		return stat.IsEnd ? DXColors.Warn : normal;
	}

	// Returns log event matching specified stat
	private LogEvent GetEvent( Stat stat )
	{
		return (stat == null) ? null : events.FirstOrDefault( evt => evt.Stat.Equals( stat ) );
	}

	// Returns log event closest to specified offset
	public LogEvent GetEvent( int offset )
	{
		LogEvent prevEvt = null;

		// Scan events in order
		foreach ( LogEvent evt in events )
		{
			Stat stat = evt.Stat;

			// RallySync uses absolute video position, Live/Video relative to first serve
			int evtOffset = recordMatch.IsRallySync ? stat.VideoOffset : stat.Offset;

			// Past offset, use event immediately prior
			if ( evtOffset > offset )
			{
				return prevEvt;
			}

			prevEvt = evt;
		}

		// No match
		return null;
	}

	/* Event Callbacks */

	// User selected event from log
	private void OnEventSelected( object obj )
	{
		LogEvent evt = obj as LogEvent;

		SelectedEvent = evt;

		// Callback listener
		EventSelected?.Invoke( evt );
	}
}

//
