using ManagedBass;
using System.ComponentModel;

namespace Harmonia.Core.Engine;

public class BassAudioEngine : IAudioEngine, IDisposable
{
    private readonly BaseMediaPlayer _mediaPlayer;

    private CancellationTokenSource? _cancellationTokenSource;

    public event EventHandler<PlaybackStoppedEventArgs>? PlaybackStopped;
    public event EventHandler? StreamFinished;
    public event EventHandler<PlaybackStateChangedEventArgs>? StateChanged;

    public string? Source { get; private set; }
    public string[] SupportedFormats { get; }

    public TimeSpan Position
    {
        get
        {
            return _mediaPlayer.Position;
        }
        set
        {
            _mediaPlayer.Position = value;
        }
    }

    public TimeSpan Length => _mediaPlayer.Duration;

    private float _volume;
    public float Volume
    {
        get
        {
            return _volume;
        }
        set
        {
            var newVolume = value;

            if (newVolume > 1.0)
                newVolume = 1.0f;
            else if (newVolume < 0.0)
                newVolume = 0.0f;

            _isMuted = false;
            _volume = newVolume;
            _mediaPlayer.Volume = newVolume;
        }
    }

    public bool CanPause => State == AudioPlaybackState.Playing;

    private bool _isMuted;
    public bool IsMuted
    {
        get
        {
            return _isMuted;
        }
        set
        {
            _isMuted = value;
            _mediaPlayer.Volume = value ? 0 : Volume;
        }
    }

    private AudioPlaybackState _state;
    public AudioPlaybackState State
    {
        get
        {
            return _state;
        }
        private set
        {
            AudioPlaybackState oldValue = _state;
            _state = value;

            StateChanged?.Invoke(this, new(oldValue, value));
        }
    }

    public BassAudioEngine()
    {
        BassLoader.Initialize();

        _mediaPlayer = new MediaPlayer();
        _mediaPlayer.MediaLoaded += OnMediaLoaded;
        _mediaPlayer.MediaFailed += OnPlaybackStopped;
        _mediaPlayer.MediaEnded += OnStreamFinished;
        _mediaPlayer.PropertyChanged += OnMediaPlayerPropertyChanged;

        List<string> supportedFormats = [.. Bass.SupportedFormats.Split(';')];
        //supportedFormats.Add(".aac");
        //supportedFormats.Add(".m4a");
        supportedFormats.Add("*.flac");
        //supportedFormats.Add(".opus");
        //supportedFormats.Add(".wma");

        SupportedFormats = [.. supportedFormats];

        IsMuted = false;
        Volume = 1;
    }

    public async Task<bool> LoadAsync(string fileName)
    {
        if (fileName == Source)
        {
            Position = TimeSpan.Zero;
            return true;
        }

        if (File.Exists(fileName) == false)
            return false;

        if (await LoadWaveSourceAsync(fileName) == false)
            return false;

        try
        {
            UpdateSource(fileName);
        }
        catch (Exception)
        {
            return false;
        }

        return true;
    }

    private async Task<bool> LoadWaveSourceAsync(string fileName)
    {
        _cancellationTokenSource?.Cancel();
        _cancellationTokenSource = new CancellationTokenSource();

        CancellationToken token = _cancellationTokenSource.Token;

        try
        {
            await _mediaPlayer.LoadAsync(fileName);
        }
        catch (Exception ex)
        {
            if (token.IsCancellationRequested)
                return false;

            //return new Result(State.Exception, ex.Message);
            throw new Exception("An error occurred - " + fileName, ex);
        }

        if (token.IsCancellationRequested)
            return false;

        return true;
    }

    private void UpdateSource(string fileName)
    {
        if (State != AudioPlaybackState.Stopped)
        {
            Stop();
        }

        Source = fileName;
    }

    public void Play()
    {
        bool result = _mediaPlayer.Play();

        if (!result)
        {
            if (Bass.LastError == Errors.Start)
            {
                Bass.Start();
                _mediaPlayer.Play();
            }
        }
    }

    public void Pause()
    {
        _mediaPlayer.Pause();
    }

    public void Stop()
    {
        _mediaPlayer.Stop();
        Position = TimeSpan.Zero;
    }

    private void OnMediaLoaded(int obj)
    {
        if (_mediaPlayer.Volume != Volume)
            _mediaPlayer.Volume = Volume;
    }

    private void OnPlaybackStopped(object? sender, EventArgs args)
    {
        PlaybackStoppedEventArgs eventArgs = new($"Playback stopped unexpectedly: Last Error = {Bass.LastError}");
        PlaybackStopped?.Invoke(sender, eventArgs);
    }

    private void OnStreamFinished(object? sender, EventArgs e)
    {
        StreamFinished?.Invoke(sender, e);
    }

    private void OnMediaPlayerPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "State":
                State = GetPlaybackState(_mediaPlayer.State);
                break;
        }
    }

    private static AudioPlaybackState GetPlaybackState(PlaybackState playbackState)
    {
        return playbackState switch
        {
            PlaybackState.Stopped => AudioPlaybackState.Stopped,
            PlaybackState.Playing => AudioPlaybackState.Playing,
            PlaybackState.Stalled => AudioPlaybackState.Stalled,
            PlaybackState.Paused => AudioPlaybackState.Paused,
            _ => throw new Exception($"Unknown audio playback state: {playbackState}")
        };
    }

    public void Dispose()
    {
        _mediaPlayer.Dispose();
        GC.SuppressFinalize(this);
    }
}