using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Linq;
using Xamarin.Forms;
using Aurora.Services;
using Aurora.Proto.General;
using Aurora.Proto.Party;
using Aurora.Proto.Events;
using Aurora.Services.ClientService;
using Aurora.Services.ClientService.Events;
using Aurora.Services.PlayerService;
using Aurora.Services.EventManager;
using Aurora.Models.Media;

namespace Aurora.Design.Views.Party
{
    //TODO refactor
    enum PartyState
    {
        SelectingHost,
        InParty,
        Hosting,
        Connecting,
    }

    public class PartyViewModel : BaseViewModel
    {
        private PartyState _state;
        private string _hostname;
        private ObservableCollection<PartyMember> _members;
        private ObservableCollection<BaseMedia> _queue;
        private BaseMedia _selectedMedia;
        private PlayerService _player;
        private ClientService _client;

        public PartyViewModel()
        {
            this.JoinCommand = new Command(OnJoinExecute, CanJoinExecute);
            this.HostCommand = new Command(OnHostExecute, CanHostExecute);

            _members = new ObservableCollection<PartyMember>();
            _queue = new ObservableCollection<BaseMedia>();

            SetState(PartyState.SelectingHost);
            this._player = PlayerService.Instance;

            PlayCommand = new Command(OnDoubleClickExecute, CanDoubleClickExecute);

            _client = ClientService.Instance;

            _client.OnMediaPaused += this.OnMediaPaused;
            _client.OnMediaResumed += this.OnMediaResumed;
            _client.OnNewMediaPlaying += this.OnNewMediaPlaying;
            _client.OnPartyMemberJoined += this.OnPartyMemberJoined;
            _client.OnPartyMemberLeft += this.OnPartyMemberLeft;
        }

        ~PartyViewModel()
        {
            //Task.Run(ServerService.Instance.Stop);
        }

        #region Properties

        /// <summary>
        /// Publc property for the members list
        /// </summary>
        /// <value></value>
        public ObservableCollection<PartyMember> Members
        {
            get
            {
                return _members;
            }
            set
            {
                SetProperty(ref _members, value);
            }
        }

        public bool IsSelectingHost
        {
            get { return _state == PartyState.SelectingHost; }
        }

        /// <summary>
        /// Public property indicating the state.
        /// </summary>
        /// <value></value>
        public bool IsNotSelectingHost
        {
            get { return _state != PartyState.SelectingHost; }
        }

        /// <summary>
        /// Public property for queue item source
        /// </summary>
        /// <value></value>
        public ObservableCollection<BaseMedia> Queue
        {
            get
            {
                return _queue;
            }
            set
            {
                if (value != _queue)
                {
                    SetProperty(ref _queue, value);
                }
            }
        }



        /// <summary>
        /// Public property for the hostname bindable field
        /// </summary>
        /// <value></value>
        public string Hostname
        {
            get { return _hostname; }
            set { SetProperty(ref _hostname, value); }
        }

        /// <summary>
        /// Public property for the currently selected song.
        /// </summary>
        /// <value></value>
        public BaseMedia SelectedSong
        {
            get { return _selectedMedia; }
            set { SetProperty(ref _selectedMedia, value); }
        }

        /// <summary>
        /// Public property for playing media
        /// </summary>
        /// <value></value>
        public Command PlayCommand { get; private set; }

        /// <summary>
        /// Public property for join command
        /// </summary>
        /// <value></value>
        public Command JoinCommand { get; set; }

        /// <summary>
        /// Pubic property for host command
        /// </summary>
        /// <value></value>
        public Command HostCommand { get; set; }

        #endregion Properties

        #region Events
        /// <summary>
        /// Called by framework when view becomes active
        /// </summary>
        /// <returns></returns>
        public override async Task OnActive()
        {
            if (this._state == PartyState.Hosting ||
                this._state == PartyState.InParty)
            {
                await _client.GetEvents().ConfigureAwait(false);
            }
        }

        /// <summary>
        /// Called by framework when view becomes inactive
        /// </summary>
        /// <returns></returns>
        public override Task OnInactive()
        {
            _client.StopEvents();
            return Task.FromResult<object>(null);
        }

        /// <summary>
        /// Remote media paused event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void OnMediaPaused(object sender, MediaPausedEventArgs args)
        {
            StopPlaying();
        }

        /// <summary>
        /// Remote playing new media event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void OnNewMediaPlaying(object sender, NewMediaPlayingEventArgs args)
        {
            PlayFromBeginning(GetMediaFromQueue(args.Event.Media.Id));
        }

        /// <summary>
        /// Remote resumed playing event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void OnMediaResumed(object sender, MediaResumedEventArgs args)
        {
            PlayResume();
        }

        /// <summary>
        /// Member joined party event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void OnPartyMemberJoined(object sender, PartyMemberJoinedEventArgs args)
        {
            PartyMember member = new PartyMember
            {
                UserName = args.Event.Member.UserName,
                Id = args.Event.Member.Id,
                IpAddress = args.Event.Member.IpAddress,
                Port = args.Event.Member.Port
            };

            Members.Add(member);
        }

        /// <summary>
        /// Member left party event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void OnPartyMemberLeft(object sender, PartyMemberLeftEventArgs args)
        {
            var found = Members.Where(x => x.Id == args.Event.Member.Id);
            foreach (PartyMember member in found)
            {
                _members.Remove(member);
            }
        }

        #endregion Events

        #region Commands
        private async void OnJoinExecute()
        {
            SetState(PartyState.Connecting);
            _client.Start(Hostname, SettingsService.Instance.DefaultPort.ToString());
            await JoinParty(false);

            //TODO add cancellation token
            try
            {
                SetState(PartyState.InParty);
                await _client.GetEvents().ConfigureAwait(true);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception occurred while receiviing events: ", ex.Message);
            }
        }

        private bool CanJoinExecute()
        {
            return true;
        }

        private async void OnHostExecute()
        {
            //Change state
            SetState(PartyState.Connecting);
            ServerService.Instance.Start();
            string localHost = ServerService.GetLocalIPAddress();
            _client.IsHost = true;
            _client.Start(localHost, SettingsService.Instance.DefaultPort.ToString());
            await JoinParty(true);


            //TODO add cancellation token
            try
            {
                SetState(PartyState.Hosting);
                await _client.GetEvents().ConfigureAwait(true);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception occurred while receiviing events: ", ex.Message);
            }
        }

        private bool CanHostExecute()
        {
            return true;
        }

        public override void OnPlayButtonExecute()
        {
            switch (_player.PlaybackState)
            {
                case PlaybackState.Buffering:
                    {
                        //Fire play resume event
                        AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
                        MediaResumedEvent mediaResumed = new MediaResumedEvent();

                        EventManager.Instance.FireEvent(new BaseEvent()
                        {
                            MediaResumedEvent = mediaResumed
                        });

                        break;
                    }
                case PlaybackState.Playing:
                    {
                        //Fire play stopped event
                        AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
                        MediaPausedEvent mediaPaused = new MediaPausedEvent();

                        EventManager.Instance.FireEvent(new BaseEvent()
                        {
                            MediaPausedEvent = mediaPaused
                        });

                        break;
                    }
            }
        }

        public override bool CanPlayButtonExecute()
        {
            return this._state == PartyState.Hosting && this._player.IsLoaded;
        }

        public override bool CanNextButtonExecute()
        {
            return this._state == PartyState.Hosting;
        }

        public override bool CanPreviousButtonExecute()
        {
            return this._state == PartyState.Hosting;
        }

        /// <summary>
        /// On double click execute, fire media playing event
        /// </summary>
        public void OnDoubleClickExecute()
        {
            //Fire Playing event
            AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
            NewMediaPlayingEvent mediaPlaying = new NewMediaPlayingEvent()
            {
                Media = new RemoteMediaData()
                {
                    Id = _selectedMedia.Id,
                    Title = meta.Title,
                    Artist = meta.Artist,
                    Album = meta.Album,
                }
            };

            EventManager.Instance.FireEvent(new BaseEvent()
            {
                NewMediaPlayingEvent = mediaPlaying
            });
        }

        public bool CanDoubleClickExecute()
        {
            return this._state == PartyState.Hosting;
        }


        #endregion Commands

        #region Private Methods
        /// <summary>
        /// Join the remote party.
        /// </summary>
        /// <returns></returns>
        private async Task JoinParty(bool asHost)
        {
            try
            {
                JoinPartyResponse resp = await _client.RemotePartyClient.JoinPartyAsync(new JoinPartyRequest
                {
                    UserName = SettingsService.Instance.Username,
                });

                SettingsService.Instance.ClientId = resp.ClientId;

                RefreshMembers();

                //Subscribe to events
                await SubscribeToEvents();

                QueueResponse queueResponse = _client.RemotePartyClient.GetQueue(new Empty());

                Queue.Clear();
                //Convert received data to remote audio models
                foreach (RemoteMediaData data in queueResponse.MediaList)
                {
                    //Assign received metadata (since this can't be aquired from a file)
                    AudioMetadata meta = new AudioMetadata();
                    meta.Title = data.Title;
                    meta.Album = data.Album;
                    meta.Artist = data.Artist;
                    meta.Duration = data.Duration;

                    RemoteAudio remote = new RemoteAudio(data.Id,
                        asHost,
                        meta,
                        _client.RemotePlaybackClient,
                        _client.RemoteSyncClient);

                    Queue.Add(remote);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error subscribing to events: " + ex.Message);
            }
        }

        private async Task LeaveParty()
        {
            //Stop receiving events
            _client.StopEvents();

            //Unsubscribe
            await UnsubscribeFromEvents();

            //Leave party
            LeavePartyRequest leaveReq = new LeavePartyRequest();
            await _client.RemotePartyClient.LeavePartyAsync(leaveReq);
        }

        private async Task SubscribeToEvents()
        {
            SubscribeRequest req = new SubscribeRequest();
            req.EventTypes.Add(EventType.PartyMemberJoined);
            req.EventTypes.Add(EventType.PartyMemberLeft);
            req.EventTypes.Add(EventType.MediaPlaying);
            req.EventTypes.Add(EventType.MediaStopped);
            if (!string.IsNullOrWhiteSpace(SettingsService.Instance.ClientId))
            {
                req.ClientId = SettingsService.Instance.ClientId;
            }


            Console.WriteLine(string.Format("CLIENT {0} - SubscribeToEvents called from client with id", SettingsService.Instance.ClientId));
            await _client.RemoteEventClient.SubscribeToEventsAsync(req);
        }
        private async Task UnsubscribeFromEvents()
        {
            UnsubscribeAllRequest unsubscribeReq = new UnsubscribeAllRequest();
            await _client.RemoteEventClient.UnsubscribeFromAllAsync(unsubscribeReq);
        }

        /// <summary>
        /// Refresh members list.
        /// </summary>
        private void RefreshMembers()
        {
            Members.Clear();
            MembersResponse response = _client.RemotePartyClient.GetPartyMembers(new Empty());
            //Add members
            foreach (PartyMember member in response.Members)
            {
                Members.Add(member);
            }
        }

        private void SetState(PartyState state)
        {
            _state = state;
            OnPropertyChanged("IsSelectingHost");
            OnPropertyChanged("IsNotSelectingHost");
        }

        private BaseMedia GetMediaFromQueue(string Id)
        {
            if (_queue.Any((BaseMedia media) => media.Id == Id))
            {
                BaseMedia media = _queue.First((BaseMedia med) => med.Id == Id);
                return media;
            }
            else
            {
                return null;
            }
        }
        private async void PlayFromBeginning(BaseMedia args)
        {
            base.Media = args;
            await _player.LoadMedia(base.Media).ConfigureAwait(true);
            _player.Play();
        }

        private void PlayResume()
        {
            _player.Play();
        }

        private void StopPlaying()
        {
            _player.Pause();
        }

        #endregion Private Methods
    }
}