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

#nullable enable

using AVKit;
using AVFoundation;

using UIKit;
using CoreMedia;
using Foundation;

using DXLib.Video;
using DXLib.Video.Source;

namespace iStatVball3.Platforms.iOS.DXLib.Video;

/*
 * iOS native implementation of video player API based on AVPlayer.
 */
public sealed class DXVideoPlayer : UIView
{
    /* Fields */
    private AVPlayerItem? _playerItem;
    private readonly AVPlayer _player;
    private readonly AVPlayerViewController _playerViewController;
    
    private DXVideo _video;
    private NSObject? _playedToEndObserver;

    /* Methods */
    public DXVideoPlayer( DXVideo video )
    {
        _video = video;

        _playerViewController = new AVPlayerViewController();
        _player = new AVPlayer();
        
        _playerViewController.Player = _player;
        _playerViewController.View!.Frame = Bounds;

        #if IOS16_0_OR_GREATER
            // iOS 16 Shell-based apps, AVPlayerViewController must be added to parent ViewController, otherwise transport controls will not display
            var viewController = WindowStateManager.Default.GetCurrentUIViewController();
    
            // If no VC, assume not Shell, continue as transport controls will still be displayed
            if ( viewController?.View is not null )
            {
                // Zero out safe area insets of AVPlayerViewController
                UIEdgeInsets insets = viewController.View.SafeAreaInsets;
                _playerViewController.AdditionalSafeAreaInsets = new UIEdgeInsets( insets.Top * -1, insets.Left, insets.Bottom * -1, insets.Right );
    
                // Add View from AVPlayerViewController to parent ViewController
                viewController.View.AddSubview( _playerViewController.View );
            }
        #endif
        
        // Use View from AVPlayerViewController as native control
        AddSubview( _playerViewController.View );
    }

    // Dispose all native resources
    protected override void Dispose( bool disposing )
    {
        if ( disposing )
        {
            DestroyPlayedToEndObserver();
            
            _player.ReplaceCurrentItemWithPlayerItem( null );
            _player.Dispose();
            _playerViewController.Dispose();

            _video = null!;
        }

        base.Dispose( disposing );
    }

    // Time conversion utility
    private static TimeSpan ConvertTime( CMTime cmTime )
    {
        return TimeSpan.FromSeconds( double.IsNaN( cmTime.Seconds ) ? 0 : cmTime.Seconds );
    }

    // PROPERTY HANDLING
    public void UpdateShowControls()
    {
        _playerViewController.ShowsPlaybackControls = _video.ShowControls;
    }

    public void UpdateSource()
    {
        AVAsset? asset = null;

        switch ( _video.Source )
        {
            // URI
            case DXUriVideoSource source:
            {
                string uri = source.Uri;

                if ( !string.IsNullOrWhiteSpace( uri ) )
                {
                    asset = AVAsset.FromUrl( new NSUrl( uri ) );
                }

                break;
            }
            // File
            case DXFileVideoSource source:
            {
                string uri = source.File;

                if ( !string.IsNullOrWhiteSpace( uri ) )
                {
                    asset = AVAsset.FromUrl( NSUrl.CreateFileUrl( [ uri ] ) );
                }

                break;
            }
            // Resource
            case DXResourceVideoSource source:
            {
                string path = source.Path;
            
                if ( !string.IsNullOrWhiteSpace( path ) )
                {
                    string directory = Path.GetDirectoryName( path )!;
                    string filename = Path.GetFileNameWithoutExtension( path );
                    string extension = Path.GetExtension( path )[ 1.. ];
                
                    NSUrl url = NSBundle.MainBundle.GetUrlForResource( filename, extension, directory );
                    asset = AVAsset.FromUrl( url );
                }

                break;
            }
        }

        _playerItem = (asset != null) ? new AVPlayerItem( asset ) : null;
        _player.ReplaceCurrentItemWithPlayerItem( _playerItem );
        
        if ( _playerItem != null && _video.AutoPlay )
        {
            _player.Play();
        }
    }

    public void UpdateIsLooping()
    {
        DestroyPlayedToEndObserver();
        
        if ( _video.IsLooping )
        {
            _player.ActionAtItemEnd = AVPlayerActionAtItemEnd.None;
            _playedToEndObserver = NSNotificationCenter.DefaultCenter.AddObserver( AVPlayerItem.DidPlayToEndTimeNotification, PlayedToEnd );
        }
        else
        {
            _player.ActionAtItemEnd = AVPlayerActionAtItemEnd.Pause;
        }
    }

    public void UpdatePosition()
    {
        TimeSpan controlPosition = ConvertTime( _player.CurrentTime );
        
        if ( Math.Abs( (controlPosition - _video.Position).TotalSeconds ) > 1 )
        {
            _player.Seek( CMTime.FromSeconds( _video.Position.TotalSeconds, 1 ) );
        }
    }

    public void UpdateSpeed()
    {
        _player.Rate = _video.Speed;
    }
    
    public void UpdateStatus()
    {
        DXVideo.VideoStatus videoStatus = DXVideo.VideoStatus.NotReady;

        switch ( _player.Status )
        {
            case AVPlayerStatus.ReadyToPlay:
            {
                videoStatus = _player.TimeControlStatus switch
                {
                    AVPlayerTimeControlStatus.Playing => DXVideo.VideoStatus.Playing,
                    AVPlayerTimeControlStatus.Paused => DXVideo.VideoStatus.Paused,
                    
                    _ => videoStatus
                };

                break;
            }
            default: break;
        }

        ((DXVideoController)_video).Status = videoStatus;

        if ( _playerItem != null )
        {
            TimeSpan duration = ConvertTime( _playerItem.Duration );
            TimeSpan position = ConvertTime( _playerItem.CurrentTime );
            
            ((DXVideoController)_video).Duration = duration;
            _video.Position = position;

            // MUST set ended state here
            if ( (duration > TimeSpan.Zero) && (duration.Subtract( position ) <= TimeSpan.Zero) )
            {
                ((DXVideoController)_video).Status = DXVideo.VideoStatus.Ended;
            }
        }
    }

    // COMMAND HANDLING
    public void PlayRequested( TimeSpan position )
    {
        _player.Play();
    }

    public void PauseRequested( TimeSpan position )
    {
        _player.Pause();
    }

    public void StopRequested( TimeSpan position )
    {
        _player.Pause();
        _player.Seek( new CMTime( 0, 1 ) );
    }

    // EVENT HANDLING
    private void PlayedToEnd( NSNotification notification )
    {
        if ( !notification.Object!.Equals( _playerViewController.Player?.CurrentItem ) )
        {
            return;
        }

        _playerViewController.Player?.Seek( CMTime.Zero );
    }

    private void DestroyPlayedToEndObserver()
    {
        if ( _playedToEndObserver != null )
        {
            NSNotificationCenter.DefaultCenter.RemoveObserver( _playedToEndObserver );
            DisposeObserver( ref _playedToEndObserver );
        }
    }

    private static void DisposeObserver( ref NSObject? disposable )
    {
        disposable?.Dispose();
        disposable = null;
    }
}

//
