using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;

namespace TINK.Model.Bike
{
    /// <summary> Holds entity of bikes. </summary>
    public class BikeCollectionMutable : ObservableCollection<BikeInfoMutable>, IBikeDictionaryMutable<BikeInfoMutable>
    {
        /// <summary> Constructs a mutable bike collection object. </summary>
        public BikeCollectionMutable()
        {
            SelectedBike = null;
        }

        /// <summary>
        /// Updates bikes dictionary from bikes response, i.e.
        /// - removes bikes which are no more contained in bikes response
        /// - updates state of all bikes from state contained in bikes response
        /// </summary>
        /// <param name="bikesAll"> Object holding bikes info from copri to update from.</param>
        /// <param name="p_oDateTimeProvider">Provices date time information.</param>
        public void Update(
            IEnumerable<BikeInfo> bikesAll)
        {
            // Get list of current bikes by state(s) to update. 
            // Needed to remove bikes which switched state and have to be removed from collection.
            var bikesToBeRemoved = this.Select(x => x.Id).ToList();

            foreach (var bikeInfo in (bikesAll ?? new List<BikeInfo>()))
            {
                /// Check if bike has to be added to list of existing station.
                if (ContainsKey(bikeInfo.Id) == false)
                {
                    // Bike does not yet exist in list of bikes.
                    Add(BikeInfoMutableFactory.Create(bikeInfo));
                    continue;
                }

                // Update bike.
                GetById(bikeInfo.Id).State.Load(bikeInfo.State);

                if (bikesToBeRemoved.Contains<string>(bikeInfo.Id))
                {
                    // Remove list from obsolete list.
                    bikesToBeRemoved.Remove(bikeInfo.Id);
                }
            }

            // Remove obsolete bikes.
            foreach (var l_oId in bikesToBeRemoved)
            {
                RemoveById(l_oId);
            }
        }

        /// <summary>
        /// Adds a new bike to collecion of bike.
        /// </summary>
        /// <param name="p_oNewBike">New bike to add.</param>
        /// <exception cref="Exception">Thrown if bike is not unique.</exception>
        public new void Add(BikeInfoMutable p_oNewBike)
        {
            // Ensure that bike id of new bike is is unique
            foreach (BikeInfoMutable l_oBike in Items)
            {
                if (l_oBike.Id == p_oNewBike.Id)
                {
                    throw new Exception(string.Format("Can not add bike with {0} to collection ob bike. Id is not unnique.", p_oNewBike));
                }
            }

            base.Add(p_oNewBike);
        }
       
        /// <summary>
        /// Bike selected by user for regerving or cancel reservation.
        /// </summary>
        public BikeInfoMutable SelectedBike
        {
            get;
            private set;
        }

        public void SetSelectedBike(string id)
        {
            SelectedBike = GetById(id);
        }

        /// <summary>
        /// Gets a bike by its id.  
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public BikeInfoMutable GetById(string id)
        {
            {
                return this.FirstOrDefault(bike => bike.Id == id);
            }
        }

        /// <summary>
        /// Deteermines whether a bike by given key exists.
        /// </summary>
        /// <param name="p_strKey">Key to check.</param>
        /// <returns>True if bike exists.</returns>
        public bool ContainsKey(string id)
        {
            return GetById(id) != null;
        }

        /// <summary>
        /// Removes a bike by its id.
        /// </summary>
        /// <param name="id">Id of bike to be removed.</param>
        public void RemoveById(string id)
        {
            var l_oBike = GetById(id);
            if (l_oBike == null)
            {
                // Nothing to do if bike does not exists.
                return;
            }

            Remove(l_oBike);
        }

        /// <summary>
        /// Create mutable objects from immutable objects.
        /// </summary>
        private static class BikeInfoMutableFactory
        {
            public static BikeInfoMutable Create(BikeInfo bikeInfo)
            {
                return (bikeInfo is BluetoothLock.BikeInfo bluetoothLockBikeInfo)
                    ? new BluetoothLock.BikeInfoMutable(bluetoothLockBikeInfo)
                    : new BikeInfoMutable(bikeInfo);
            }
        }
    }
}