#pragma once
/*
 *      Copyright (C) 2012-2013 Team XBMC
 *      http://xbmc.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#include "threads/CriticalSection.h"
#include "threads/Thread.h"
#include "utils/Observer.h"

#include "pvr/channels/PVRChannel.h"
#include "pvr/recordings/PVRRecording.h"

#include "addons/PVRClient.h"

#include <deque>
#include <vector>

namespace EPG
{
  class CEpg;
}

namespace PVR
{
  class CPVRGUIInfo;

  typedef std::shared_ptr<CPVRClient> PVR_CLIENT;
  typedef std::map< int, PVR_CLIENT >                 PVR_CLIENTMAP;
  typedef std::map< int, PVR_CLIENT >::iterator       PVR_CLIENTMAP_ITR;
  typedef std::map< int, PVR_CLIENT >::const_iterator PVR_CLIENTMAP_CITR;
  typedef std::map< int, PVR_STREAM_PROPERTIES >      STREAMPROPS;

  /**
   * Holds generic data about a backend (number of channels etc.)
   */
  struct SBackend
  {
    std::string name;
    std::string version;
    std::string host;
    int         numTimers = 0;
    int         numRecordings = 0;
    int         numDeletedRecordings = 0;
    int         numChannels = 0;
    long long   diskUsed = 0;
    long long   diskTotal = 0;
  };

  class CPVRClients : public ADDON::IAddonMgrCallback
  {
  friend class CPVRClient;
  public:
    CPVRClients(void);
    virtual ~CPVRClients(void);

    /*!
     * @brief Start the backend.
     */
    void Start(void);


    /*! @name Backend methods */
    //@{

    /*!
     * @brief Check whether a client ID points to a valid and connected add-on.
     * @param iClientId The client ID.
     * @return True when the client ID is valid and connected, false otherwise.
     */
    bool IsCreatedClient(int iClientId) const;

    bool IsCreatedClient(const ADDON::AddonPtr addon);

    /*!
     * @brief Restart a single client add-on.
     * @param addon The add-on to restart.
     * @param bDataChanged True if the client's data changed, false otherwise (unused).
     * @return True if the client was found and restarted, false otherwise.
     */
    virtual bool RequestRestart(ADDON::AddonPtr addon, bool bDataChanged) override;

    /*!
     * @brief Remove a single client add-on.
     * @param addon The add-on to remove.
     * @return True if the client was found and removed, false otherwise.
     */
    virtual bool RequestRemoval(ADDON::AddonPtr addon) override;

    /*!
     * @brief Unload all loaded add-ons and reset all class properties.
     */
    void Unload(void);

    /*!
     * @brief The ID of the first active client or -1 if no clients are active;
     */
    int GetFirstConnectedClientID(void);

    /*!
     * @return True when at least one client is known and enabled, false otherwise.
     */
    bool HasEnabledClients(void) const;

    /*!
     * @return The amount of enabled clients.
     */
    int EnabledClientAmount(void) const;

    /*!
     * @brief Stop a client.
     * @param addon The client to stop.
     * @param bRestart If true, restart the client.
     * @return True if the client was found, false otherwise.
     */
    bool StopClient(ADDON::AddonPtr client, bool bRestart);

    /*!
     * @return The amount of connected clients.
     */
    int CreatedClientAmount(void) const;

    /*!
     * @brief Check whether there are any connected clients.
     * @return True if at least one client is connected.
     */
    bool HasCreatedClients(void) const;

    /*!
     * @brief Get the friendly name for the client with the given id.
     * @param iClientId The id of the client.
     * @param strName The friendly name of the client or an empty string when it wasn't found.
     * @return True if the client was found, false otherwise.
     */
    bool GetClientFriendlyName(int iClientId, std::string &strName) const;

    /*!
     * @brief Get the addon name for the client with the given id.
     * @param iClientId The id of the client.
     * @param strName The addon name of the client or an empty string when it wasn't found.
     * @return True if the client was found, false otherwise.
     */
    bool GetClientAddonName(int iClientId, std::string &strName) const;

    /*!
     * @brief Get the addon icon for the client with the given id.
     * @param iClientId The id of the client.
     * @param strIcon The path to the addon icon of the client or an empty string when it wasn't found.
     * @return True if the client was found, false otherwise.
     */
    bool GetClientAddonIcon(int iClientId, std::string &strIcon) const;

    /*!
     * @brief Returns properties about all connected clients
     * @return the properties
     */
    std::vector<SBackend> GetBackendProperties() const;

    /*!
     * Get the add-on ID of the client
     * @param iClientId The db id of the client
     * @return The add-on id
     */
    std::string GetClientAddonId(int iClientId) const;

    /*!
     * @return The client ID of the client that is currently playing a stream or -1 if no client is playing.
     */
    int GetPlayingClientID(void) const;

    //@}

    /*! @name Stream methods */
    //@{

    /*!
     * @return True if a stream is playing, false otherwise.
     */
    bool IsPlaying(void) const;

    /*!
     * @return The friendly name of the client that is currently playing or an empty string if nothing is playing.
     */
    const std::string GetPlayingClientName(void) const;

    /*!
     * @brief Read from an open stream.
     * @param lpBuf Target buffer.
     * @param uiBufSize The size of the buffer.
     * @return The amount of bytes that was added.
     */
    int ReadStream(void* lpBuf, int64_t uiBufSize);

    /*!
     * @brief Return the filesize of the currently running stream.
     *        Limited to recordings playback at the moment.
     * @return The size of the stream.
     */
    int64_t GetStreamLength(void);

    /*!
     * @brief Seek to a position in a stream.
     *        Limited to recordings playback at the moment.
     * @param iFilePosition The position to seek to.
     * @param iWhence Specify how to seek ("new position=pos", "new position=pos+actual postion" or "new position=filesize-pos")
     * @return The new stream position.
     */
    int64_t SeekStream(int64_t iFilePosition, int iWhence = SEEK_SET);

    /*!
     * @brief Close a PVR stream.
     */
    void CloseStream(void);

    /*!
     * @brief (Un)Pause a PVR stream (only called when timeshifting is supported)
     */
    void PauseStream(bool bPaused);

    /*!
     * @brief Check whether it is possible to pause the currently playing livetv or recording stream
     */
    bool CanPauseStream(void) const;

    /*!
     * @brief Check whether it is possible to seek the currently playing livetv or recording stream
     */
    bool CanSeekStream(void) const;

    /*!
     * @brief Get the input format name of the current playing stream content.
     * @return A pointer to the properties or NULL if no stream is playing.
     */
    std::string GetCurrentInputFormat(void) const;

    /*!
     * @return True if a TV channel is playing, false otherwise.
     */
    bool IsPlayingTV(void) const;

    /*!
     * @return True if a radio channel playing, false otherwise.
     */
    bool IsPlayingRadio(void) const;

    /*!
     * @return True if the currently playing channel is encrypted, false otherwise.
     */
    bool IsEncrypted(void) const;

    /*!
     * @brief Open a stream on the given channel.
     * @param channel The channel to start playing.
     * @param bIsSwitchingChannel True when switching channels, false otherwise.
     * @return True if the stream was opened successfully, false otherwise.
     */
    bool OpenStream(const CPVRChannelPtr &channel, bool bIsSwitchingChannel);

    /*!
     * @brief Get the URL for the stream to the given channel.
     * @param channel The channel to get the stream url for.
     * @return The requested stream url or an empty string if it wasn't found.
     */
    std::string GetStreamURL(const CPVRChannelPtr &channel);

    /*!
     * @brief Switch an opened live tv stream to another channel.
     * @param channel The channel to switch to.
     * @return True if the switch was successfull, false otherwise.
     */
    bool SwitchChannel(const CPVRChannelPtr &channel);

    /*!
     * @brief Get the channel that is currently playing.
     * @return the channel that is currently playing, NULL otherwise.
     */
    CPVRChannelPtr GetPlayingChannel() const;

    /*!
     * @return True if a recording is playing, false otherwise.
     */
    bool IsPlayingRecording(void) const;

    /*!
     * @brief Open a stream from the given recording.
     * @param tag The recording to start playing.
     * @return True if the stream was opened successfully, false otherwise.
     */
    bool OpenStream(const CPVRRecordingPtr &tag);

    /*!
     * @brief Get the recordings that is currently playing.
     * @return The recording that is currently playing, NULL otherwise.
     */
    CPVRRecordingPtr GetPlayingRecording(void) const;

    //@}

    /*! @name Timer methods */
    //@{

    /*!
     * @brief Check whether there is at least one connected client supporting timers.
     * @return True if at least one connected client supports timers, false otherwise.
     */
    bool SupportsTimers() const;

    /*!
     * @brief Check whether a client supports timers.
     * @param iClientId The id of the client to check.
     * @return True if the supports timers, false otherwise.
     */
    bool HasTimerSupport(int iClientId);

    /*!
     * @brief Get all timers from clients
     * @param timers Store the timers in this container.
     * @param failedClients in case of errors will contain the ids of the clients for which the timers could not be obtained.
     * @return true on success for all clients, false in case of error for at least one client.
     */
    bool GetTimers(CPVRTimers *timers, std::vector<int> &failedClients);

    /*!
     * @brief Add a new timer to a backend.
     * @param timer The timer to add.
     * @param error An error if it occured.
     * @return True if the timer was added successfully, false otherwise.
     */
    PVR_ERROR AddTimer(const CPVRTimerInfoTag &timer);

    /*!
     * @brief Update a timer on the backend.
     * @param timer The timer to update.
     * @param error An error if it occured.
     * @return True if the timer was updated successfully, false otherwise.
     */
    PVR_ERROR UpdateTimer(const CPVRTimerInfoTag &timer);

    /*!
     * @brief Delete a timer from the backend.
     * @param timer The timer to delete.
     * @param bForce Also delete when currently recording if true.
     * @param error An error if it occured.
     * @return True if the timer was deleted successfully, false otherwise.
     */
    PVR_ERROR DeleteTimer(const CPVRTimerInfoTag &timer, bool bForce);

    /*!
     * @brief Rename a timer on the backend.
     * @param timer The timer to rename.
     * @param strNewName The new name.
     * @param error An error if it occured.
     * @return True if the timer was renamed successfully, false otherwise.
     */
    PVR_ERROR RenameTimer(const CPVRTimerInfoTag &timer, const std::string &strNewName);

    /*!
     * @brief Get all supported timer types.
     * @param results The container to store the result in.
     * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully.
     */
    PVR_ERROR GetTimerTypes(CPVRTimerTypes& results) const;

    /*!
     * @brief Get all timer types supported by a certain client.
     * @param iClientId The id of the client.
     * @param results The container to store the result in.
     * @return PVR_ERROR_NO_ERROR if the list has been fetched successfully.
     */
    PVR_ERROR GetTimerTypes(CPVRTimerTypes& results, int iClientId) const;

    //@}

    /*! @name Recording methods */
    //@{

    /*!
     * @brief Check whether a client supports recordings.
     * @param iClientId The id of the client to check.
     * @return True if the supports recordings, false otherwise.
     */
    bool SupportsRecordings(int iClientId) const;

    /*!
     * @brief Check whether a client supports undelete of recordings.
     * @param iClientId The id of the client to check.
     * @return True if the supports undeleted of recordings, false otherwise.
     */
    bool SupportsRecordingsUndelete(int iClientId) const;

    /*!
     * @brief Get all recordings from clients
     * @param recordings Store the recordings in this container.
     * @param deleted Return deleted recordings
     * @return The amount of recordings that were added.
     */
    PVR_ERROR GetRecordings(CPVRRecordings *recordings, bool deleted);

    /*!
     * @brief Rename a recordings on the backend.
     * @param recording The recordings to rename.
     * @param error An error if it occured.
     * @return True if the recording was renamed successfully, false otherwise.
     */
    PVR_ERROR RenameRecording(const CPVRRecording &recording);

    /*!
     * @brief Delete a recording from the backend.
     * @param recording The recording to delete.
     * @param error An error if it occured.
     * @return True if the recordings was deleted successfully, false otherwise.
     */
    PVR_ERROR DeleteRecording(const CPVRRecording &recording);

    /*!
     * @brief Undelete a recording from the backend.
     * @param recording The recording to undelete.
     * @param error An error if it occured.
     * @return True if the recording was undeleted successfully, false otherwise.
     */
    PVR_ERROR UndeleteRecording(const CPVRRecording &recording);

    /*!
     * @brief Delete all recordings permanent which in the deleted folder on the backend.
     * @return PVR_ERROR_NO_ERROR if the recordings has been deleted successfully.
     */
    PVR_ERROR DeleteAllRecordingsFromTrash();

    /*!
     * @brief Set play count of a recording on the backend.
     * @param recording The recording to set the play count.
     * @param count Play count.
     * @param error An error if it occured.
     * @return True if the recording's play count was set successfully, false otherwise.
     */
    bool SetRecordingPlayCount(const CPVRRecording &recording, int count, PVR_ERROR *error);

    /*!
     * @brief Set the last watched position of a recording on the backend.
     * @param recording The recording.
     * @param position The last watched position in seconds
     * @param error An error if it occured.
     * @return True if the last played position was updated successfully, false otherwise
    */
    bool SetRecordingLastPlayedPosition(const CPVRRecording &recording, int lastplayedposition, PVR_ERROR *error);

    /*!
    * @brief Retrieve the last watched position of a recording on the backend.
    * @param recording The recording.
    * @return The last watched position in seconds
    */
    int GetRecordingLastPlayedPosition(const CPVRRecording &recording);

    /*!
    * @brief Retrieve the edit decision list (EDL) from the backend.
    * @param recording The recording.
    * @return The edit decision list (empty on error).
    */
    std::vector<PVR_EDL_ENTRY> GetRecordingEdl(const CPVRRecording &recording);

    /*!
     * @brief Check whether there is an active recording on the current channel.
     * @return True if there is, false otherwise.
     */
    bool IsRecordingOnPlayingChannel(void) const;

    /*!
     * @brief Check whether the current channel can be recorded instantly.
     * @return True if it can, false otherwise.
     */
    bool CanRecordInstantly(void);

    //@}

    /*! @name EPG methods */
    //@{

    /*!
     * @brief Check whether a client supports EPG transfer.
     * @param iClientId The id of the client to check.
     * @return True if the supports EPG transfer, false otherwise.
     */
    bool SupportsEPG(int iClientId) const;

    /*!
     * @brief Get the EPG table for a channel.
     * @param channel The channel to get the EPG table for.
     * @param epg Store the EPG in this container.
     * @param start Get entries after this start time.
     * @param end Get entries before this end time.
     * @param error An error if it occured.
     * @return True if the EPG was transfered successfully, false otherwise.
     */
    PVR_ERROR GetEPGForChannel(const CPVRChannelPtr &channel, EPG::CEpg *epg, time_t start, time_t end);

    /*!
     * Tell the client the time frame to use when notifying epg events back to Kodi. The client might push epg events asynchronously
     * to Kodi using the callback function EpgEventStateChange. To be able to only push events that are actually of interest for Kodi,
     * client needs to know about the epg time frame Kodi uses.
     * @param iDays number of days from "now". EPG_TIMEFRAME_UNLIMITED means that Kodi is interested in all epg events, regardless of event times.
     * @return PVR_ERROR_NO_ERROR if new value was successfully set.
     */
    PVR_ERROR SetEPGTimeFrame(int iDays);

    //@}

    /*! @name Channel methods */
    //@{

    /*!
     * @brief Get all channels from backends.
     * @param group The container to store the channels in.
     * @param error An error if it occured.
     * @return The amount of channels that were added.
     */
    PVR_ERROR GetChannels(CPVRChannelGroupInternal *group);

    /*!
     * @brief Check whether a client supports channel groups.
     * @param iClientId The id of the client to check.
     * @return True if the supports channel groups, false otherwise.
     */
    bool SupportsChannelGroups(int iClientId) const;

    /*!
     * @brief Get all channel groups from backends.
     * @param groups Store the channel groups in this container.
     * @param error An error if it occured.
     * @return The amount of groups that were added.
     */
    PVR_ERROR GetChannelGroups(CPVRChannelGroups *groups);

    /*!
     * @brief Get all group members of a channel group.
     * @param group The group to get the member for.
     * @param error An error if it occured.
     * @return The amount of channels that were added.
     */
    PVR_ERROR GetChannelGroupMembers(CPVRChannelGroup *group);

    //@}

    /*! @name Menu hook methods */
    //@{

    /*!
     * @brief Check whether a client has any PVR specific menu entries.
     * @param iClientId The ID of the client to get the menu entries for. Get the menu for the active channel if iClientId < 0.
     * @return True if the client has any menu hooks, false otherwise.
     */
    bool HasMenuHooks(int iClientId, PVR_MENUHOOK_CAT cat);

    /*!
     * @brief Open selection and progress PVR actions.
     * @param iClientId The ID of the client to process the menu entries for. Process the menu entries for the active channel if iClientId < 0.
     * @param item The selected file item for which the hook was called.
     */
    void ProcessMenuHooks(int iClientID, PVR_MENUHOOK_CAT cat, const CFileItem *item);

    //@}

    /*! @name Channel scan methods */
    //@{

    /*!
     * @return True when a channel scan is currently running, false otherwise.
     */
    bool IsRunningChannelScan(void) const;

    /*!
     * @brief Open a selection dialog and start a channel scan on the selected client.
     */
    void StartChannelScan(void);

    /*!
     * @return All clients that support channel scanning.
     */
    std::vector<PVR_CLIENT> GetClientsSupportingChannelScan(void) const;

    //@}

    /*! @name Channel settings methods */
    //@{

    /*!
     * @return All clients that support channel settings inside addon.
     */
    std::vector<PVR_CLIENT> GetClientsSupportingChannelSettings(bool bRadio) const;

    /*!
     * @brief Open addon settings dialog to add a channel
     * @param channel The channel to edit.
     * @return True if the edit was successfull, false otherwise.
     */
    bool OpenDialogChannelAdd(const CPVRChannelPtr &channel);

    /*!
     * @brief Open addon settings dialog to related channel
     * @param channel The channel to edit.
     * @return True if the edit was successfull, false otherwise.
     */
    bool OpenDialogChannelSettings(const CPVRChannelPtr &channel);

    /*!
     * @brief Inform addon to delete channel
     * @param channel The channel to delete.
     * @return True if it was successfull, false otherwise.
     */
    bool DeleteChannel(const CPVRChannelPtr &channel);

    /*!
     * @brief Request the client to rename given channel
     * @param channel The channel to rename
     * @return True if the edit was successfull, false otherwise.
     */
    bool RenameChannel(const CPVRChannelPtr &channel);

    //@}

    bool GetClient(const std::string &strId, ADDON::AddonPtr &addon) const;

    bool SupportsChannelScan(int iClientId) const;
    bool SupportsChannelSettings(int iClientId) const;
    bool SupportsLastPlayedPosition(int iClientId) const;
    bool SupportsRadio(int iClientId) const;
    bool SupportsRecordingPlayCount(int iClientId) const;
    bool SupportsRecordingEdl(int iClientId) const;
    bool SupportsTimers(int iClientId) const;
    bool SupportsTV(int iClientId) const;
    bool HandlesDemuxing(int iClientId) const;
    bool HandlesInputStream(int iClientId) const;

    bool GetPlayingClient(PVR_CLIENT &client) const;

    std::string GetBackendHostnameByClientId(int iClientId) const;

    bool IsTimeshifting() const;
    time_t GetPlayingTime() const;
    time_t GetBufferTimeStart() const;
    time_t GetBufferTimeEnd() const;


    int GetClientId(const std::string& strId) const;

    bool IsRealTimeStream() const;

    void ConnectionStateChange(int clientId, std::string &strConnectionString, PVR_CONNECTION_STATE newState,
                               std::string &strMessage);

    /*!
     * @brief Propagate event to clients
     */
    void OnSystemSleep();
    void OnSystemWake();
    void OnPowerSavingActivated();
    void OnPowerSavingDeactivated();

  private:
    /*!
     * @brief Update add-ons from the AddonManager
     */
    void UpdateAddons(void);

    /*!
     * @brief Get the instance of the client.
     * @param iClientId The id of the client to get.
     * @param addon The client.
     * @return True if the client was found, false otherwise.
     */
    bool GetClient(int iClientId, PVR_CLIENT &addon) const;

    /*!
     * @brief Get the instance of the client, if it's created.
     * @param iClientId The id of the client to get.
     * @param addon The client.
     * @return True if the client is connected, false otherwise.
     */
    bool GetCreatedClient(int iClientId, PVR_CLIENT &addon) const;

    /*!
     * @bried Get all created clients.
     * @param clients Store the active clients in this map.
     * @return The amount of added clients.
     */
    int GetCreatedClients(PVR_CLIENTMAP &clients) const;

    /*!
     * @brief Check whether a client is registered.
     * @param client The client to check.
     * @return True if this client is registered, false otherwise.
     */
    bool IsKnownClient(const ADDON::AddonPtr client) const;


    int GetClientId(const ADDON::AddonPtr client) const;


    bool                  m_bChannelScanRunning;      /*!< true when a channel scan is currently running, false otherwise */
    bool                  m_bIsSwitchingChannels;        /*!< true while switching channels */
    int                   m_playingClientId;          /*!< the ID of the client that is currently playing */
    bool                  m_bIsPlayingLiveTV;
    bool                  m_bIsPlayingRecording;
    std::string           m_strPlayingClientName;     /*!< the name client that is currenty playing a stream or an empty string if nothing is playing */
    PVR_CLIENTMAP         m_clientMap;                /*!< a map of all known clients */
    CCriticalSection      m_critSection;
    std::map<std::string, int> m_addonNameIds; /*!< map add-on names to IDs */
  };
}
