/*
 *  Copyright (C) 2012-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.
 */

#include "PVRGUIInfo.h"

#include "Application.h"
#include "GUIInfoManager.h"
#include "ServiceBroker.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
#include "guilib/guiinfo/GUIInfo.h"
#include "guilib/guiinfo/GUIInfoLabels.h"
#include "pvr/PVRItem.h"
#include "pvr/PVRManager.h"
#include "pvr/PVRPlaybackState.h"
#include "pvr/addons/PVRClient.h"
#include "pvr/addons/PVRClients.h"
#include "pvr/channels/PVRChannel.h"
#include "pvr/channels/PVRChannelGroup.h"
#include "pvr/channels/PVRChannelGroupsContainer.h"
#include "pvr/channels/PVRRadioRDSInfoTag.h"
#include "pvr/epg/EpgInfoTag.h"
#include "pvr/guilib/PVRGUIActions.h"
#include "pvr/recordings/PVRRecording.h"
#include "pvr/recordings/PVRRecordings.h"
#include "pvr/timers/PVRTimerInfoTag.h"
#include "pvr/timers/PVRTimers.h"
#include "settings/AdvancedSettings.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "threads/SingleLock.h"
#include "utils/StringUtils.h"

#include <cmath>
#include <ctime>
#include <memory>
#include <string>
#include <vector>

using namespace PVR;
using namespace KODI::GUILIB::GUIINFO;

CPVRGUIInfo::CPVRGUIInfo() : CThread("PVRGUIInfo")
{
  ResetProperties();
}

void CPVRGUIInfo::ResetProperties()
{
  CSingleLock lock(m_critSection);

  m_anyTimersInfo.ResetProperties();
  m_tvTimersInfo.ResetProperties();
  m_radioTimersInfo.ResetProperties();
  m_timesInfo.Reset();
  m_bHasTVRecordings = false;
  m_bHasRadioRecordings = false;
  m_iCurrentActiveClient = 0;
  m_strPlayingClientName        .clear();
  m_strBackendName              .clear();
  m_strBackendVersion           .clear();
  m_strBackendHost              .clear();
  m_strBackendTimers            .clear();
  m_strBackendRecordings        .clear();
  m_strBackendDeletedRecordings .clear();
  m_strBackendChannels          .clear();
  m_iBackendDiskTotal = 0;
  m_iBackendDiskUsed = 0;
  m_bIsPlayingTV = false;
  m_bIsPlayingRadio = false;
  m_bIsPlayingRecording = false;
  m_bIsPlayingEpgTag = false;
  m_bIsPlayingEncryptedStream = false;
  m_bIsRecordingPlayingChannel = false;
  m_bCanRecordPlayingChannel = false;
  m_bIsPlayingActiveRecording = false;
  m_bHasTVChannels = false;
  m_bHasRadioChannels = false;

  ClearQualityInfo(m_qualityInfo);
  ClearDescrambleInfo(m_descrambleInfo);

  m_updateBackendCacheRequested = false;
  m_bRegistered = false;
}

void CPVRGUIInfo::ClearQualityInfo(PVR_SIGNAL_STATUS& qualityInfo)
{
  memset(&qualityInfo, 0, sizeof(qualityInfo));
  strncpy(qualityInfo.strAdapterName, g_localizeStrings.Get(13106).c_str(), PVR_ADDON_NAME_STRING_LENGTH - 1);
  strncpy(qualityInfo.strAdapterStatus, g_localizeStrings.Get(13106).c_str(), PVR_ADDON_NAME_STRING_LENGTH - 1);
}

void CPVRGUIInfo::ClearDescrambleInfo(PVR_DESCRAMBLE_INFO& descrambleInfo)
{
  descrambleInfo = {0};
}

void CPVRGUIInfo::Start()
{
  ResetProperties();
  Create();
  SetPriority(-1);
}

void CPVRGUIInfo::Stop()
{
  StopThread();
  CServiceBroker::GetPVRManager().Events().Unsubscribe(this);

  CGUIComponent* gui = CServiceBroker::GetGUI();
  if (gui)
  {
    gui->GetInfoManager().UnregisterInfoProvider(this);
    m_bRegistered = false;
  }
}

void CPVRGUIInfo::Notify(const PVREvent& event)
{
  if (event == PVREvent::Timers || event == PVREvent::TimersInvalidated)
    UpdateTimersCache();
}

void CPVRGUIInfo::Process()
{
  unsigned int iLoop = 0;
  int toggleInterval = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRInfoToggleInterval / 1000;

  /* updated on request */
  CServiceBroker::GetPVRManager().Events().Subscribe(this, &CPVRGUIInfo::Notify);
  UpdateTimersCache();

  /* update the backend cache once initially */
  m_updateBackendCacheRequested = true;

  while (!g_application.m_bStop && !m_bStop)
  {
    if (!m_bRegistered)
    {
      CGUIComponent* gui = CServiceBroker::GetGUI();
      if (gui)
      {
        gui->GetInfoManager().RegisterInfoProvider(this);
        m_bRegistered = true;
      }
    }

    if (!m_bStop)
      UpdateQualityData();
    std::this_thread::yield();

    if (!m_bStop)
      UpdateDescrambleData();
    std::this_thread::yield();

    if (!m_bStop)
      UpdateMisc();
    std::this_thread::yield();

    if (!m_bStop)
      UpdateTimeshiftData();
    std::this_thread::yield();

    if (!m_bStop)
      UpdateTimersToggle();
    std::this_thread::yield();

    if (!m_bStop)
      UpdateNextTimer();
    std::this_thread::yield();

    // Update the backend cache every toggleInterval seconds
    if (!m_bStop && iLoop % toggleInterval == 0)
      UpdateBackendCache();

    if (++iLoop == 1000)
      iLoop = 0;

    if (!m_bStop)
      CThread::Sleep(500);
  }
}

void CPVRGUIInfo::UpdateQualityData()
{
  PVR_SIGNAL_STATUS qualityInfo;
  ClearQualityInfo(qualityInfo);

  if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_PVRPLAYBACK_SIGNALQUALITY))
  {
    const int channelUid = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannelUniqueID();
    if (channelUid > 0)
    {
      std::shared_ptr<CPVRClient> client;
      CServiceBroker::GetPVRManager().Clients()->GetCreatedClient(CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingClientID(), client);
      if (client && client->SignalQuality(channelUid, qualityInfo) == PVR_ERROR_NO_ERROR)
      {
        m_qualityInfo = qualityInfo;
      }
    }
  }
}

void CPVRGUIInfo::UpdateDescrambleData()
{
  PVR_DESCRAMBLE_INFO descrambleInfo;
  ClearDescrambleInfo(descrambleInfo);

  const int channelUid = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannelUniqueID();
  if (channelUid > 0)
  {
    std::shared_ptr<CPVRClient> client;
    CServiceBroker::GetPVRManager().Clients()->GetCreatedClient(CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingClientID(), client);
    if (client && client->GetDescrambleInfo(channelUid, descrambleInfo) == PVR_ERROR_NO_ERROR)
    {
      m_descrambleInfo = descrambleInfo;
    }
  }
}

void CPVRGUIInfo::UpdateMisc()
{
  CPVRManager& mgr = CServiceBroker::GetPVRManager();
  bool bStarted = mgr.IsStarted();
  const std::shared_ptr<CPVRPlaybackState> state = mgr.PlaybackState();

  /* safe to fetch these unlocked, since they're updated from the same thread as this one */
  const std::string strPlayingClientName = bStarted ? state->GetPlayingClientName() : "";
  const bool bHasTVRecordings = bStarted && mgr.Recordings()->GetNumTVRecordings() > 0;
  const bool bHasRadioRecordings = bStarted && mgr.Recordings()->GetNumRadioRecordings() > 0;
  const bool bIsPlayingTV = bStarted && state->IsPlayingTV();
  const bool bIsPlayingRadio = bStarted && state->IsPlayingRadio();
  const bool bIsPlayingRecording = bStarted && state->IsPlayingRecording();
  const bool bIsPlayingEpgTag = bStarted && state->IsPlayingEpgTag();
  const bool bIsPlayingEncryptedStream = bStarted && state->IsPlayingEncryptedChannel();
  const bool bHasTVChannels = bStarted && mgr.ChannelGroups()->GetGroupAllTV()->HasChannels();
  const bool bHasRadioChannels = bStarted && mgr.ChannelGroups()->GetGroupAllRadio()->HasChannels();
  const bool bCanRecordPlayingChannel = bStarted && state->CanRecordOnPlayingChannel();
  const bool bIsRecordingPlayingChannel = bStarted && state->IsRecordingOnPlayingChannel();
  const bool bIsPlayingActiveRecording = bStarted && state->IsPlayingActiveRecording();
  const std::string strPlayingTVGroup = (bStarted && bIsPlayingTV) ? state->GetPlayingGroup(false)->GroupName() : "";
  const std::string strPlayingRadioGroup = (bStarted && bIsPlayingRadio) ? state->GetPlayingGroup(true)->GroupName() : "";

  CSingleLock lock(m_critSection);
  m_strPlayingClientName = strPlayingClientName;
  m_bHasTVRecordings = bHasTVRecordings;
  m_bHasRadioRecordings = bHasRadioRecordings;
  m_bIsPlayingTV = bIsPlayingTV;
  m_bIsPlayingRadio = bIsPlayingRadio;
  m_bIsPlayingRecording = bIsPlayingRecording;
  m_bIsPlayingEpgTag = bIsPlayingEpgTag;
  m_bIsPlayingEncryptedStream = bIsPlayingEncryptedStream;
  m_bHasTVChannels = bHasTVChannels;
  m_bHasRadioChannels = bHasRadioChannels;
  m_strPlayingTVGroup = strPlayingTVGroup;
  m_strPlayingRadioGroup = strPlayingRadioGroup;
  m_bCanRecordPlayingChannel = bCanRecordPlayingChannel;
  m_bIsRecordingPlayingChannel = bIsRecordingPlayingChannel;
  m_bIsPlayingActiveRecording = bIsPlayingActiveRecording;
}

void CPVRGUIInfo::UpdateTimeshiftData()
{
  m_timesInfo.Update();
}

bool CPVRGUIInfo::InitCurrentItem(CFileItem* item)
{
  CServiceBroker::GetPVRManager().PublishEvent(PVREvent::CurrentItem);
  return false;
}

bool CPVRGUIInfo::GetLabel(std::string& value, const CFileItem* item, int contextWindow, const CGUIInfo& info, std::string* fallback) const
{
  return GetListItemAndPlayerLabel(item, info, value) ||
         GetPVRLabel(item, info, value) ||
         GetRadioRDSLabel(item, info, value);
}

namespace
{
  std::string GetAsLocalizedDateString(const CDateTime& datetime, bool bLongDate)
  {
    return datetime.IsValid() ? datetime.GetAsLocalizedDate(bLongDate) : "";
  }

  std::string GetAsLocalizedTimeString(const CDateTime& datetime)
  {
    return datetime.IsValid() ? datetime.GetAsLocalizedTime("", false) : "";
  }

  std::string GetAsLocalizedDateTimeString(const CDateTime& datetime)
  {
    return datetime.IsValid() ? datetime.GetAsLocalizedDateTime(false, false) : "";
  }

  std::string GetEpgTagTitle(const std::shared_ptr<CPVREpgInfoTag>& epgTag)
  {
    if (epgTag)
    {
      if (CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
        return g_localizeStrings.Get(19266); // Parental locked
      else if (!epgTag->Title().empty())
        return epgTag->Title();
    }

    if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
            CSettings::SETTING_EPG_HIDENOINFOAVAILABLE))
      return g_localizeStrings.Get(19055); // no information available

    return {};
  }

} // unnamed namespace

bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, const CGUIInfo& info, std::string& strValue) const
{
  const std::shared_ptr<CPVRTimerInfoTag> timer = item->GetPVRTimerInfoTag();
  if (timer)
  {
    switch (info.m_info)
    {
      case LISTITEM_DATE:
        strValue = timer->Summary();
        return true;
      case LISTITEM_STARTDATE:
        strValue = GetAsLocalizedDateString(timer->StartAsLocalTime(), true);
        return true;
      case LISTITEM_STARTTIME:
        strValue = GetAsLocalizedTimeString(timer->StartAsLocalTime());
        return true;
      case LISTITEM_ENDDATE:
        strValue = GetAsLocalizedDateString(timer->EndAsLocalTime(), true);
        return true;
      case LISTITEM_ENDTIME:
        strValue = GetAsLocalizedTimeString(timer->EndAsLocalTime());
        return true;
      case LISTITEM_DURATION:
        if (timer->GetDuration() > 0)
        {
          strValue = StringUtils::SecondsToTimeString(timer->GetDuration(), static_cast<TIME_FORMAT>(info.GetData4()));
          return true;
        }
        return false;
      case LISTITEM_TITLE:
        strValue = timer->Title();
        return true;
      case LISTITEM_COMMENT:
        strValue = timer->GetStatus(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_RADIO_TIMER_RULES);
        return true;
      case LISTITEM_TIMERTYPE:
        strValue = timer->GetTypeAsString();
        return true;
      case LISTITEM_CHANNEL_NAME:
        strValue = timer->ChannelName();
        return true;
      case LISTITEM_EPG_EVENT_TITLE:
      case LISTITEM_EPG_EVENT_ICON:
      case LISTITEM_GENRE:
      case LISTITEM_PLOT:
      case LISTITEM_PLOT_OUTLINE:
      case LISTITEM_ORIGINALTITLE:
      case LISTITEM_YEAR:
      case LISTITEM_SEASON:
      case LISTITEM_EPISODE:
      case LISTITEM_EPISODENAME:
      case LISTITEM_DIRECTOR:
      case LISTITEM_CHANNEL_NUMBER:
      case LISTITEM_PREMIERED:
        break; // obtain value from channel/epg
      default:
        return false;
    }
  }

  const std::shared_ptr<CPVRRecording> recording(item->GetPVRRecordingInfoTag());
  if (recording)
  {
    // Note: CPVRRecoding is derived from CVideoInfoTag. All base class properties will be handled
    //       by CVideoGUIInfoProvider. Only properties introduced by CPVRRecording need to be handled here.
    switch (info.m_info)
    {
      case LISTITEM_DATE:
        strValue = GetAsLocalizedDateTimeString(recording->RecordingTimeAsLocalTime());
        return true;
      case LISTITEM_STARTDATE:
        strValue = GetAsLocalizedDateString(recording->RecordingTimeAsLocalTime(), true);
        return true;
      case VIDEOPLAYER_STARTTIME:
      case LISTITEM_STARTTIME:
        strValue = GetAsLocalizedTimeString(recording->RecordingTimeAsLocalTime());
        return true;
      case LISTITEM_ENDDATE:
        strValue = GetAsLocalizedDateString(recording->EndTimeAsLocalTime(), true);
        return true;
      case VIDEOPLAYER_ENDTIME:
      case LISTITEM_ENDTIME:
        strValue = GetAsLocalizedTimeString(recording->EndTimeAsLocalTime());
        return true;
      case LISTITEM_EXPIRATION_DATE:
        if (recording->HasExpirationTime())
        {
          strValue = GetAsLocalizedDateString(recording->ExpirationTimeAsLocalTime(), false);
          return true;
        }
        break;
      case LISTITEM_EXPIRATION_TIME:
        if (recording->HasExpirationTime())
        {
          strValue = GetAsLocalizedTimeString(recording->ExpirationTimeAsLocalTime());;
          return true;
        }
        break;
      case VIDEOPLAYER_EPISODENAME:
      case LISTITEM_EPISODENAME:
        strValue = recording->EpisodeName();
        // fixup multiline episode name strings (which do not fit in any way in our GUI)
        StringUtils::Replace(strValue, "\n", ", ");
        return true;
      case VIDEOPLAYER_CHANNEL_NAME:
      case LISTITEM_CHANNEL_NAME:
        strValue = recording->m_strChannelName;
        return true;
      case VIDEOPLAYER_CHANNEL_NUMBER:
      case LISTITEM_CHANNEL_NUMBER:
      {
        const std::shared_ptr<CPVRChannel> channel = recording->Channel();
        if (channel)
        {
          strValue = channel->ChannelNumber().FormattedChannelNumber();
          return true;
        }
        break;
      }
      case VIDEOPLAYER_CHANNEL_GROUP:
      {
        CSingleLock lock(m_critSection);
        strValue = recording->IsRadio() ? m_strPlayingRadioGroup : m_strPlayingTVGroup;
        return true;
      }
      case VIDEOPLAYER_PREMIERED:
      case LISTITEM_PREMIERED:
        if (recording->FirstAired().IsValid())
        {
          strValue = recording->FirstAired().GetAsLocalizedDate();
          return true;
        }
        else if (recording->HasYear())
        {
          strValue = StringUtils::Format("%i", recording->GetYear());
          return true;
        }
        return false;
      case LISTITEM_SIZE:
        if (recording->GetSizeInBytes() > 0)
        {
          strValue = StringUtils::SizeToString(recording->GetSizeInBytes());
          return true;
        }
        return false;
    }
    return false;
  }

  std::shared_ptr<CPVREpgInfoTag> epgTag;
  std::shared_ptr<CPVRChannel> channel;
  if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
  {
    CPVRItem pvrItem(item);
    channel = pvrItem.GetChannel();

    switch (info.m_info)
    {
      case VIDEOPLAYER_NEXT_TITLE:
      case VIDEOPLAYER_NEXT_GENRE:
      case VIDEOPLAYER_NEXT_PLOT:
      case VIDEOPLAYER_NEXT_PLOT_OUTLINE:
      case VIDEOPLAYER_NEXT_STARTTIME:
      case VIDEOPLAYER_NEXT_ENDTIME:
      case VIDEOPLAYER_NEXT_DURATION:
      case LISTITEM_NEXT_TITLE:
      case LISTITEM_NEXT_GENRE:
      case LISTITEM_NEXT_PLOT:
      case LISTITEM_NEXT_PLOT_OUTLINE:
      case LISTITEM_NEXT_STARTDATE:
      case LISTITEM_NEXT_STARTTIME:
      case LISTITEM_NEXT_ENDDATE:
      case LISTITEM_NEXT_ENDTIME:
      case LISTITEM_NEXT_DURATION:
        // next playing event
        epgTag = pvrItem.GetNextEpgInfoTag();
        break;
      default:
        // now playing event
        epgTag = pvrItem.GetEpgInfoTag();
        break;
    }

    switch (info.m_info)
    {
      // special handling for channels without epg or with radio rds data
      case PLAYER_TITLE:
      case LISTITEM_TITLE:
      case VIDEOPLAYER_NEXT_TITLE:
      case LISTITEM_NEXT_TITLE:
      case LISTITEM_EPG_EVENT_TITLE:
        // Note: in difference to LISTITEM_TITLE, LISTITEM_EPG_EVENT_TITLE returns the title
        // associated with the epg event of a timer, if any, and not the title of the timer.
        strValue = GetEpgTagTitle(epgTag);
        return true;
    }
  }

  if (epgTag)
  {
    switch (info.m_info)
    {
      case VIDEOPLAYER_GENRE:
      case LISTITEM_GENRE:
      case VIDEOPLAYER_NEXT_GENRE:
      case LISTITEM_NEXT_GENRE:
        strValue = epgTag->GetGenresLabel();
        return true;
      case VIDEOPLAYER_PLOT:
      case LISTITEM_PLOT:
      case VIDEOPLAYER_NEXT_PLOT:
      case LISTITEM_NEXT_PLOT:
        if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
          strValue = epgTag->Plot();
        return true;
      case VIDEOPLAYER_PLOT_OUTLINE:
      case LISTITEM_PLOT_OUTLINE:
      case VIDEOPLAYER_NEXT_PLOT_OUTLINE:
      case LISTITEM_NEXT_PLOT_OUTLINE:
        if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
          strValue = epgTag->PlotOutline();
        return true;
      case LISTITEM_DATE:
        strValue = GetAsLocalizedDateTimeString(epgTag->StartAsLocalTime());
        return true;
      case LISTITEM_STARTDATE:
      case LISTITEM_NEXT_STARTDATE:
        strValue = GetAsLocalizedDateString(epgTag->StartAsLocalTime(), true);
        return true;
      case VIDEOPLAYER_STARTTIME:
      case VIDEOPLAYER_NEXT_STARTTIME:
      case LISTITEM_STARTTIME:
      case LISTITEM_NEXT_STARTTIME:
        strValue = GetAsLocalizedTimeString(epgTag->StartAsLocalTime());
        return true;
      case LISTITEM_ENDDATE:
      case LISTITEM_NEXT_ENDDATE:
        strValue = GetAsLocalizedDateString(epgTag->EndAsLocalTime(), true);
        return true;
      case VIDEOPLAYER_ENDTIME:
      case VIDEOPLAYER_NEXT_ENDTIME:
      case LISTITEM_ENDTIME:
      case LISTITEM_NEXT_ENDTIME:
        strValue = GetAsLocalizedTimeString(epgTag->EndAsLocalTime());
        return true;
      // note: for some reason, there is no VIDEOPLAYER_DURATION
      case LISTITEM_DURATION:
      case VIDEOPLAYER_NEXT_DURATION:
      case LISTITEM_NEXT_DURATION:
        if (epgTag->GetDuration() > 0)
        {
          strValue = StringUtils::SecondsToTimeString(epgTag->GetDuration(), static_cast<TIME_FORMAT>(info.GetData4()));
          return true;
        }
        return false;
      case VIDEOPLAYER_IMDBNUMBER:
      case LISTITEM_IMDBNUMBER:
        strValue = epgTag->IMDBNumber();
        return true;
      case VIDEOPLAYER_ORIGINALTITLE:
      case LISTITEM_ORIGINALTITLE:
        if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
          strValue = epgTag->OriginalTitle();
        return true;
      case VIDEOPLAYER_YEAR:
      case LISTITEM_YEAR:
        if (epgTag->Year() > 0)
        {
          strValue = StringUtils::Format("%i", epgTag->Year());
          return true;
        }
        return false;
      case VIDEOPLAYER_SEASON:
      case LISTITEM_SEASON:
        if (epgTag->SeriesNumber() >= 0)
        {
          strValue = StringUtils::Format("%i", epgTag->SeriesNumber());
          return true;
        }
        return false;
      case VIDEOPLAYER_EPISODE:
      case LISTITEM_EPISODE:
        if (epgTag->EpisodeNumber() >= 0)
        {
          strValue = StringUtils::Format("%i", epgTag->EpisodeNumber());
          return true;
        }
        return false;
      case VIDEOPLAYER_EPISODENAME:
      case LISTITEM_EPISODENAME:
        if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
        {
          strValue = epgTag->EpisodeName();
          // fixup multiline episode name strings (which do not fit in any way in our GUI)
          StringUtils::Replace(strValue, "\n", ", ");
        }
        return true;
      case VIDEOPLAYER_CAST:
      case LISTITEM_CAST:
        strValue = epgTag->GetCastLabel();
        return true;
      case VIDEOPLAYER_DIRECTOR:
      case LISTITEM_DIRECTOR:
        strValue = epgTag->GetDirectorsLabel();
        return true;
      case VIDEOPLAYER_WRITER:
      case LISTITEM_WRITER:
        strValue = epgTag->GetWritersLabel();
        return true;
      case LISTITEM_EPG_EVENT_ICON:
        strValue = epgTag->Icon();
        return true;
      case VIDEOPLAYER_PARENTAL_RATING:
      case LISTITEM_PARENTAL_RATING:
        if (epgTag->ParentalRating() > 0)
        {
          strValue = StringUtils::Format("%i", epgTag->ParentalRating());
          return true;
        }
        return false;
      case VIDEOPLAYER_PREMIERED:
      case LISTITEM_PREMIERED:
        if (epgTag->FirstAired().IsValid())
        {
          strValue = epgTag->FirstAired().GetAsLocalizedDate();
          return true;
        }
        else if (epgTag->Year() > 0)
        {
          strValue = StringUtils::Format("%i", epgTag->Year());
          return true;
        }
        return false;
      case VIDEOPLAYER_RATING:
      case LISTITEM_RATING:
      {
        int iStarRating = epgTag->StarRating();
        if (iStarRating > 0)
        {
          strValue = StringUtils::FormatNumber(iStarRating);
          return true;
        }
        return false;
      }
    }
  }

  if (channel)
  {
    switch (info.m_info)
    {
      case MUSICPLAYER_CHANNEL_NAME:
      {
        const std::shared_ptr<CPVRRadioRDSInfoTag> rdsTag = channel->GetRadioRDSInfoTag();
        if (rdsTag)
        {
          strValue = rdsTag->GetProgStation();
          if (!strValue.empty())
            return true;
        }
        // fall-thru is intended
      }
      case VIDEOPLAYER_CHANNEL_NAME:
      case LISTITEM_CHANNEL_NAME:
        strValue = channel->ChannelName();
        return true;
      case MUSICPLAYER_CHANNEL_NUMBER:
      case VIDEOPLAYER_CHANNEL_NUMBER:
      case LISTITEM_CHANNEL_NUMBER:
        strValue = channel->ChannelNumber().FormattedChannelNumber();
        return true;
      case MUSICPLAYER_CHANNEL_GROUP:
      case VIDEOPLAYER_CHANNEL_GROUP:
      {
        CSingleLock lock(m_critSection);
        strValue = channel->IsRadio() ? m_strPlayingRadioGroup : m_strPlayingTVGroup;
        return true;
      }
    }
  }

  return false;
}

bool CPVRGUIInfo::GetPVRLabel(const CFileItem* item, const CGUIInfo& info, std::string& strValue) const
{
  CSingleLock lock(m_critSection);

  switch (info.m_info)
  {
    case PVR_EPG_EVENT_ICON:
    {
      const std::shared_ptr<CPVREpgInfoTag> epgTag = (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
      if (epgTag)
      {
        strValue = epgTag->Icon();
      }
      return true;
    }
    case PVR_EPG_EVENT_DURATION:
    {
      const std::shared_ptr<CPVREpgInfoTag> epgTag = (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
      strValue = m_timesInfo.GetEpgEventDuration(epgTag, static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    }
   case PVR_EPG_EVENT_ELAPSED_TIME:
    {
      const std::shared_ptr<CPVREpgInfoTag> epgTag = (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
      strValue = m_timesInfo.GetEpgEventElapsedTime(epgTag, static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    }
    case PVR_EPG_EVENT_REMAINING_TIME:
    {
      const std::shared_ptr<CPVREpgInfoTag> epgTag = (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
      strValue = m_timesInfo.GetEpgEventRemainingTime(epgTag, static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    }
    case PVR_EPG_EVENT_FINISH_TIME:
    {
      const std::shared_ptr<CPVREpgInfoTag> epgTag = (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
      strValue = m_timesInfo.GetEpgEventFinishTime(epgTag, static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    }
    case PVR_TIMESHIFT_START_TIME:
      strValue = m_timesInfo.GetTimeshiftStartTime(static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    case PVR_TIMESHIFT_END_TIME:
      strValue = m_timesInfo.GetTimeshiftEndTime(static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    case PVR_TIMESHIFT_PLAY_TIME:
      strValue = m_timesInfo.GetTimeshiftPlayTime(static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    case PVR_TIMESHIFT_OFFSET:
      strValue = m_timesInfo.GetTimeshiftOffset(static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    case PVR_TIMESHIFT_PROGRESS_DURATION:
      strValue = m_timesInfo.GetTimeshiftProgressDuration(static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    case PVR_TIMESHIFT_PROGRESS_START_TIME:
      strValue = m_timesInfo.GetTimeshiftProgressStartTime(static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    case PVR_TIMESHIFT_PROGRESS_END_TIME:
      strValue = m_timesInfo.GetTimeshiftProgressEndTime(static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    case PVR_EPG_EVENT_SEEK_TIME:
      strValue = m_timesInfo.GetEpgEventSeekTime(g_application.GetAppPlayer().GetSeekHandler().GetSeekSize(),
                                                     static_cast<TIME_FORMAT>(info.GetData1()));
      return true;
    case PVR_NOW_RECORDING_TITLE:
      strValue = m_anyTimersInfo.GetActiveTimerTitle();
      return true;
    case PVR_NOW_RECORDING_CHANNEL:
      strValue = m_anyTimersInfo.GetActiveTimerChannelName();
      return true;
    case PVR_NOW_RECORDING_CHAN_ICO:
      strValue = m_anyTimersInfo.GetActiveTimerChannelIcon();
      return true;
    case PVR_NOW_RECORDING_DATETIME:
      strValue = m_anyTimersInfo.GetActiveTimerDateTime();
      return true;
    case PVR_NEXT_RECORDING_TITLE:
      strValue = m_anyTimersInfo.GetNextTimerTitle();
      return true;
    case PVR_NEXT_RECORDING_CHANNEL:
      strValue = m_anyTimersInfo.GetNextTimerChannelName();
      return true;
    case PVR_NEXT_RECORDING_CHAN_ICO:
      strValue = m_anyTimersInfo.GetNextTimerChannelIcon();
      return true;
    case PVR_NEXT_RECORDING_DATETIME:
      strValue = m_anyTimersInfo.GetNextTimerDateTime();
      return true;
    case PVR_TV_NOW_RECORDING_TITLE:
      strValue = m_tvTimersInfo.GetActiveTimerTitle();
      return true;
    case PVR_TV_NOW_RECORDING_CHANNEL:
      strValue = m_tvTimersInfo.GetActiveTimerChannelName();
      return true;
    case PVR_TV_NOW_RECORDING_CHAN_ICO:
      strValue = m_tvTimersInfo.GetActiveTimerChannelIcon();
      return true;
    case PVR_TV_NOW_RECORDING_DATETIME:
      strValue = m_tvTimersInfo.GetActiveTimerDateTime();
      return true;
    case PVR_TV_NEXT_RECORDING_TITLE:
      strValue = m_tvTimersInfo.GetNextTimerTitle();
      return true;
    case PVR_TV_NEXT_RECORDING_CHANNEL:
      strValue = m_tvTimersInfo.GetNextTimerChannelName();
      return true;
    case PVR_TV_NEXT_RECORDING_CHAN_ICO:
      strValue = m_tvTimersInfo.GetNextTimerChannelIcon();
      return true;
    case PVR_TV_NEXT_RECORDING_DATETIME:
      strValue = m_tvTimersInfo.GetNextTimerDateTime();
      return true;
    case PVR_RADIO_NOW_RECORDING_TITLE:
      strValue = m_radioTimersInfo.GetActiveTimerTitle();
      return true;
    case PVR_RADIO_NOW_RECORDING_CHANNEL:
      strValue = m_radioTimersInfo.GetActiveTimerChannelName();
      return true;
    case PVR_RADIO_NOW_RECORDING_CHAN_ICO:
      strValue = m_radioTimersInfo.GetActiveTimerChannelIcon();
      return true;
    case PVR_RADIO_NOW_RECORDING_DATETIME:
      strValue = m_radioTimersInfo.GetActiveTimerDateTime();
      return true;
    case PVR_RADIO_NEXT_RECORDING_TITLE:
      strValue = m_radioTimersInfo.GetNextTimerTitle();
      return true;
    case PVR_RADIO_NEXT_RECORDING_CHANNEL:
      strValue = m_radioTimersInfo.GetNextTimerChannelName();
      return true;
    case PVR_RADIO_NEXT_RECORDING_CHAN_ICO:
      strValue = m_radioTimersInfo.GetNextTimerChannelIcon();
      return true;
    case PVR_RADIO_NEXT_RECORDING_DATETIME:
      strValue = m_radioTimersInfo.GetNextTimerDateTime();
      return true;
    case PVR_NEXT_TIMER:
      strValue = m_anyTimersInfo.GetNextTimer();
      return true;
    case PVR_ACTUAL_STREAM_SIG:
      CharInfoSignal(strValue);
      return true;
    case PVR_ACTUAL_STREAM_SNR:
      CharInfoSNR(strValue);
      return true;
    case PVR_ACTUAL_STREAM_BER:
      CharInfoBER(strValue);
      return true;
    case PVR_ACTUAL_STREAM_UNC:
      CharInfoUNC(strValue);
      return true;
    case PVR_ACTUAL_STREAM_CLIENT:
      CharInfoPlayingClientName(strValue);
      return true;
    case PVR_ACTUAL_STREAM_DEVICE:
      CharInfoFrontendName(strValue);
      return true;
    case PVR_ACTUAL_STREAM_STATUS:
      CharInfoFrontendStatus(strValue);
      return true;
    case PVR_ACTUAL_STREAM_CRYPTION:
      CharInfoEncryption(strValue);
      return true;
    case PVR_ACTUAL_STREAM_SERVICE:
      CharInfoService(strValue);
      return true;
    case PVR_ACTUAL_STREAM_MUX:
      CharInfoMux(strValue);
      return true;
    case PVR_ACTUAL_STREAM_PROVIDER:
      CharInfoProvider(strValue);
      return true;
    case PVR_BACKEND_NAME:
      CharInfoBackendName(strValue);
      return true;
    case PVR_BACKEND_VERSION:
      CharInfoBackendVersion(strValue);
      return true;
    case PVR_BACKEND_HOST:
      CharInfoBackendHost(strValue);
      return true;
    case PVR_BACKEND_DISKSPACE:
      CharInfoBackendDiskspace(strValue);
      return true;
    case PVR_BACKEND_CHANNELS:
      CharInfoBackendChannels(strValue);
      return true;
    case PVR_BACKEND_TIMERS:
      CharInfoBackendTimers(strValue);
      return true;
    case PVR_BACKEND_RECORDINGS:
      CharInfoBackendRecordings(strValue);
      return true;
    case PVR_BACKEND_DELETED_RECORDINGS:
      CharInfoBackendDeletedRecordings(strValue);
      return true;
    case PVR_BACKEND_NUMBER:
      CharInfoBackendNumber(strValue);
      return true;
    case PVR_TOTAL_DISKSPACE:
      CharInfoTotalDiskSpace(strValue);
      return true;
    case PVR_CHANNEL_NUMBER_INPUT:
      strValue = CServiceBroker::GetPVRManager().GUIActions()->GetChannelNumberInputHandler().GetChannelNumberLabel();
      return true;
  }

  return false;
}

bool CPVRGUIInfo::GetRadioRDSLabel(const CFileItem* item, const CGUIInfo& info, std::string& strValue) const
{
  if (!item->HasPVRChannelInfoTag())
    return false;

  const std::shared_ptr<CPVRRadioRDSInfoTag> tag = item->GetPVRChannelInfoTag()->GetRadioRDSInfoTag();
  if (tag)
  {
    switch (info.m_info)
    {
      case RDS_CHANNEL_COUNTRY:
        strValue = tag->GetCountry();
        return true;
      case RDS_TITLE:
        strValue = tag->GetTitle();
        return true;
      case RDS_ARTIST:
        strValue = tag->GetArtist();
        return true;
      case RDS_BAND:
        strValue = tag->GetBand();
        return true;
      case RDS_COMPOSER:
        strValue = tag->GetComposer();
        return true;
      case RDS_CONDUCTOR:
        strValue = tag->GetConductor();
        return true;
      case RDS_ALBUM:
        strValue = tag->GetAlbum();
        return true;
      case RDS_ALBUM_TRACKNUMBER:
        if (tag->GetAlbumTrackNumber() > 0)
        {
          strValue = StringUtils::Format("%i", tag->GetAlbumTrackNumber());
          return true;
        }
        break;
      case RDS_GET_RADIO_STYLE:
        strValue = tag->GetRadioStyle();
        return true;
      case RDS_COMMENT:
        strValue = tag->GetComment();
        return true;
      case RDS_INFO_NEWS:
        strValue = tag->GetInfoNews();
        return true;
      case RDS_INFO_NEWS_LOCAL:
        strValue = tag->GetInfoNewsLocal();
        return true;
      case RDS_INFO_STOCK:
        strValue = tag->GetInfoStock();
        return true;
      case RDS_INFO_STOCK_SIZE:
        strValue = StringUtils::Format("%i", static_cast<int>(tag->GetInfoStock().size()));
        return true;
      case RDS_INFO_SPORT:
        strValue = tag->GetInfoSport();
        return true;
      case RDS_INFO_SPORT_SIZE:
        strValue = StringUtils::Format("%i", static_cast<int>(tag->GetInfoSport().size()));
        return true;
      case RDS_INFO_LOTTERY:
        strValue = tag->GetInfoLottery();
        return true;
      case RDS_INFO_LOTTERY_SIZE:
        strValue = StringUtils::Format("%i", static_cast<int>(tag->GetInfoLottery().size()));
        return true;
      case RDS_INFO_WEATHER:
        strValue = tag->GetInfoWeather();
        return true;
      case RDS_INFO_WEATHER_SIZE:
        strValue = StringUtils::Format("%i", static_cast<int>(tag->GetInfoWeather().size()));
        return true;
      case RDS_INFO_HOROSCOPE:
        strValue = tag->GetInfoHoroscope();
        return true;
      case RDS_INFO_HOROSCOPE_SIZE:
        strValue = StringUtils::Format("%i", static_cast<int>(tag->GetInfoHoroscope().size()));
        return true;
      case RDS_INFO_CINEMA:
        strValue = tag->GetInfoCinema();
        return true;
      case RDS_INFO_CINEMA_SIZE:
        strValue = StringUtils::Format("%i", static_cast<int>(tag->GetInfoCinema().size()));
        return true;
      case RDS_INFO_OTHER:
        strValue = tag->GetInfoOther();
        return true;
      case RDS_INFO_OTHER_SIZE:
        strValue = StringUtils::Format("%i", static_cast<int>(tag->GetInfoOther().size()));
        return true;
      case RDS_PROG_HOST:
        strValue = tag->GetProgHost();
        return true;
      case RDS_PROG_EDIT_STAFF:
        strValue = tag->GetEditorialStaff();
        return true;
      case RDS_PROG_HOMEPAGE:
        strValue = tag->GetProgWebsite();
        return true;
      case RDS_PROG_STYLE:
        strValue = tag->GetProgStyle();
        return true;
      case RDS_PHONE_HOTLINE:
        strValue = tag->GetPhoneHotline();
        return true;
      case RDS_PHONE_STUDIO:
        strValue = tag->GetPhoneStudio();
        return true;
      case RDS_SMS_STUDIO:
        strValue = tag->GetSMSStudio();
        return true;
      case RDS_EMAIL_HOTLINE:
        strValue = tag->GetEMailHotline();
        return true;
      case RDS_EMAIL_STUDIO:
        strValue = tag->GetEMailStudio();
        return true;
      case RDS_PROG_STATION:
        strValue = tag->GetProgStation();
        return true;
      case RDS_PROG_NOW:
        strValue = tag->GetProgNow();
        return true;
      case RDS_PROG_NEXT:
        strValue = tag->GetProgNext();
        return true;
      case RDS_AUDIO_LANG:
        strValue = tag->GetLanguage();
        return true;
    }
  }

  switch (info.m_info)
  {
    case RDS_GET_RADIOTEXT_LINE:
      strValue = g_application.GetAppPlayer().GetRadioText(info.GetData1());
      return true;
  }
  return false;
}

bool CPVRGUIInfo::GetFallbackLabel(std::string& value,
                                   const CFileItem* item,
                                   int contextWindow,
                                   const CGUIInfo& info,
                                   std::string* fallback)
{
  if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
  {
    switch (info.m_info)
    {
      /////////////////////////////////////////////////////////////////////////////////////////////
      // VIDEOPLAYER_*, MUSICPLAYER_*
      /////////////////////////////////////////////////////////////////////////////////////////////
      case VIDEOPLAYER_TITLE:
      case MUSICPLAYER_TITLE:
        value = GetEpgTagTitle(CPVRItem(item).GetEpgInfoTag());
        return !value.empty();
      default:
        break;
    }
  }
  return false;
}

bool CPVRGUIInfo::GetInt(int& value, const CGUIListItem* item, int contextWindow, const CGUIInfo& info) const
{
  if (!item->IsFileItem())
    return false;

  const CFileItem* fitem = static_cast<const CFileItem*>(item);
  return GetListItemAndPlayerInt(fitem, info, value) ||
         GetPVRInt(fitem, info, value);
}

bool CPVRGUIInfo::GetListItemAndPlayerInt(const CFileItem* item, const CGUIInfo& info, int& iValue) const
{
  switch (info.m_info)
  {
    case LISTITEM_PROGRESS:
      if (item->IsPVRChannel() || item->IsEPG())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
        if (epgTag)
          iValue = static_cast<int>(epgTag->ProgressPercentage());
      }
      return true;
  }
  return false;
}

bool CPVRGUIInfo::GetPVRInt(const CFileItem* item, const CGUIInfo& info, int& iValue) const
{
  CSingleLock lock(m_critSection);

  switch (info.m_info)
  {
    case PVR_EPG_EVENT_DURATION:
    {
      const std::shared_ptr<CPVREpgInfoTag> epgTag = (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
      iValue = m_timesInfo.GetEpgEventDuration(epgTag);
      return true;
    }
    case PVR_EPG_EVENT_PROGRESS:
    {
      const std::shared_ptr<CPVREpgInfoTag> epgTag = (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
      iValue = m_timesInfo.GetEpgEventProgress(epgTag);
      return true;
    }
    case PVR_TIMESHIFT_PROGRESS:
      iValue = m_timesInfo.GetTimeshiftProgress();
      return true;
    case PVR_TIMESHIFT_PROGRESS_DURATION:
      iValue = m_timesInfo.GetTimeshiftProgressDuration();
      return true;
    case PVR_TIMESHIFT_PROGRESS_PLAY_POS:
      iValue = m_timesInfo.GetTimeshiftProgressPlayPosition();
      return true;
    case PVR_TIMESHIFT_PROGRESS_EPG_START:
      iValue = m_timesInfo.GetTimeshiftProgressEpgStart();
      return true;
    case PVR_TIMESHIFT_PROGRESS_EPG_END:
      iValue = m_timesInfo.GetTimeshiftProgressEpgEnd();
      return true;
    case PVR_TIMESHIFT_PROGRESS_BUFFER_START:
      iValue = m_timesInfo.GetTimeshiftProgressBufferStart();
      return true;
    case PVR_TIMESHIFT_PROGRESS_BUFFER_END:
      iValue = m_timesInfo.GetTimeshiftProgressBufferEnd();
      return true;
    case PVR_TIMESHIFT_SEEKBAR:
      iValue = GetTimeShiftSeekPercent();
      return true;
    case PVR_ACTUAL_STREAM_SIG_PROGR:
      iValue = std::lrintf(static_cast<float>(m_qualityInfo.iSignal) / 0xFFFF * 100);
      return true;
    case PVR_ACTUAL_STREAM_SNR_PROGR:
      iValue = std::lrintf(static_cast<float>(m_qualityInfo.iSNR) / 0xFFFF * 100);
      return true;
    case PVR_BACKEND_DISKSPACE_PROGR:
      if (m_iBackendDiskTotal > 0)
        iValue = std::lrintf(static_cast<float>(m_iBackendDiskUsed) / m_iBackendDiskTotal * 100);
      else
        iValue = 0xFF;
      return true;
  }
  return false;
}

bool CPVRGUIInfo::GetBool(bool& value, const CGUIListItem* item, int contextWindow, const CGUIInfo& info) const
{
  if (!item->IsFileItem())
    return false;

  const CFileItem* fitem = static_cast<const CFileItem*>(item);
  return GetListItemAndPlayerBool(fitem, info, value) ||
         GetPVRBool(fitem, info, value) ||
         GetRadioRDSBool(fitem, info, value);
}

bool CPVRGUIInfo::GetListItemAndPlayerBool(const CFileItem* item, const CGUIInfo& info, bool& bValue) const
{
  switch (info.m_info)
  {
    case LISTITEM_HASARCHIVE:
      if (item->IsPVRChannel())
      {
        bValue = item->GetPVRChannelInfoTag()->HasArchive();
        return true;
      }
      break;
    case LISTITEM_ISPLAYABLE:
      if (item->IsEPG())
      {
        bValue = item->GetEPGInfoTag()->IsPlayable();
        return true;
      }
      break;
    case LISTITEM_ISRECORDING:
      if (item->IsPVRChannel())
      {
        bValue = CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*item->GetPVRChannelInfoTag());
        return true;
      }
      else if (item->IsEPG() || item->IsPVRTimer())
      {
        const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
        if (timer)
          bValue = timer->IsRecording();
        return true;
      }
      else if (item->IsPVRRecording())
      {
        bValue = item->GetPVRRecordingInfoTag()->IsInProgress();
        return true;
      }
      break;
    case LISTITEM_INPROGRESS:
      if (item->IsPVRChannel() || item->IsEPG())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
        if (epgTag)
          bValue = epgTag->IsActive();
        return true;
      }
      break;
    case LISTITEM_HASTIMER:
      if (item->IsPVRChannel() || item->IsEPG())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
        if (epgTag)
          bValue = !!CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag);
        return true;
      }
      break;
    case LISTITEM_HASTIMERSCHEDULE:
      if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
      {
        const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
        if (timer)
          bValue = timer->GetTimerRuleId() != PVR_TIMER_NO_PARENT;
        return true;
      }
      break;
    case LISTITEM_HASREMINDER:
      if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
      {
        const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
        if (timer)
          bValue = timer->IsReminder();
        return true;
      }
      break;
    case LISTITEM_HASREMINDERRULE:
      if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
      {
        const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
        if (timer)
          bValue = timer->IsReminder() && (timer->GetTimerRuleId() != PVR_TIMER_NO_PARENT);
        return true;
      }
      break;
    case LISTITEM_TIMERISACTIVE:
      if (item->IsPVRChannel() || item->IsEPG())
      {
        const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
        if (timer)
          bValue = timer->IsActive();
        break;
      }
      break;
    case LISTITEM_TIMERHASCONFLICT:
      if (item->IsPVRChannel() || item->IsEPG())
      {
        const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
        if (timer)
          bValue = timer->HasConflict();
        return true;
      }
      break;
    case LISTITEM_TIMERHASERROR:
      if (item->IsPVRChannel() || item->IsEPG())
      {
        const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
        if (timer)
          bValue = (timer->IsBroken() && !timer->HasConflict());
        return true;
      }
      break;
    case LISTITEM_HASRECORDING:
      if (item->IsPVRChannel() || item->IsEPG())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
        if (epgTag)
          bValue = !!CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(epgTag);
        return true;
      }
      break;
    case LISTITEM_HAS_EPG:
      if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
        bValue = (epgTag != nullptr);
        return true;
      }
      break;
    case LISTITEM_ISENCRYPTED:
      if (item->IsPVRChannel() || item->IsEPG())
      {
        const std::shared_ptr<CPVRChannel> channel = CPVRItem(item).GetChannel();
        if (channel)
          bValue = channel->IsEncrypted();
        return true;
      }
      break;
    case LISTITEM_IS_NEW:
      if (item->IsEPG())
      {
        if (item->GetEPGInfoTag())
        {
          bValue = item->GetEPGInfoTag()->IsNew();
          return true;
        }
      }
      else if (item->IsPVRRecording())
      {
        bValue = item->GetPVRRecordingInfoTag()->IsNew();
        return true;
      }
      else if (item->IsPVRTimer() && item->GetPVRTimerInfoTag()->GetEpgInfoTag())
      {
        bValue = item->GetPVRTimerInfoTag()->GetEpgInfoTag()->IsNew();
        return true;
      }
      else if (item->IsPVRChannel())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgNow = item->GetPVRChannelInfoTag()->GetEPGNow();
        bValue = epgNow ? epgNow->IsNew() : false;
        return true;
      }
      break;
    case LISTITEM_IS_PREMIERE:
      if (item->IsEPG())
      {
        bValue = item->GetEPGInfoTag()->IsPremiere();
        return true;
      }
      else if (item->IsPVRRecording())
      {
        bValue = item->GetPVRRecordingInfoTag()->IsPremiere();
        return true;
      }
      else if (item->IsPVRTimer() && item->GetPVRTimerInfoTag()->GetEpgInfoTag())
      {
        bValue = item->GetPVRTimerInfoTag()->GetEpgInfoTag()->IsPremiere();
        return true;
      }
      else if (item->IsPVRChannel())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgNow = item->GetPVRChannelInfoTag()->GetEPGNow();
        bValue = epgNow ? epgNow->IsPremiere() : false;
        return true;
      }
      break;
    case LISTITEM_IS_FINALE:
      if (item->IsEPG())
      {
        bValue = item->GetEPGInfoTag()->IsFinale();
        return true;
      }
      else if (item->IsPVRRecording())
      {
        bValue = item->GetPVRRecordingInfoTag()->IsFinale();
        return true;
      }
      else if (item->IsPVRTimer() && item->GetPVRTimerInfoTag()->GetEpgInfoTag())
      {
        bValue = item->GetPVRTimerInfoTag()->GetEpgInfoTag()->IsFinale();
        return true;
      }
      else if (item->IsPVRChannel())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgNow = item->GetPVRChannelInfoTag()->GetEPGNow();
        bValue = epgNow ? epgNow->IsFinale() : false;
        return true;
      }
      break;
    case LISTITEM_IS_LIVE:
      if (item->IsEPG())
      {
        bValue = item->GetEPGInfoTag()->IsLive();
        return true;
      }
      else if (item->IsPVRRecording())
      {
        bValue = item->GetPVRRecordingInfoTag()->IsLive();
        return true;
      }
      else if (item->IsPVRTimer() && item->GetPVRTimerInfoTag()->GetEpgInfoTag())
      {
        bValue = item->GetPVRTimerInfoTag()->GetEpgInfoTag()->IsLive();
        return true;
      }
      else if (item->IsPVRChannel())
      {
        const std::shared_ptr<CPVREpgInfoTag> epgNow = item->GetPVRChannelInfoTag()->GetEPGNow();
        bValue = epgNow ? epgNow->IsLive() : false;
        return true;
      }
      break;
    case MUSICPLAYER_CONTENT:
    case VIDEOPLAYER_CONTENT:
      if (item->IsPVRChannel())
      {
        bValue = StringUtils::EqualsNoCase(info.GetData3(), "livetv");
        return bValue; // if no match for this provider, other providers shall be asked.
      }
      break;
    case VIDEOPLAYER_HAS_INFO:
      if (item->IsPVRChannel())
      {
        bValue = !item->GetPVRChannelInfoTag()->IsEmpty();
        return true;
      }
      break;
    case VIDEOPLAYER_HAS_EPG:
      if (item->IsPVRChannel())
      {
        bValue = (item->GetPVRChannelInfoTag()->GetEPGNow() != nullptr);
        return true;
      }
      break;
    case VIDEOPLAYER_CAN_RESUME_LIVE_TV:
      if (item->IsPVRRecording())
      {
        const std::shared_ptr<CPVRRecording> recording = item->GetPVRRecordingInfoTag();
        const std::shared_ptr<CPVREpg> epg = recording->Channel() ? recording->Channel()->GetEPG() : nullptr;
        const std::shared_ptr<CPVREpgInfoTag> epgTag = CServiceBroker::GetPVRManager().EpgContainer().GetTagById(epg, recording->BroadcastUid());
        bValue = (epgTag && epgTag->IsActive());
        return true;
      }
      break;
    case PLAYER_IS_CHANNEL_PREVIEW_ACTIVE:
      if (item->IsPVRChannel())
      {
        if (CServiceBroker::GetPVRManager().GUIActions()->GetChannelNavigator().IsPreviewAndShowInfo())
        {
          bValue = true;
        }
        else
        {
          bValue = !m_videoInfo.valid;
          if (bValue && item->GetPVRChannelInfoTag()->IsRadio())
            bValue = !m_audioInfo.valid;
        }
        return true;
      }
      break;
  }
  return false;
}

bool CPVRGUIInfo::GetPVRBool(const CFileItem* item, const CGUIInfo& info, bool& bValue) const
{
  CSingleLock lock(m_critSection);

  switch (info.m_info)
  {
    case PVR_IS_RECORDING:
      bValue = m_anyTimersInfo.HasRecordingTimers();
      return true;
    case PVR_IS_RECORDING_TV:
      bValue = m_tvTimersInfo.HasRecordingTimers();
      return true;
    case PVR_IS_RECORDING_RADIO:
      bValue = m_radioTimersInfo.HasRecordingTimers();
      return true;
    case PVR_HAS_TIMER:
      bValue = m_anyTimersInfo.HasTimers();
      return true;
    case PVR_HAS_TV_TIMER:
      bValue = m_tvTimersInfo.HasTimers();
      return true;
    case PVR_HAS_RADIO_TIMER:
      bValue = m_radioTimersInfo.HasTimers();
      return true;
    case PVR_HAS_TV_CHANNELS:
      bValue = m_bHasTVChannels;
      return true;
    case PVR_HAS_RADIO_CHANNELS:
      bValue = m_bHasRadioChannels;
      return true;
    case PVR_HAS_NONRECORDING_TIMER:
      bValue = m_anyTimersInfo.HasNonRecordingTimers();
      return true;
    case PVR_HAS_NONRECORDING_TV_TIMER:
      bValue = m_tvTimersInfo.HasNonRecordingTimers();
      return true;
    case PVR_HAS_NONRECORDING_RADIO_TIMER:
      bValue = m_radioTimersInfo.HasNonRecordingTimers();
      return true;
    case PVR_IS_PLAYING_TV:
      bValue = m_bIsPlayingTV;
      return true;
    case PVR_IS_PLAYING_RADIO:
      bValue = m_bIsPlayingRadio;
      return true;
    case PVR_IS_PLAYING_RECORDING:
      bValue = m_bIsPlayingRecording;
      return true;
    case PVR_IS_PLAYING_EPGTAG:
      bValue = m_bIsPlayingEpgTag;
      return true;
    case PVR_ACTUAL_STREAM_ENCRYPTED:
      bValue = m_bIsPlayingEncryptedStream;
      return true;
    case PVR_IS_TIMESHIFTING:
      bValue = m_timesInfo.IsTimeshifting();
      return true;
    case PVR_CAN_RECORD_PLAYING_CHANNEL:
      bValue = m_bCanRecordPlayingChannel;
      return true;
    case PVR_IS_RECORDING_PLAYING_CHANNEL:
      bValue = m_bIsRecordingPlayingChannel;
      return true;
    case PVR_IS_PLAYING_ACTIVE_RECORDING:
      bValue = m_bIsPlayingActiveRecording;
      return true;
  }
  return false;
}

bool CPVRGUIInfo::GetRadioRDSBool(const CFileItem* item, const CGUIInfo& info, bool& bValue) const
{
  if (!item->HasPVRChannelInfoTag())
    return false;

  const std::shared_ptr<CPVRRadioRDSInfoTag> tag = item->GetPVRChannelInfoTag()->GetRadioRDSInfoTag();
  if (tag)
  {
    switch (info.m_info)
    {
      case RDS_HAS_RADIOTEXT:
        bValue = tag->IsPlayingRadiotext();
        return true;
      case RDS_HAS_RADIOTEXT_PLUS:
        bValue = tag->IsPlayingRadiotextPlus();
        return true;
      case RDS_HAS_HOTLINE_DATA:
        bValue = (!tag->GetEMailHotline().empty() || !tag->GetPhoneHotline().empty());
        return true;
      case RDS_HAS_STUDIO_DATA:
        bValue = (!tag->GetEMailStudio().empty() || !tag->GetSMSStudio().empty() || !tag->GetPhoneStudio().empty());
        return true;
    }
  }

  switch (info.m_info)
  {
    case RDS_HAS_RDS:
      bValue = g_application.GetAppPlayer().IsPlayingRDS();
      return true;
  }

  return false;
}

void CPVRGUIInfo::CharInfoBackendNumber(std::string& strValue) const
{
  size_t numBackends = m_backendProperties.size();

  if (numBackends > 0)
    strValue = StringUtils::Format("{0} {1} {2}", m_iCurrentActiveClient + 1, g_localizeStrings.Get(20163).c_str(), numBackends);
  else
    strValue = g_localizeStrings.Get(14023);
}

void CPVRGUIInfo::CharInfoTotalDiskSpace(std::string& strValue) const
{
  strValue = StringUtils::SizeToString(m_iBackendDiskTotal).c_str();
}

void CPVRGUIInfo::CharInfoSignal(std::string& strValue) const
{
  strValue = StringUtils::Format("%d %%", m_qualityInfo.iSignal / 655);
}

void CPVRGUIInfo::CharInfoSNR(std::string& strValue) const
{
  strValue = StringUtils::Format("%d %%", m_qualityInfo.iSNR / 655);
}

void CPVRGUIInfo::CharInfoBER(std::string& strValue) const
{
  strValue = StringUtils::Format("%08lX", m_qualityInfo.iBER);
}

void CPVRGUIInfo::CharInfoUNC(std::string& strValue) const
{
  strValue = StringUtils::Format("%08lX", m_qualityInfo.iUNC);
}

void CPVRGUIInfo::CharInfoFrontendName(std::string& strValue) const
{
  if (!strlen(m_qualityInfo.strAdapterName))
    strValue = g_localizeStrings.Get(13205);
  else
    strValue = m_qualityInfo.strAdapterName;
}

void CPVRGUIInfo::CharInfoFrontendStatus(std::string& strValue) const
{
  if (!strlen(m_qualityInfo.strAdapterStatus))
    strValue = g_localizeStrings.Get(13205);
  else
    strValue = m_qualityInfo.strAdapterStatus;
}

void CPVRGUIInfo::CharInfoBackendName(std::string& strValue) const
{
  m_updateBackendCacheRequested = true;
  strValue = m_strBackendName;
}

void CPVRGUIInfo::CharInfoBackendVersion(std::string& strValue) const
{
  m_updateBackendCacheRequested = true;
  strValue = m_strBackendVersion;
}

void CPVRGUIInfo::CharInfoBackendHost(std::string& strValue) const
{
  m_updateBackendCacheRequested = true;
  strValue = m_strBackendHost;
}

void CPVRGUIInfo::CharInfoBackendDiskspace(std::string& strValue) const
{
  m_updateBackendCacheRequested = true;

  auto diskTotal = m_iBackendDiskTotal;
  auto diskUsed = m_iBackendDiskUsed;

  if (diskTotal > 0)
  {
    strValue = StringUtils::Format(g_localizeStrings.Get(802).c_str(),
        StringUtils::SizeToString(diskTotal - diskUsed).c_str(),
        StringUtils::SizeToString(diskTotal).c_str());
  }
  else
    strValue = g_localizeStrings.Get(13205);
}

void CPVRGUIInfo::CharInfoBackendChannels(std::string& strValue) const
{
  m_updateBackendCacheRequested = true;
  strValue = m_strBackendChannels;
}

void CPVRGUIInfo::CharInfoBackendTimers(std::string& strValue) const
{
  m_updateBackendCacheRequested = true;
  strValue = m_strBackendTimers;
}

void CPVRGUIInfo::CharInfoBackendRecordings(std::string& strValue) const
{
  m_updateBackendCacheRequested = true;
  strValue = m_strBackendRecordings;
}

void CPVRGUIInfo::CharInfoBackendDeletedRecordings(std::string& strValue) const
{
  m_updateBackendCacheRequested = true;
  strValue = m_strBackendDeletedRecordings;
}

void CPVRGUIInfo::CharInfoPlayingClientName(std::string& strValue) const
{
  if (m_strPlayingClientName.empty())
    strValue = g_localizeStrings.Get(13205);
  else
    strValue = m_strPlayingClientName;
}

void CPVRGUIInfo::CharInfoEncryption(std::string& strValue) const
{
  if (m_descrambleInfo.iCaid != PVR_DESCRAMBLE_INFO_NOT_AVAILABLE)
  {
    // prefer dynamically updated info, if available
    strValue = CPVRChannel::GetEncryptionName(m_descrambleInfo.iCaid);
    return;
  }
  else
  {
    const std::shared_ptr<CPVRChannel> channel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
    if (channel)
    {
      strValue = channel->EncryptionName();
      return;
    }
  }

  strValue.clear();
}

void CPVRGUIInfo::CharInfoService(std::string& strValue) const
{
  if (!strlen(m_qualityInfo.strServiceName))
    strValue = g_localizeStrings.Get(13205);
  else
    strValue = m_qualityInfo.strServiceName;
}

void CPVRGUIInfo::CharInfoMux(std::string& strValue) const
{
  if (!strlen(m_qualityInfo.strMuxName))
    strValue = g_localizeStrings.Get(13205);
  else
    strValue = m_qualityInfo.strMuxName;
}

void CPVRGUIInfo::CharInfoProvider(std::string& strValue) const
{
  if (!strlen(m_qualityInfo.strProviderName))
    strValue = g_localizeStrings.Get(13205);
  else
    strValue = m_qualityInfo.strProviderName;
}

void CPVRGUIInfo::UpdateBackendCache()
{
  CSingleLock lock(m_critSection);

  // Update the backend information for all backends if
  // an update has been requested
  if (m_iCurrentActiveClient == 0 && m_updateBackendCacheRequested)
  {
    std::vector<SBackend> backendProperties;
    {
      CSingleExit exit(m_critSection);
      backendProperties = CServiceBroker::GetPVRManager().Clients()->GetBackendProperties();
    }

    m_backendProperties = backendProperties;
    m_updateBackendCacheRequested = false;
  }

  // Store some defaults
  m_strBackendName = g_localizeStrings.Get(13205);
  m_strBackendVersion = g_localizeStrings.Get(13205);
  m_strBackendHost = g_localizeStrings.Get(13205);
  m_strBackendChannels = g_localizeStrings.Get(13205);
  m_strBackendTimers = g_localizeStrings.Get(13205);
  m_strBackendRecordings = g_localizeStrings.Get(13205);
  m_strBackendDeletedRecordings = g_localizeStrings.Get(13205);
  m_iBackendDiskTotal = 0;
  m_iBackendDiskUsed = 0;

  // Update with values from the current client when we have at least one
  if (!m_backendProperties.empty())
  {
    const auto& backend = m_backendProperties[m_iCurrentActiveClient];

    m_strBackendName = backend.name;
    m_strBackendVersion = backend.version;
    m_strBackendHost = backend.host;

    if (backend.numChannels >= 0)
      m_strBackendChannels = StringUtils::Format("%i", backend.numChannels);

    if (backend.numTimers >= 0)
      m_strBackendTimers = StringUtils::Format("%i", backend.numTimers);

    if (backend.numRecordings >= 0)
      m_strBackendRecordings = StringUtils::Format("%i", backend.numRecordings);

    if (backend.numDeletedRecordings >= 0)
      m_strBackendDeletedRecordings = StringUtils::Format("%i", backend.numDeletedRecordings);

    m_iBackendDiskTotal = backend.diskTotal;
    m_iBackendDiskUsed = backend.diskUsed;
  }

  // Update the current active client, eventually wrapping around
  if (++m_iCurrentActiveClient >= m_backendProperties.size())
    m_iCurrentActiveClient = 0;
}

void CPVRGUIInfo::UpdateTimersCache()
{
  m_anyTimersInfo.UpdateTimersCache();
  m_tvTimersInfo.UpdateTimersCache();
  m_radioTimersInfo.UpdateTimersCache();
}

void CPVRGUIInfo::UpdateTimersToggle()
{
  m_anyTimersInfo.UpdateTimersToggle();
  m_tvTimersInfo.UpdateTimersToggle();
  m_radioTimersInfo.UpdateTimersToggle();
}

void CPVRGUIInfo::UpdateNextTimer()
{
  m_anyTimersInfo.UpdateNextTimer();
  m_tvTimersInfo.UpdateNextTimer();
  m_radioTimersInfo.UpdateNextTimer();
}

int CPVRGUIInfo::GetTimeShiftSeekPercent() const
{
  int progress = m_timesInfo.GetTimeshiftProgressPlayPosition();

  int seekSize = g_application.GetAppPlayer().GetSeekHandler().GetSeekSize();
  if (seekSize != 0)
  {
    int total = m_timesInfo.GetTimeshiftProgressDuration();

    float totalTime = static_cast<float>(total);
    if (totalTime == 0.0f)
      return 0;

    float percentPerSecond = 100.0f / totalTime;
    float percent = progress + percentPerSecond * seekSize;
    percent = std::max(0.0f, std::min(percent, 100.0f));
    return std::lrintf(percent);
  }
  return progress;
}
