/*
 *  Copyright (C) 2005-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#pragma once

#include <memory>
#include <vector>

#include "peripherals/PeripheralTypes.h"
#include "threads/Thread.h"

class CFileItemList;

namespace PERIPHERALS
{
  class CPeripheral;
  class CPeripherals;

  /*!
   * @class CPeripheralBus
   * This represents a bus on the system. By default, this bus instance will scan for changes every 5 seconds.
   * If this bus only has to be updated after a notification sent by the system, set m_bNeedsPolling to false
   * in the constructor, and implement the OnDeviceAdded(), OnDeviceChanged() and OnDeviceRemoved() methods.
   *
   * The PerformDeviceScan() method has to be implemented by each specific bus implementation.
   */
  class CPeripheralBus : protected CThread
  {
  public:
    CPeripheralBus(const std::string &threadname, CPeripherals& manager, PeripheralBusType type);
    ~CPeripheralBus(void) override { Clear(); }

    /*!
     * @return The bus type
     */
    PeripheralBusType Type(void) const { return m_type; }

    /*!
     * @return True if this bus needs to be polled for changes, false if this bus performs updates via callbacks
     */
    bool NeedsPolling(void) const { CSingleLock lock(m_critSection); return m_bNeedsPolling; }

    /*!
    * \brief Initialize the properties of a peripheral with a known location
    */
    virtual bool InitializeProperties(CPeripheral& peripheral);

    /*!
     * @brief Get the instance of the peripheral at the given location.
     * @param strLocation The location.
     * @return The peripheral or NULL if it wasn't found.
     */
    virtual PeripheralPtr GetPeripheral(const std::string &strLocation) const;

    /*!
     * @brief Check whether a peripheral is present at the given location.
     * @param strLocation The location.
     * @return True when a peripheral was found, false otherwise.
     */
    virtual bool HasPeripheral(const std::string &strLocation) const;

    /*!
     * @brief Check if the bus supports the given feature
     * @param feature The feature to check for
     * @return True if the bus supports the feature, false otherwise
     */
    virtual bool SupportsFeature(PeripheralFeature feature) const { return false; }

    /*!
     * @brief Get all peripheral instances that have the given feature.
     * @param results The list of results.
     * @param feature The feature to search for.
     * @return The number of devices that have been found.
     */
    virtual unsigned int GetPeripheralsWithFeature(PeripheralVector &results, const PeripheralFeature feature) const;

    virtual unsigned int GetNumberOfPeripherals() const;
    virtual unsigned int GetNumberOfPeripheralsWithId(const int iVendorId, const int iProductId) const;

    /*!
     * @brief Get all features that are supported by devices on this bus.
     * @param features All features.
     */
    virtual void GetFeatures(std::vector<PeripheralFeature> &features) const;

    /*!
     * @brief Check whether there is at least one device present with the given feature.
     * @param feature The feature to check for.
     * @return True when at least one device was found with this feature, false otherwise.
     */
    virtual bool HasFeature(const PeripheralFeature feature) const;

    /*!
     * @brief Callback method for when a device has been added. Will perform a device scan.
     * @param strLocation The location of the device that has been added.
     */
    virtual void OnDeviceAdded(const std::string &strLocation);

    /*!
     * @brief Callback method for when a device has been changed. Will perform a device scan.
     * @param strLocation The location of the device that has been changed.
     */
    virtual void OnDeviceChanged(const std::string &strLocation);

    /*!
     * @brief Callback method for when a device has been removed. Will perform a device scan.
     * @param strLocation The location of the device that has been removed.
     */
    virtual void OnDeviceRemoved(const std::string &strLocation);

    /*!
     * @brief Initialise this bus and start a polling thread if this bus needs polling.
     */
    virtual void Initialise(void);

    /*!
     * @brief Stop the polling thread and clear all known devices on this bus.
     */
    virtual void Clear(void);

    /*!
     * @brief Scan for devices.
     */
    virtual void TriggerDeviceScan(void);

    /*!
     * @brief Get all fileitems for a path.
     * @param strPath The path to the directory to get the items from.
     * @param items The item list.
     */
    virtual void GetDirectory(const std::string &strPath, CFileItemList &items) const;

    /*!
     * @brief Get the instance of a peripheral given it's path.
     * @param strPath The path to the peripheral.
     * @return The peripheral or NULL if it wasn't found.
     */
    virtual PeripheralPtr GetByPath(const std::string &strPath) const;

    /*!
     * @brief Register a new peripheral on this bus.
     * @param peripheral The peripheral to register.
     */
    virtual void Register(const PeripheralPtr& peripheral);

    virtual bool FindComPort(std::string &strLocation) { return false; }

    /*!
     * \brief Poll for events
     */
    virtual void ProcessEvents(void) { }

    /*!
    * \brief Initialize button mapping
    * \return True if button mapping is enabled for this bus
    */
    virtual void EnableButtonMapping() { }

    /*!
     * \brief Power off the specified device
     * \param strLocation The device's location
     */
    virtual void PowerOff(const std::string& strLocation) { }

  protected:
    void Process(void) override;
    virtual bool ScanForDevices(void);
    virtual void UnregisterRemovedDevices(const PeripheralScanResults &results);
    virtual void RegisterNewDevices(const PeripheralScanResults &results);

    /*!
     * @brief Scan for devices on this bus and add them to the results list. This will have to be implemented for each bus.
     * @param results The result list.
     * @return True when the scan was successful, false otherwise.
     */
    virtual bool PerformDeviceScan(PeripheralScanResults &results) = 0;

    PeripheralVector           m_peripherals;
    int                        m_iRescanTime;
    bool                       m_bNeedsPolling; /*!< true when this bus needs to be polled for new devices, false when it uses callbacks to notify this bus of changed */
    CPeripherals&              m_manager;
    const PeripheralBusType    m_type;
    mutable CCriticalSection   m_critSection;
    CEvent                     m_triggerEvent;
  };
  using PeripheralBusPtr = std::shared_ptr<CPeripheralBus>;
  using PeripheralBusVector = std::vector<PeripheralBusPtr>;
}
