using Serilog;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using TINK.ViewModel.Bikes.Bike;
using TINK.ViewModel.Bikes.Bike.BC;
using Plugin.Permissions.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using TINK.MultilingualResources;
using TINK.Model.Device;

namespace TINK.ViewModel.Bikes
{
    public abstract class BikesViewModel : ObservableCollection<BikeViewModelBase>, IBikesViewModel
    {
        /// <summary> Provides info about the smart device (phone, tablet, ...).</summary>
        protected ISmartDevice SmartDevice;
        
        /// <summary>
        /// Reference on view servcie to show modal notifications and to perform navigation.
        /// </summary>
        protected IViewService ViewService { get; }

        /// <summary>
        /// Holds the exception which occurred getting bikes occupied information.
        /// </summary>
        private Exception m_oException;

        /// <summary> Provides an connector object.</summary>
        protected Func<bool, IConnector> ConnectorFactory { get; }

        protected IGeolocation Geolocation { get; }

        /// <summary> Provides an connector object.</summary>
        protected ILocksService LockService { get; }

        /// <summary> Delegate to retrieve connected state. </summary>
        protected Func<bool> IsConnectedDelegate { get; }

        /// <summary>Holds whether to poll or not and the periode leght is polling is on.</summary>
        private TINK.Settings.PollingParameters m_oPolling;

        /// <summary> Object to manage update of view model objects from Copri.</summary>
        protected IPollingUpdateTaskManager m_oViewUpdateManager;

        /// <summary> Action to post to GUI thread.</summary>
        public Action<SendOrPostCallback, object> PostAction { get; }

        /// <summary>Enables derived class to fire property changed event. </summary>
        /// <param name="p_oEventArgs"></param>
        protected override void OnPropertyChanged(PropertyChangedEventArgs p_oEventArgs) => base.OnPropertyChanged(p_oEventArgs);

        /// <summary>
        /// Handles events from bike viewmodel which require GUI updates.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnBikeRequestHandlerPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChanged(e);
        }

        /// <summary>
        /// Instantiates a new item.
        /// </summary>
        Func<IInUseStateInfoProvider> m_oItemFactory;

        /// <summary>
        /// Constructs bike collection view model.
        /// </summary>
        /// </param>
        /// <param name="p_oUser">Mail address of active user.</param>
        /// <param name="isReportLevelVerbose">True if report level is verbose, false if not.</param>
        /// <param name="permissions">Holds object to query location permisions.</param>
        /// <param name="bluetoothLE">Holds object to query bluetooth state.</param>
        /// <param name="runtimPlatform">Specifies on which platform code is run.</param>
        /// <param name="isConnectedDelegate">Returns if mobile is connected to web or not.</param>
        /// <param name="connectorFactory">Connects system to copri for purposes of requesting a bike/ cancel request.</param>
        /// <param name="lockService">Service to control lock retrieve info.</param>
        /// <param name="p_oPolling"> Holds whether to poll or not and the periode leght is polling is on. </param>
        /// <param name="postAction">Executes actions on GUI thread.</param>
        /// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
        /// <param name="p_oViewService">Interface to actuate methodes on GUI.</param>
        public BikesViewModel(
            User user,
            IPermissions permissions,
            IBluetoothLE bluetoothLE,
            string runtimPlatform,
            Func<bool> isConnectedDelegate,
            Func<bool, IConnector> connectorFactory,
            IGeolocation geolocation,
            ILocksService lockService,
            TINK.Settings.PollingParameters polling,
            Action<SendOrPostCallback, object> postAction,
            ISmartDevice smartDevice,
            IViewService viewService,
            Func<IInUseStateInfoProvider> itemFactory)
        {
            User = user
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No user available.");
                        
            RuntimePlatform = runtimPlatform
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No runtime platform information available.");

            PermissionsService = permissions
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No permissions available.");

            BluetoothService = bluetoothLE
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No bluetooth available.");

            ConnectorFactory = connectorFactory
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No connector available.");

            Geolocation = geolocation
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No geolocation object available.");

            LockService = lockService
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No lock service object available.");

            IsConnectedDelegate = isConnectedDelegate
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No is connected delegate available.");

            m_oItemFactory = itemFactory
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No factory member available.");

            PostAction = postAction
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No post action available.");

            SmartDevice = smartDevice
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No smart device object available.");

            ViewService = viewService
                ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No view available.");

            m_oViewUpdateManager = new IdlePollingUpdateTaskManager();

            BikeCollection = new BikeCollectionMutable();

            BikeCollection.CollectionChanged += OnDecoratedCollectionChanged;

            m_oPolling = polling;

            isConnected = IsConnectedDelegate();

            CollectionChanged += (sender, eventargs) =>
            {
                // Notify about bikes occuring/ vanishing from list.
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsBikesListVisible)));
            };
        }

        /// <summary>
        /// Is invoked if decorated bike collection changes. 
        /// Collection of view model objects has to be synced.
        /// </summary>
        /// <param name="p_oSender">Sender of the event.</param>
        /// <param name="p_oEventArgs">Event arguments.</param>
        private void OnDecoratedCollectionChanged(object p_oSender, System.Collections.Specialized.NotifyCollectionChangedEventArgs p_oEventArgs)
        {
            switch (p_oEventArgs.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:

                    // New bike avaialbe (new arrived at station or bike wased booked on a different device)
                    foreach (var l_oBike in BikeCollection)
                    {
                        if (!Contains(l_oBike.Id))
                        {
                            var bikeViewModel = BikeViewModelFactory.Create(
                                IsConnectedDelegate,
                                ConnectorFactory,
                                Geolocation,
                                LockService,
                                (id) => Remove(id),
                                () => m_oViewUpdateManager,
                                SmartDevice,
                                ViewService,
                                l_oBike,
                                User,
                                m_oItemFactory(),
                                this);

                            bikeViewModel.PropertyChanged += OnBikeRequestHandlerPropertyChanged;

                            Add(bikeViewModel);
                        }
                    }

                    break;

                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:

                    // Bike was removed (either a different user requested/ booked a bike or request expired or bike was returned.)
                    foreach (BikeViewModelBase l_oBike in Items)
                    {
                        if (!BikeCollection.ContainsKey(l_oBike.Id))
                        {
                            l_oBike.PropertyChanged -= OnBikeRequestHandlerPropertyChanged;

                            Remove(l_oBike);
                            break;
                        }
                    }

                    break;
            }
        }

        /// <summary> All bikes to be displayed. </summary>
        protected BikeCollectionMutable BikeCollection { get; private set; }

        protected User User { get; private set; }

#if USCSHARP9
        public bool IsReportLevelVerbose { get; init; }
#else
        public bool IsReportLevelVerbose { get; set; }
#endif

        /// <summary> Specified whether code is run under iOS or Android.</summary>
        protected string RuntimePlatform { get; private set; }

        /// <summary>
        /// Service to manage permissions (location) of the app.
        /// </summary>
        protected IPermissions PermissionsService { get; private set; }

        /// <summary>
        /// Service to manage bluetooth stack.
        /// </summary>
        protected IBluetoothLE BluetoothService { get; private set; }

        /// <summary>
        /// User which is logged in.
        /// </summary>
        public User ActiveUser
        {
            get
            {
                return User;
            }
        }

        /// <summary>
        /// Exception which occurred getting bike information.
        /// </summary>
        protected Exception Exception
        {
            get
            {
                return m_oException;
            }

            set
            {
                var l_oException = m_oException;
                var statusInfoText = StatusInfoText;
                m_oException = value;
                if ((m_oException != null && l_oException == null)
                    || (m_oException == null && l_oException != null))
                {
                    // Because an error occurred non error related info must be hidden.
                    OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsIdle)));
                    OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsBikesListVisible)));
                }

                if (statusInfoText != StatusInfoText)
                {
                    OnPropertyChanged(new PropertyChangedEventArgs(nameof(StatusInfoText)));
                }
            }
        }

        /// <summary>
        /// Bike selected in list of bikes, null if no bike is selected.
        /// Binds to GUI.
        /// </summary>
        public BikeViewModelBase SelectedBike { get; set; }

        /// <summary>
        /// True if bikes list has to be displayed.
        /// </summary>
        public bool IsBikesListVisible
        {
            get
            {
                // If an exception occurred there is no information about occupied bikes available.
                return Count > 0;
            }
        }

        /// <summary> Used to block more than on copri requests at a given time.</summary>
        private bool isIdle = false;

        /// <summary>
        /// True if any action can be performed (request and cancel request)
        /// </summary>
        public virtual bool IsIdle
        {
            get => isIdle;
            set
            {
                if (value == isIdle)
                    return;

                Log.ForContext<BikesViewModel>().Debug($"Switch value of {nameof(IsIdle)} to {value}.");
                isIdle = value;
                base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsIdle)));
                base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsRunning)));
            }
        }

        public bool IsRunning => !isIdle;

        /// <summary> Holds info about current action. </summary>
        private string actionText;

        /// <summary> Holds info about current action. </summary>
        public string ActionText
        {
            get => actionText;
            set
            {
                var statusInfoText = StatusInfoText;
                actionText = value;

                if (statusInfoText == StatusInfoText)
                {
                    // Nothing to do because value did not change.
                    Log.ForContext<BikeViewModel>().Debug($"Property {nameof(ActionText)} set to value \"{actionText}\" but {nameof(StatusInfoText)} did not change.");
                    return;
                }

                Log.ForContext<BikeViewModel>().Debug($"Property {nameof(ActionText)} set to value \"{actionText}\" .");
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(StatusInfoText)));
            }
        }

        /// <summary> Holds information whether app is connected to web or not. </summary>
        protected bool? isConnected = null;

        /// <summary>Exposes the is connected state. </summary>
        protected bool IsConnected
        {
            get => isConnected ?? false;
            set
            {
                var statusInfoText = StatusInfoText;
                isConnected = value;
                if (statusInfoText == StatusInfoText)
                {
                    // Nothing to do.
                    return;
                }

                OnPropertyChanged(new PropertyChangedEventArgs(nameof(StatusInfoText)));
            }
        }

        /// <summary> Holds the status information text. </summary>
        public string StatusInfoText
        {
            get
            {
                if (Exception != null)
                {
                    // An error occurred getting data from copri.
                    return IsReportLevelVerbose
                        ? Exception.GetShortErrorInfoText()
                        : AppResources.ActivityTextException;
                }

                if (!IsConnected)
                {
                    return AppResources.ActivityTextConnectionStateOffline;
                }

                return ActionText ?? string.Empty;
            }
        }

        /// <summary>
        /// Removes a bike view model by id.
        /// </summary>
        /// <param name="id">Id of bike to removed.</param>
        public void Remove(string id)
        {
            foreach (var bike in BikeCollection)
            {
                if (bike.Id == id)
                {
                    BikeCollection.Remove(bike);
                    return;
                }
            }
        }

        /// <summary>
        /// Gets whether a bike is contained in collection of bikes.
        /// </summary>
        /// <param name="id">Id of bike to check existance.</param>
        /// <returns>True if bike exists.</returns>
        private bool Contains(string id)
        {
            foreach (var l_oBike in Items)
            {
                if (l_oBike.Id == id)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Transforms bikes view model object to string.
        /// </summary>
        /// <returns></returns>
        public new string ToString()
        {
            var l_oToString = string.Empty;
            foreach (var item in Items)
            {
                l_oToString += item.ToString();
            }

            return l_oToString;
        }

        /// <summary>
        /// Invoked when page is shown. 
        /// Starts update process.
        /// </summary>
        /// <param name="p_oUpdateAction"> Update fuction passed as argument by child class.</param>
        protected async Task OnAppearing(Action p_oUpdateAction)
        {
            m_oViewUpdateManager = new PollingUpdateTaskManager(() => GetType().Name, p_oUpdateAction);

            try
            {
                // Update bikes at station or my bikes depending on context.
                await m_oViewUpdateManager.StartUpdateAyncPeridically(m_oPolling);
            }
            catch (Exception l_oExcetion)
            {
                Exception = l_oExcetion;
            }
        }

        /// <summary> 
        /// Invoked when page is shutdown. 
        /// Currently invoked by code behind, would be nice if called by XAML in future versions.
        /// </summary>
        public async Task OnDisappearing()
        {

            await m_oViewUpdateManager.StopUpdatePeridically();
        }
    }
}