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

using System.ComponentModel;

using DXLib.Video.Source;

namespace DXLib.Video;

/*
 * Implements a MAUI view to play video backed by the native video player on both iOS and Android. Video can be played
 * via URI, local file, or embedded resource. Exposes properties for configuring the player, methods to control playback,
 * and events for monitoring status.
 *
 * Based on code by David Britch available at:
 * https://github.com/dotnet/maui-samples/tree/main/8.0/UserInterface/Handlers/CreateHandlerDemo/VideoDemos
 */
public partial class DXVideo : View, DXVideoController
{
    /* Events */
    public Action Ready { get; set; }
    public Action Ended { get; set; }
    
    /* Constants */
    
    // Video player states
    public enum VideoStatus
    {
        NotReady,
        Playing,
        Paused,
        Ended
    }
    
    #region Bindable Properties
    
    /* Properties */
    public LayoutOptions Horizontal { set => HorizontalOptions = value; }
    public LayoutOptions Vertical { set => VerticalOptions = value; }

    // ShowControls
    public bool ShowControls
    {
        get => (bool) GetValue( ShowControlsProperty );
        set => SetValue( ShowControlsProperty, value );
    }

    // Source
    [TypeConverter(typeof(DXVideoSourceConverter))]
    public DXVideoSource Source
    {
        get => (DXVideoSource) GetValue( SourceProperty );
        set => SetValue( SourceProperty, value );
    }

    // AutoPlay
    public bool AutoPlay
    {
        get => (bool) GetValue( AutoPlayProperty );
        set => SetValue( AutoPlayProperty, value );
    }

    // IsLooping
    public bool IsLooping
    {
        get => (bool) GetValue( IsLoopingProperty );
        set => SetValue( IsLoopingProperty, value );
    }

    // Status
    VideoStatus DXVideoController.Status
    {
        get => Status;
        set => SetValue( StatusPropertyKey, value );
    }
    
    public VideoStatus Status => (VideoStatus) GetValue( StatusProperty );

    // Duration
    TimeSpan DXVideoController.Duration
    {
        get => Duration;
        set => SetValue( DurationPropertyKey, value );
    }

    public TimeSpan Duration => (TimeSpan) GetValue( DurationProperty );

    // Position
    public TimeSpan Position
    {
        get => (TimeSpan) GetValue(PositionProperty );
        set => SetValue( PositionProperty, value );
    }

    // TimeToEnd
    public TimeSpan TimeToEnd
    {
        get => (TimeSpan) GetValue( TimeToEndProperty );
        private set => SetValue( TimeToEndPropertyKey, value );
    }

    // Speed
    public float Speed
    { 
        get => (float) GetValue( SpeedProperty );
        set => SetValue( SpeedProperty, value );
    }

    // Muted
    public bool Muted
    { 
        get => (bool)GetValue( MutedProperty );
        set => SetValue( MutedProperty, value );
    }

    // Status shorthand
    public bool IsLoading => (Status == VideoStatus.NotReady);
    public bool IsPlaying => (Status == VideoStatus.Playing);
    public bool IsStopped { get; private set; }
    
    /* Bindings */
    public static readonly BindableProperty ShowControlsProperty = BindableProperty.Create( nameof(ShowControls), typeof(bool), typeof(DXVideo), true );
    public static readonly BindableProperty SourceProperty = BindableProperty.Create( nameof(Source), typeof(DXVideoSource), typeof(DXVideo), null );
    
    public static readonly BindableProperty AutoPlayProperty = BindableProperty.Create( nameof(AutoPlay), typeof(bool), typeof(DXVideo), true );
    public static readonly BindableProperty IsLoopingProperty = BindableProperty.Create( nameof(IsLooping), typeof(bool), typeof(DXVideo), false );
    public static readonly BindableProperty PositionProperty = BindableProperty.Create( nameof(Position), typeof(TimeSpan), typeof(DXVideo), TimeSpan.Zero, propertyChanged: (bindable, oldValue, newValue) => ((DXVideo)bindable).SetTimeToEnd() );

    private static readonly BindablePropertyKey StatusPropertyKey = BindableProperty.CreateReadOnly( nameof(Status), typeof(VideoStatus), typeof(DXVideo), VideoStatus.NotReady );
    public static readonly BindableProperty StatusProperty = StatusPropertyKey.BindableProperty;

    private static readonly BindablePropertyKey DurationPropertyKey = BindableProperty.CreateReadOnly( nameof(Duration), typeof(TimeSpan), typeof(DXVideo), TimeSpan.Zero, propertyChanged: (bindable, oldValue, newValue) => ((DXVideo)bindable).SetTimeToEnd() );
    public static readonly BindableProperty DurationProperty = DurationPropertyKey.BindableProperty;
    
    private static readonly BindablePropertyKey TimeToEndPropertyKey = BindableProperty.CreateReadOnly( nameof(TimeToEnd), typeof(TimeSpan), typeof(DXVideo), TimeSpan.Zero );
    public static readonly BindableProperty TimeToEndProperty = TimeToEndPropertyKey.BindableProperty;
    
    public static readonly BindableProperty SpeedProperty = BindableProperty.Create( nameof(Speed), typeof(float), typeof(DXVideo) );
    public static readonly BindableProperty MutedProperty = BindableProperty.Create( nameof(Muted), typeof(bool), typeof(DXVideo) );
    
    #endregion

    /* Events */
    #region Events

    public event EventHandler UpdateStatus;
    public event EventHandler<DXVideoPositionEventArgs> PlayRequested;
    public event EventHandler<DXVideoPositionEventArgs> PauseRequested;
    public event EventHandler<DXVideoPositionEventArgs> StopRequested;

    #endregion

    /* Fields */
    private readonly IDispatcherTimer _timer;

    /* Methods */
    public DXVideo()
    {
        BackgroundColor = Colors.Black;

        HorizontalOptions = LayoutOptions.Fill;
        VerticalOptions = LayoutOptions.Fill;
        
        IsStopped = true;
        
        _timer = Dispatcher.CreateTimer();
        _timer.Interval = TimeSpan.FromMilliseconds( 100 );
        _timer.Tick += OnTimerTick;
        _timer.Start();
    }
    
    ~DXVideo() => _timer.Tick -= OnTimerTick;
    
    // Starts video playback
    public void Play()
    {
        DXVideoPositionEventArgs args = new( Position );
        PlayRequested?.Invoke( this, args );
        Handler?.Invoke( nameof(PlayRequested), args );
    }

    // Pause video playback
    public void Pause()
    {
        DXVideoPositionEventArgs args = new( Position );
        PauseRequested?.Invoke( this, args );
        Handler?.Invoke( nameof(PauseRequested), args );
    }

    // Stops video playback
    public void Stop()
    {
        IsStopped = true;
        
        DXVideoPositionEventArgs args = new( Position );
        StopRequested?.Invoke( this, args );
        Handler?.Invoke( nameof(StopRequested), args );
    }

    // Updates time remaining
    private void SetTimeToEnd()
    {
        TimeToEnd = (Duration - Position);
    }

    /* Event Callbacks */
    
    // Updates status on each timer tick
    private void OnTimerTick( object sender, EventArgs e )
    {
        // Ready for playback
        if ( IsStopped && (Status == VideoStatus.Playing) )
        {
            Ready?.Invoke();

            IsStopped = false;
        }
        
        UpdateStatus?.Invoke( this, EventArgs.Empty );
        Handler?.Invoke( nameof(UpdateStatus) );
    }
}

//
