/*
 *  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 "PVRTimerInfoTag.h"

#include "ServiceBroker.h"
#include "guilib/LocalizeStrings.h"
#include "pvr/PVRDatabase.h"
#include "pvr/PVRManager.h"
#include "pvr/addons/PVRClient.h"
#include "pvr/addons/PVRClients.h"
#include "pvr/channels/PVRChannel.h"
#include "pvr/channels/PVRChannelGroups.h"
#include "pvr/channels/PVRChannelGroupsContainer.h"
#include "pvr/epg/Epg.h"
#include "pvr/epg/EpgInfoTag.h"
#include "pvr/timers/PVRTimersPath.h"
#include "settings/AdvancedSettings.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/Variant.h"
#include "utils/log.h"

#include <ctime>
#include <memory>
#include <string>

using namespace PVR;

CPVRTimerInfoTag::CPVRTimerInfoTag(bool bRadio /* = false */) :
  m_strTitle(g_localizeStrings.Get(19056)), // New Timer
  m_iClientId(CServiceBroker::GetPVRManager().Clients()->GetFirstCreatedClientID()),
  m_iClientIndex(PVR_TIMER_NO_CLIENT_INDEX),
  m_iParentClientIndex(PVR_TIMER_NO_PARENT),
  m_iClientChannelUid(PVR_CHANNEL_INVALID_UID),
  m_iPriority(DEFAULT_RECORDING_PRIORITY),
  m_iLifetime(DEFAULT_RECORDING_LIFETIME),
  m_iPreventDupEpisodes(DEFAULT_RECORDING_DUPLICATEHANDLING),
  m_bIsRadio(bRadio),
  m_iMarginStart(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_PVRRECORD_MARGINSTART)),
  m_iMarginEnd(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_PVRRECORD_MARGINEND)),
  m_iEpgUid(EPG_TAG_INVALID_UID),
  m_StartTime(CDateTime::GetUTCDateTime()),
  m_StopTime(m_StartTime + CDateTimeSpan(0, 2, 0, 0)),
  m_FirstDay(m_StartTime)
{
  static const uint64_t iMustHaveAttr = PVR_TIMER_TYPE_IS_MANUAL;
  static const uint64_t iMustNotHaveAttr =
      PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES;

  std::shared_ptr<CPVRTimerType> type;

  const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
  if (client && client->GetClientCapabilities().SupportsTimers())
  {
    // default to first available type for given client
    type = CPVRTimerType::GetFirstAvailableType(client);
  }

  // fallback to manual one-shot reminder, which is always available
  if (!type)
    type = CPVRTimerType::CreateFromAttributes(iMustHaveAttr | PVR_TIMER_TYPE_IS_REMINDER, iMustNotHaveAttr, m_iClientId);

  if (type)
    SetTimerType(type);
  else
    CLog::LogF(LOGERROR, "Unable to obtain timer type!");

  m_iWeekdays = (m_timerType && m_timerType->IsTimerRule()) ? PVR_WEEKDAY_ALLDAYS : PVR_WEEKDAY_NONE;

  UpdateSummary();
}

CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer, const std::shared_ptr<CPVRChannel>& channel, unsigned int iClientId) :
  m_strTitle(timer.strTitle),
  m_strEpgSearchString(timer.strEpgSearchString),
  m_bFullTextEpgSearch(timer.bFullTextEpgSearch),
  m_strDirectory(timer.strDirectory),
  m_state(timer.state),
  m_iClientId(iClientId),
  m_iClientIndex(timer.iClientIndex),
  m_iParentClientIndex(timer.iParentClientIndex),
  m_iClientChannelUid(channel ? channel->UniqueID() : (timer.iClientChannelUid > 0) ? timer.iClientChannelUid : PVR_CHANNEL_INVALID_UID),
  m_bStartAnyTime(timer.bStartAnyTime),
  m_bEndAnyTime(timer.bEndAnyTime),
  m_iPriority(timer.iPriority),
  m_iLifetime(timer.iLifetime),
  m_iMaxRecordings(timer.iMaxRecordings),
  m_iWeekdays(timer.iWeekdays),
  m_iPreventDupEpisodes(timer.iPreventDuplicateEpisodes),
  m_iRecordingGroup(timer.iRecordingGroup),
  m_strFileNameAndPath(StringUtils::Format("pvr://client%i/timers/%i", m_iClientId, m_iClientIndex)),
  m_bIsRadio(channel && channel->IsRadio()),
  m_iMarginStart(timer.iMarginStart),
  m_iMarginEnd(timer.iMarginEnd),
  m_iEpgUid(timer.iEpgUid),
  m_strSeriesLink(timer.strSeriesLink),
  m_StartTime(timer.startTime + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection),
  m_StopTime(timer.endTime + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection),
  m_channel(channel)
{
  if (timer.firstDay)
    m_FirstDay = CDateTime(timer.firstDay + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection);
  else
    m_FirstDay = CDateTime::GetUTCDateTime();

  if (m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX)
    CLog::LogF(LOGERROR, "Invalid client index supplied by client %d (must be > 0)!", m_iClientId);

  const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
  if (client && client->GetClientCapabilities().SupportsTimers())
  {
    // begin compat section

    // Create timer type according to certain timer values already available in Isengard.
    // This is for migration only and does not make changes to the addons obsolete. Addons should
    // work and benefit from some UI changes (e.g. some of the timer settings dialog enhancements),
    // but all old problems/bugs due to static attributes and values will remain the same as in
    // Isengard. Also, new features (like epg search) are not available to addons automatically.
    // This code can be removed once all addons actually support the respective PVR Addon API version.
    if (timer.iTimerType == PVR_TIMER_TYPE_NONE)
    {
      // Create type according to certain timer values.
      uint64_t iMustHave = PVR_TIMER_TYPE_ATTRIBUTE_NONE;
      uint64_t iMustNotHave = PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES;

      if (timer.iEpgUid == PVR_TIMER_NO_EPG_UID && timer.iWeekdays != PVR_WEEKDAY_NONE)
        iMustHave |= PVR_TIMER_TYPE_IS_REPEATING;
      else
        iMustNotHave |= PVR_TIMER_TYPE_IS_REPEATING;

      if (timer.iEpgUid == PVR_TIMER_NO_EPG_UID)
        iMustHave |= PVR_TIMER_TYPE_IS_MANUAL;
      else
        iMustNotHave |= PVR_TIMER_TYPE_IS_MANUAL;

      const std::shared_ptr<CPVRTimerType> type = CPVRTimerType::CreateFromAttributes(iMustHave, iMustNotHave, m_iClientId);

      if (type)
        SetTimerType(type);
    }
    // end compat section
    else
    {
      SetTimerType(CPVRTimerType::CreateFromIds(timer.iTimerType, m_iClientId));
    }

    if (!m_timerType)
      CLog::LogF(LOGERROR, "No timer type, although timers are supported by client %d!", m_iClientId);
    else if (m_iEpgUid == EPG_TAG_INVALID_UID && m_timerType->IsEpgBasedOnetime())
      CLog::LogF(LOGERROR, "No epg tag given for epg based timer type (%d)!", m_timerType->GetTypeId());
  }

  UpdateSummary();
  UpdateEpgInfoTag();
}

bool CPVRTimerInfoTag::operator ==(const CPVRTimerInfoTag& right) const
{
  bool bChannelsMatch = true;
  if (m_channel && right.m_channel)
    bChannelsMatch = *m_channel == *right.m_channel;
  else if (m_channel != right.m_channel)
    bChannelsMatch = false;

  return (bChannelsMatch &&
          m_iClientIndex == right.m_iClientIndex &&
          m_iParentClientIndex == right.m_iParentClientIndex &&
          m_strSummary == right.m_strSummary &&
          m_iClientChannelUid == right.m_iClientChannelUid &&
          m_bIsRadio == right.m_bIsRadio &&
          m_iPreventDupEpisodes == right.m_iPreventDupEpisodes &&
          m_iRecordingGroup == right.m_iRecordingGroup &&
          m_StartTime == right.m_StartTime &&
          m_StopTime == right.m_StopTime &&
          m_bStartAnyTime == right.m_bStartAnyTime &&
          m_bEndAnyTime == right.m_bEndAnyTime &&
          m_FirstDay == right.m_FirstDay &&
          m_iWeekdays == right.m_iWeekdays &&
          m_iPriority == right.m_iPriority &&
          m_iLifetime == right.m_iLifetime &&
          m_iMaxRecordings == right.m_iMaxRecordings &&
          m_strFileNameAndPath == right.m_strFileNameAndPath &&
          m_strTitle == right.m_strTitle &&
          m_strEpgSearchString == right.m_strEpgSearchString &&
          m_bFullTextEpgSearch == right.m_bFullTextEpgSearch &&
          m_strDirectory == right.m_strDirectory &&
          m_iClientId == right.m_iClientId &&
          m_iMarginStart == right.m_iMarginStart &&
          m_iMarginEnd == right.m_iMarginEnd &&
          m_state == right.m_state &&
          m_timerType == right.m_timerType &&
          m_iTimerId == right.m_iTimerId &&
          m_strSeriesLink == right.m_strSeriesLink &&
          m_iEpgUid == right.m_iEpgUid &&
          m_iTVChildTimersActive == right.m_iTVChildTimersActive &&
          m_iTVChildTimersConflictNOK == right.m_iTVChildTimersConflictNOK &&
          m_iTVChildTimersRecording == right.m_iTVChildTimersRecording &&
          m_iTVChildTimersErrors == right.m_iTVChildTimersErrors &&
          m_iRadioChildTimersActive == right.m_iRadioChildTimersActive &&
          m_iRadioChildTimersConflictNOK == right.m_iRadioChildTimersConflictNOK &&
          m_iRadioChildTimersRecording == right.m_iRadioChildTimersRecording &&
          m_iRadioChildTimersErrors == right.m_iRadioChildTimersErrors);
}

/**
 * Compare not equal for two CPVRTimerInfoTag
 */
bool CPVRTimerInfoTag::operator !=(const CPVRTimerInfoTag& right) const
{
  return !(*this == right);
}

void CPVRTimerInfoTag::Serialize(CVariant& value) const
{
  value["channelid"] = m_channel != NULL ? m_channel->ChannelID() : -1;
  value["summary"] = m_strSummary;
  value["isradio"] = m_bIsRadio;
  value["preventduplicateepisodes"] = m_iPreventDupEpisodes;
  value["starttime"] = m_StartTime.IsValid() ? m_StartTime.GetAsDBDateTime() : "";
  value["endtime"] = m_StopTime.IsValid() ? m_StopTime.GetAsDBDateTime() : "";
  value["startanytime"] = m_bStartAnyTime;
  value["endanytime"] = m_bEndAnyTime;
  value["runtime"] = m_StartTime.IsValid() && m_StopTime.IsValid() ? (m_StopTime - m_StartTime).GetSecondsTotal() : 0;
  value["firstday"] = m_FirstDay.IsValid() ? m_FirstDay.GetAsDBDate() : "";

  CVariant weekdays(CVariant::VariantTypeArray);
  if (m_iWeekdays & PVR_WEEKDAY_MONDAY)
    weekdays.push_back("monday");
  if (m_iWeekdays & PVR_WEEKDAY_TUESDAY)
    weekdays.push_back("tuesday");
  if (m_iWeekdays & PVR_WEEKDAY_WEDNESDAY)
    weekdays.push_back("wednesday");
  if (m_iWeekdays & PVR_WEEKDAY_THURSDAY)
    weekdays.push_back("thursday");
  if (m_iWeekdays & PVR_WEEKDAY_FRIDAY)
    weekdays.push_back("friday");
  if (m_iWeekdays & PVR_WEEKDAY_SATURDAY)
    weekdays.push_back("saturday");
  if (m_iWeekdays & PVR_WEEKDAY_SUNDAY)
    weekdays.push_back("sunday");
  value["weekdays"] = weekdays;

  value["priority"] = m_iPriority;
  value["lifetime"] = m_iLifetime;
  value["title"] = m_strTitle;
  value["directory"] = m_strDirectory;
  value["startmargin"] = m_iMarginStart;
  value["endmargin"] = m_iMarginEnd;

  value["timerid"] = m_iTimerId;

  switch (m_state)
  {
  case PVR_TIMER_STATE_NEW:
    value["state"] = "new";
    break;
  case PVR_TIMER_STATE_SCHEDULED:
    value["state"] = "scheduled";
    break;
  case PVR_TIMER_STATE_RECORDING:
    value["state"] = "recording";
    break;
  case PVR_TIMER_STATE_COMPLETED:
    value["state"] = "completed";
    break;
  case PVR_TIMER_STATE_ABORTED:
    value["state"] = "aborted";
    break;
  case PVR_TIMER_STATE_CANCELLED:
    value["state"] = "cancelled";
    break;
  case PVR_TIMER_STATE_CONFLICT_OK:
    value["state"] = "conflict_ok";
    break;
  case PVR_TIMER_STATE_CONFLICT_NOK:
    value["state"] = "conflict_notok";
    break;
  case PVR_TIMER_STATE_ERROR:
    value["state"] = "error";
    break;
  case PVR_TIMER_STATE_DISABLED:
    value["state"] = "disabled";
    break;
  default:
    value["state"] = "unknown";
    break;
  }

  value["istimerrule"] = m_timerType && m_timerType->IsTimerRule();
  value["ismanual"] = m_timerType && m_timerType->IsManual();
  value["isreadonly"] = m_timerType && m_timerType->IsReadOnly();

  value["epgsearchstring"]   = m_strEpgSearchString;
  value["fulltextepgsearch"] = m_bFullTextEpgSearch;
  value["recordinggroup"]    = m_iRecordingGroup;
  value["maxrecordings"]     = m_iMaxRecordings;
  value["epguid"]            = m_iEpgUid;
  value["serieslink"]        = m_strSeriesLink;
}

void CPVRTimerInfoTag::UpdateSummary()
{
  CSingleLock lock(m_critSection);
  m_strSummary.clear();

  const std::string startDate(StartAsLocalTime().GetAsLocalizedDate());
  const std::string endDate(EndAsLocalTime().GetAsLocalizedDate());

  if (m_bEndAnyTime)
  {
    m_strSummary = StringUtils::Format("%s %s %s",
        m_iWeekdays != PVR_WEEKDAY_NONE ?
          GetWeekdaysString().c_str() : startDate.c_str(), //for "Any day" set PVR_WEEKDAY_ALLDAYS
        g_localizeStrings.Get(19107).c_str(), // "at"
        m_bStartAnyTime ?
          g_localizeStrings.Get(19161).c_str() /* "any time" */ : StartAsLocalTime().GetAsLocalizedTime("", false).c_str());
  }
  else if ((m_iWeekdays != PVR_WEEKDAY_NONE) || (startDate == endDate))
  {
    m_strSummary = StringUtils::Format("%s %s %s %s %s",
        m_iWeekdays != PVR_WEEKDAY_NONE ?
          GetWeekdaysString().c_str() : startDate.c_str(), //for "Any day" set PVR_WEEKDAY_ALLDAYS
        g_localizeStrings.Get(19159).c_str(), // "from"
        m_bStartAnyTime ?
          g_localizeStrings.Get(19161).c_str() /* "any time" */ : StartAsLocalTime().GetAsLocalizedTime("", false).c_str(),
        g_localizeStrings.Get(19160).c_str(), // "to"
        m_bEndAnyTime ?
          g_localizeStrings.Get(19161).c_str() /* "any time" */ : EndAsLocalTime().GetAsLocalizedTime("", false).c_str());
  }
  else
  {
    m_strSummary = StringUtils::Format("%s %s %s %s %s %s",
        startDate.c_str(),
        g_localizeStrings.Get(19159).c_str(), // "from"
        m_bStartAnyTime ?
          g_localizeStrings.Get(19161).c_str() /* "any time" */ : StartAsLocalTime().GetAsLocalizedTime("", false).c_str(),
        g_localizeStrings.Get(19160).c_str(), // "to"
        endDate.c_str(),
        m_bEndAnyTime ?
          g_localizeStrings.Get(19161).c_str() /* "any time" */ : EndAsLocalTime().GetAsLocalizedTime("", false).c_str());
  }
}

void CPVRTimerInfoTag::SetTimerType(const std::shared_ptr<CPVRTimerType>& type)
{
  CSingleLock lock(m_critSection);
  m_timerType = type;

  if (m_timerType && m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX)
  {
    m_iPriority = m_timerType->GetPriorityDefault();
    m_iLifetime = m_timerType->GetLifetimeDefault();
    m_iMaxRecordings = m_timerType->GetMaxRecordingsDefault();
    m_iPreventDupEpisodes = m_timerType->GetPreventDuplicateEpisodesDefault();
    m_iRecordingGroup = m_timerType->GetRecordingGroupDefault();
  }

  if (m_timerType && !m_timerType->IsTimerRule())
    m_iWeekdays = PVR_WEEKDAY_NONE;
}

std::string CPVRTimerInfoTag::GetStatus(bool bRadio) const
{
  std::string strReturn = g_localizeStrings.Get(305);
  CSingleLock lock(m_critSection);
  if (URIUtils::PathEquals(m_strFileNameAndPath, CPVRTimersPath::PATH_ADDTIMER))
    strReturn = g_localizeStrings.Get(19026);
  else if (m_state == PVR_TIMER_STATE_CANCELLED || m_state == PVR_TIMER_STATE_ABORTED)
    strReturn = g_localizeStrings.Get(13106);
  else if (m_state == PVR_TIMER_STATE_RECORDING)
    strReturn = g_localizeStrings.Get(19162);
  else if (m_state == PVR_TIMER_STATE_CONFLICT_OK)
    strReturn = g_localizeStrings.Get(19275);
  else if (m_state == PVR_TIMER_STATE_CONFLICT_NOK)
    strReturn = g_localizeStrings.Get(19276);
  else if (m_state == PVR_TIMER_STATE_ERROR)
    strReturn = g_localizeStrings.Get(257);
  else if (m_state == PVR_TIMER_STATE_DISABLED)
    strReturn = g_localizeStrings.Get(13106);
  else if (m_state == PVR_TIMER_STATE_COMPLETED)
  {
    if ((m_iTVChildTimersRecording > 0 && !bRadio) || (m_iRadioChildTimersRecording > 0 && bRadio))
      strReturn = g_localizeStrings.Get(19162); // "Recording active"
    else
      strReturn = g_localizeStrings.Get(19256); // "Completed"
  }
  else if (m_state == PVR_TIMER_STATE_SCHEDULED || m_state == PVR_TIMER_STATE_NEW)
  {
    if ((m_iTVChildTimersRecording > 0 && !bRadio) || (m_iRadioChildTimersRecording > 0 && bRadio))
      strReturn = g_localizeStrings.Get(19162); // "Recording active"
    else if ((m_iTVChildTimersErrors > 0 && !bRadio) || (m_iRadioChildTimersErrors > 0 && bRadio))
      strReturn = g_localizeStrings.Get(257); // "Error"
    else if ((m_iTVChildTimersConflictNOK > 0 && !bRadio) || (m_iRadioChildTimersConflictNOK > 0 && bRadio))
      strReturn = g_localizeStrings.Get(19276); // "Conflict error"
    else if ((m_iTVChildTimersActive > 0 && !bRadio) || (m_iRadioChildTimersActive > 0 && bRadio))
      strReturn = StringUtils::Format(g_localizeStrings.Get(19255).c_str(),
                                      bRadio ? m_iRadioChildTimersActive : m_iTVChildTimersActive); // "%d scheduled"
  }

  return strReturn;
}

/**
 * Get the type string of this timer
 */
std::string CPVRTimerInfoTag::GetTypeAsString() const
{
  CSingleLock lock(m_critSection);
  return m_timerType ? m_timerType->GetDescription() : "";
}

namespace
{
void AppendDay(std::string& strReturn, unsigned int iId)
{
  if (!strReturn.empty())
    strReturn += "-";

  if (iId > 0)
    strReturn += g_localizeStrings.Get(iId).c_str();
  else
    strReturn += "__";
}
} // unnamed namespace

std::string CPVRTimerInfoTag::GetWeekdaysString(unsigned int iWeekdays, bool bEpgBased, bool bLongMultiDaysFormat)
{
  std::string strReturn;

  if (iWeekdays == PVR_WEEKDAY_NONE)
    return strReturn;
  else if (iWeekdays == PVR_WEEKDAY_ALLDAYS)
    strReturn = bEpgBased
              ? g_localizeStrings.Get(807) // "Any day"
              : g_localizeStrings.Get(808); // "Every day"
  else if (iWeekdays == PVR_WEEKDAY_MONDAY)
    strReturn = g_localizeStrings.Get(831); // "Mondays"
  else if (iWeekdays == PVR_WEEKDAY_TUESDAY)
    strReturn = g_localizeStrings.Get(832); // "Tuesdays"
  else if (iWeekdays == PVR_WEEKDAY_WEDNESDAY)
    strReturn = g_localizeStrings.Get(833); // "Wednesdays"
  else if (iWeekdays == PVR_WEEKDAY_THURSDAY)
    strReturn = g_localizeStrings.Get(834); // "Thursdays"
  else if (iWeekdays == PVR_WEEKDAY_FRIDAY)
    strReturn = g_localizeStrings.Get(835); // "Fridays"
  else if (iWeekdays == PVR_WEEKDAY_SATURDAY)
    strReturn = g_localizeStrings.Get(836); // "Saturdays"
  else if (iWeekdays == PVR_WEEKDAY_SUNDAY)
    strReturn = g_localizeStrings.Get(837); // "Sundays"
  else
  {
    // Any other combination. Assemble custom string.
    if (iWeekdays & PVR_WEEKDAY_MONDAY)
      AppendDay(strReturn, 19149); // Mo
    else if (bLongMultiDaysFormat)
      AppendDay(strReturn, 0);

    if (iWeekdays & PVR_WEEKDAY_TUESDAY)
      AppendDay(strReturn, 19150); // Tu
    else if (bLongMultiDaysFormat)
      AppendDay(strReturn, 0);

    if (iWeekdays & PVR_WEEKDAY_WEDNESDAY)
      AppendDay(strReturn, 19151); // We
    else if (bLongMultiDaysFormat)
      AppendDay(strReturn, 0);

    if (iWeekdays & PVR_WEEKDAY_THURSDAY)
      AppendDay(strReturn, 19152); // Th
    else if (bLongMultiDaysFormat)
      AppendDay(strReturn, 0);

    if (iWeekdays & PVR_WEEKDAY_FRIDAY)
      AppendDay(strReturn, 19153); // Fr
    else if (bLongMultiDaysFormat)
      AppendDay(strReturn, 0);

    if (iWeekdays & PVR_WEEKDAY_SATURDAY)
      AppendDay(strReturn, 19154); // Sa
    else if (bLongMultiDaysFormat)
      AppendDay(strReturn, 0);

    if (iWeekdays & PVR_WEEKDAY_SUNDAY)
      AppendDay(strReturn, 19155); // So
    else if (bLongMultiDaysFormat)
      AppendDay(strReturn, 0);
  }
  return strReturn;
}

std::string CPVRTimerInfoTag::GetWeekdaysString() const
{
  CSingleLock lock(m_critSection);
  return GetWeekdaysString(m_iWeekdays, m_timerType ? m_timerType->IsEpgBased() : false, false);
}

bool CPVRTimerInfoTag::IsOwnedByClient() const
{
  return m_timerType && m_timerType->GetClientId() > -1;
}

bool CPVRTimerInfoTag::AddToClient() const
{
  const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
  if (client)
    return client->AddTimer(*this) == PVR_ERROR_NO_ERROR;
  return false;
}

TimerOperationResult CPVRTimerInfoTag::DeleteFromClient(bool bForce /* = false */) const
{
  const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
  PVR_ERROR error = PVR_ERROR_UNKNOWN;

  if (client)
    error = client->DeleteTimer(*this, bForce);

  if (error == PVR_ERROR_RECORDING_RUNNING)
    return TimerOperationResult::RECORDING;

  return (error == PVR_ERROR_NO_ERROR) ? TimerOperationResult::OK : TimerOperationResult::FAILED;
}

bool CPVRTimerInfoTag::RenameOnClient(const std::string& strNewName)
{
  {
    // set the new timer title locally
    CSingleLock lock(m_critSection);
    m_strTitle = strNewName;
  }

  // update timer data in the backend
  return UpdateOnClient();
}

bool CPVRTimerInfoTag::Persist()
{
  const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase();
  if (database)
    return database->Persist(*this);

  return false;
}

bool CPVRTimerInfoTag::DeleteFromDatabase()
{
  const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase();
  if (database)
    return database->Delete(*this);

  return false;
}

bool CPVRTimerInfoTag::UpdateEntry(const std::shared_ptr<CPVRTimerInfoTag>& tag)
{
  CSingleLock lock(m_critSection);

  m_iClientId = tag->m_iClientId;
  m_iClientIndex = tag->m_iClientIndex;
  m_iParentClientIndex = tag->m_iParentClientIndex;
  m_strTitle = tag->m_strTitle;
  m_strEpgSearchString = tag->m_strEpgSearchString;
  m_bFullTextEpgSearch = tag->m_bFullTextEpgSearch;
  m_strDirectory = tag->m_strDirectory;
  m_iClientChannelUid = tag->m_iClientChannelUid;
  m_StartTime = tag->m_StartTime;
  m_StopTime = tag->m_StopTime;
  m_bStartAnyTime = tag->m_bStartAnyTime;
  m_bEndAnyTime = tag->m_bEndAnyTime;
  m_FirstDay = tag->m_FirstDay;
  m_iPriority = tag->m_iPriority;
  m_iLifetime = tag->m_iLifetime;
  m_iMaxRecordings = tag->m_iMaxRecordings;
  m_state = tag->m_state;
  m_iPreventDupEpisodes = tag->m_iPreventDupEpisodes;
  m_iRecordingGroup = tag->m_iRecordingGroup;
  m_iWeekdays = tag->m_iWeekdays;
  m_bIsRadio = tag->m_bIsRadio;
  m_iMarginStart = tag->m_iMarginStart;
  m_iMarginEnd = tag->m_iMarginEnd;
  m_strSeriesLink = tag->m_strSeriesLink;
  m_iEpgUid = tag->m_iEpgUid;
  m_epgTag = tag->m_epgTag;
  m_strSummary = tag->m_strSummary;
  m_channel = tag->m_channel;
  m_bProbedEpgTag = tag->m_bProbedEpgTag;

  m_iTVChildTimersActive = tag->m_iTVChildTimersActive;
  m_iTVChildTimersConflictNOK = tag->m_iTVChildTimersConflictNOK;
  m_iTVChildTimersRecording = tag->m_iTVChildTimersRecording;
  m_iTVChildTimersErrors = tag->m_iTVChildTimersErrors;
  m_iRadioChildTimersActive = tag->m_iRadioChildTimersActive;
  m_iRadioChildTimersConflictNOK = tag->m_iRadioChildTimersConflictNOK;
  m_iRadioChildTimersRecording = tag->m_iRadioChildTimersRecording;
  m_iRadioChildTimersErrors = tag->m_iRadioChildTimersErrors;

  SetTimerType(tag->m_timerType);

  if (m_strSummary.empty())
    UpdateSummary();

  UpdateEpgInfoTag();

  return true;
}

bool CPVRTimerInfoTag::UpdateChildState(const std::shared_ptr<CPVRTimerInfoTag>& childTimer, bool bAdd)
{
  if (!childTimer || childTimer->m_iParentClientIndex != m_iClientIndex)
    return false;

  int iDelta = bAdd ? +1 : -1;
  switch (childTimer->m_state)
  {
  case PVR_TIMER_STATE_NEW:
  case PVR_TIMER_STATE_SCHEDULED:
  case PVR_TIMER_STATE_CONFLICT_OK:
    if (childTimer->m_bIsRadio)
      m_iRadioChildTimersActive += iDelta;
    else
      m_iTVChildTimersActive += iDelta;
    break;
  case PVR_TIMER_STATE_RECORDING:
    if (childTimer->m_bIsRadio)
    {
      m_iRadioChildTimersActive += iDelta;
      m_iRadioChildTimersRecording += iDelta;
    }
    else
    {
      m_iTVChildTimersActive += iDelta;
      m_iTVChildTimersRecording += iDelta;
    }
    break;
  case PVR_TIMER_STATE_CONFLICT_NOK:
    if (childTimer->m_bIsRadio)
      m_iRadioChildTimersConflictNOK += iDelta;
    else
      m_iTVChildTimersConflictNOK += iDelta;
    break;
  case PVR_TIMER_STATE_ERROR:
    if (childTimer->m_bIsRadio)
      m_iRadioChildTimersErrors += iDelta;
    else
      m_iTVChildTimersErrors += iDelta;
    break;
  case PVR_TIMER_STATE_COMPLETED:
  case PVR_TIMER_STATE_ABORTED:
  case PVR_TIMER_STATE_CANCELLED:
  case PVR_TIMER_STATE_DISABLED:
    //these are not the child timers we are looking for
    break;
  }
  return true;
}

void CPVRTimerInfoTag::ResetChildState()
{
  m_iTVChildTimersActive = 0;
  m_iTVChildTimersRecording = 0;
  m_iTVChildTimersConflictNOK = 0;
  m_iTVChildTimersErrors = 0;
  m_iRadioChildTimersActive = 0;
  m_iRadioChildTimersRecording = 0;
  m_iRadioChildTimersConflictNOK = 0;
  m_iRadioChildTimersErrors = 0;
}

bool CPVRTimerInfoTag::UpdateOnClient()
{
  const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
  return client && (client->UpdateTimer(*this) == PVR_ERROR_NO_ERROR);
}

std::string CPVRTimerInfoTag::ChannelName() const
{
  std::string strReturn;
  std::shared_ptr<CPVRChannel> channeltag = Channel();
  if (channeltag)
    strReturn = channeltag->ChannelName();
  else if (m_timerType && m_timerType->IsEpgBasedTimerRule())
    strReturn = StringUtils::Format("(%s)", g_localizeStrings.Get(809).c_str()); // "Any channel"

  return strReturn;
}

std::string CPVRTimerInfoTag::ChannelIcon() const
{
  std::string strReturn;
  std::shared_ptr<CPVRChannel> channeltag = Channel();
  if (channeltag)
    strReturn = channeltag->IconPath();
  return strReturn;
}

std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateReminderFromDate(
  const CDateTime& start, int iDuration,
  const std::shared_ptr<CPVRTimerInfoTag>& parent /* = std::shared_ptr<CPVRTimerInfoTag>() */)
{
  bool bReadOnly = !!parent; // children of reminder rules are always read-only
  const std::shared_ptr<CPVRTimerInfoTag> newTimer = CreateFromDate(parent->Channel(), start, iDuration, true, bReadOnly);
  if (newTimer && parent)
  {
    // set parent
    newTimer->m_iParentClientIndex = parent->m_iClientIndex;

    // set relevant props for the new one-time reminder timer
    newTimer->m_strTitle = parent->Title();
  }
  return newTimer;
}

static const time_t INSTANT_TIMER_START = 0; // PVR addon API: special start time value to denote an instant timer

std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateInstantTimerTag(const std::shared_ptr<CPVRChannel>& channel, int iDuration /* = DEFAULT_PVRRECORD_INSTANTRECORDTIME */)
{
  return CreateFromDate(channel, CDateTime(INSTANT_TIMER_START), iDuration, false, false);
}

std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateTimerTag(const std::shared_ptr<CPVRChannel>& channel, const CDateTime& start, int iDuration)
{
  return CreateFromDate(channel, start, iDuration, false, false);
}

std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateFromDate(
  const std::shared_ptr<CPVRChannel>& channel, const CDateTime& start, int iDuration, bool bCreateReminder, bool bReadOnly)
{
  if (!channel)
  {
    CLog::LogF(LOGERROR, "No channel");
    return std::shared_ptr<CPVRTimerInfoTag>();
  }

  bool bInstantStart = (start == CDateTime(INSTANT_TIMER_START));

  std::shared_ptr<CPVREpgInfoTag> epgTag;
  if (!bCreateReminder) // time-based reminders never have epg tags
  {
    if (bInstantStart)
      epgTag = channel->GetEPGNow();
    else if (channel->GetEPG())
      epgTag = channel->GetEPG()->GetTagBetween(start, start + CDateTimeSpan(0, 0, iDuration, 0));
  }

  std::shared_ptr<CPVRTimerInfoTag> newTimer;
  if (epgTag)
  {
    if (epgTag->IsRecordable())
    {
      newTimer = CreateFromEpg(epgTag, false, bCreateReminder, bReadOnly);
    }
    else
    {
      CLog::LogF(LOGERROR, "EPG tag is not recordable");
      return std::shared_ptr<CPVRTimerInfoTag>();
    }
  }

  if (!newTimer)
  {
    newTimer.reset(new CPVRTimerInfoTag);

    newTimer->m_iClientIndex = PVR_TIMER_NO_CLIENT_INDEX;
    newTimer->m_iParentClientIndex = PVR_TIMER_NO_PARENT;
    newTimer->m_channel = channel;
    newTimer->m_strTitle = channel->ChannelName();
    newTimer->m_iClientChannelUid = channel->UniqueID();
    newTimer->m_iClientId = channel->ClientID();
    newTimer->m_bIsRadio = channel->IsRadio();

    uint64_t iMustHaveAttribs = PVR_TIMER_TYPE_IS_MANUAL;
    if (bCreateReminder)
      iMustHaveAttribs |= PVR_TIMER_TYPE_IS_REMINDER;
    if (bReadOnly)
      iMustHaveAttribs |= PVR_TIMER_TYPE_IS_READONLY;

    // timertype: manual one-shot timer for given client
    const std::shared_ptr<CPVRTimerType> timerType = CPVRTimerType::CreateFromAttributes(
      iMustHaveAttribs, PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES, channel->ClientID());
    if (!timerType)
    {
      CLog::LogF(LOGERROR, "Unable to create one shot manual timer type");
      return std::shared_ptr<CPVRTimerInfoTag>();
    }

    newTimer->SetTimerType(timerType);

    if (epgTag)
      newTimer->SetEpgInfoTag(epgTag);
    else
      newTimer->UpdateEpgInfoTag();
  }

  /* no matter the timer was created from an epg tag, overwrite timer start and end times. */
  CDateTime now(CDateTime::GetUTCDateTime());
  if (bInstantStart)
    newTimer->SetStartFromUTC(now);
  else
    newTimer->SetStartFromUTC(start);

  if (iDuration == DEFAULT_PVRRECORD_INSTANTRECORDTIME)
    iDuration = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);

  if (bInstantStart)
  {
    CDateTime endTime = now + CDateTimeSpan(0, 0, iDuration ? iDuration : 120, 0);
    newTimer->SetEndFromUTC(endTime);
  }
  else
  {
    CDateTime endTime = start + CDateTimeSpan(0, 0, iDuration ? iDuration : 120, 0);
    newTimer->SetEndFromUTC(endTime);
  }

  /* update summary string according to instant recording start/end time */
  newTimer->UpdateSummary();

  if (bInstantStart)
  {
    // "Instant recording: <summary>
    newTimer->m_strSummary = StringUtils::Format(g_localizeStrings.Get(19093).c_str(), newTimer->Summary().c_str());

    // now that we have a nice summary, we can set the "special" start time value that indicates an instant recording
    newTimer->SetStartFromUTC(start);
  }

  newTimer->m_iMarginStart = 0;
  newTimer->m_iMarginEnd = 0;

  /* unused only for reference */
  newTimer->m_strFileNameAndPath = CPVRTimersPath::PATH_NEW;

  return newTimer;
}

std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateReminderFromEpg(
  const std::shared_ptr<CPVREpgInfoTag>& tag,
  const std::shared_ptr<CPVRTimerInfoTag>& parent /* = std::shared_ptr<CPVRTimerInfoTag>() */)
{
  bool bReadOnly = !!parent; // children of reminder rules are always read-only
  const std::shared_ptr<CPVRTimerInfoTag> newTimer = CreateFromEpg(tag, false, true, bReadOnly);
  if (newTimer && parent)
  {
    // set parent
    newTimer->m_iParentClientIndex = parent->m_iClientIndex;

    // set relevant props for the new one-time reminder timer (everything not epg-related)
    newTimer->m_iMarginStart = parent->m_iMarginStart;
    newTimer->m_iMarginEnd = parent->m_iMarginEnd;
  }
  return newTimer;
}

std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateFromEpg(
  const std::shared_ptr<CPVREpgInfoTag>& tag, bool bCreateRule /* = false */)
{
  return CreateFromEpg(tag, bCreateRule, false, false);
}

std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateFromEpg(
  const std::shared_ptr<CPVREpgInfoTag>& tag, bool bCreateRule, bool bCreateReminder, bool bReadOnly)
{
  std::shared_ptr<CPVRTimerInfoTag> newTag(new CPVRTimerInfoTag());

  /* check if a valid channel is set */
  const std::shared_ptr<CPVRChannel> channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(tag);
  if (!channel)
  {
    CLog::LogF(LOGERROR, "EPG tag has no channel");
    return std::shared_ptr<CPVRTimerInfoTag>();
  }

  newTag->m_iClientIndex = PVR_TIMER_NO_CLIENT_INDEX;
  newTag->m_iParentClientIndex = PVR_TIMER_NO_PARENT;
  if (!CServiceBroker::GetPVRManager().IsParentalLocked(tag))
    newTag->m_strTitle = tag->Title();
  if (newTag->m_strTitle.empty())
    newTag->m_strTitle = channel->ChannelName();
  newTag->m_iClientChannelUid = channel->UniqueID();
  newTag->m_iClientId = channel->ClientID();
  newTag->m_bIsRadio = channel->IsRadio();
  newTag->m_channel = channel;
  newTag->m_strSeriesLink = tag->SeriesLink();
  newTag->m_iEpgUid = tag->UniqueBroadcastID();
  newTag->SetStartFromUTC(tag->StartAsUTC());
  newTag->SetEndFromUTC(tag->EndAsUTC());

  const uint64_t iMustNotHaveAttribs = PVR_TIMER_TYPE_IS_MANUAL |
                                       PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES |
                                       PVR_TIMER_TYPE_FORBIDS_EPG_TAG_ON_CREATE;
  std::shared_ptr<CPVRTimerType> timerType;
  if (bCreateRule)
  {
    // create epg-based timer rule, prefer rule using series link if available.

    uint64_t iMustHaveAttribs = PVR_TIMER_TYPE_IS_REPEATING;
    if (bCreateReminder)
      iMustHaveAttribs |= PVR_TIMER_TYPE_IS_REMINDER;
    if (bReadOnly)
      iMustHaveAttribs |= PVR_TIMER_TYPE_IS_READONLY;

    if (!tag->SeriesLink().empty())
      timerType = CPVRTimerType::CreateFromAttributes(
        iMustHaveAttribs | PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE, iMustNotHaveAttribs, channel->ClientID());
    if (!timerType)
      timerType = CPVRTimerType::CreateFromAttributes(
        iMustHaveAttribs, iMustNotHaveAttribs | PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE, channel->ClientID());
    if (timerType)
    {
      if (timerType->SupportsEpgTitleMatch())
        newTag->m_strEpgSearchString = newTag->m_strTitle;

      if (timerType->SupportsWeekdays())
        newTag->m_iWeekdays = PVR_WEEKDAY_ALLDAYS;

      if (timerType->SupportsStartAnyTime())
        newTag->m_bStartAnyTime = true;

      if (timerType->SupportsEndAnyTime())
        newTag->m_bEndAnyTime = true;
    }
  }
  else
  {
    // create one-shot epg-based timer

    uint64_t iMustHaveAttribs = PVR_TIMER_TYPE_ATTRIBUTE_NONE;
    if (bCreateReminder)
      iMustHaveAttribs |= PVR_TIMER_TYPE_IS_REMINDER;
    if (bReadOnly)
      iMustHaveAttribs |= PVR_TIMER_TYPE_IS_READONLY;

    timerType = CPVRTimerType::CreateFromAttributes(
      iMustHaveAttribs, PVR_TIMER_TYPE_IS_REPEATING | iMustNotHaveAttribs, channel->ClientID());
  }

  if (!timerType)
  {
    CLog::LogF(LOGERROR, "Unable to create any epg-based timer type");
    return std::shared_ptr<CPVRTimerInfoTag>();
  }

  newTag->SetTimerType(timerType);
  newTag->UpdateSummary();
  newTag->SetEpgInfoTag(tag);

  /* unused only for reference */
  newTag->m_strFileNameAndPath = CPVRTimersPath::PATH_NEW;

  return newTag;
}

//! @todo CDateTime class does not handle daylight saving timezone bias correctly (and cannot easily
//  be changed to do so due to performance and platform specific issues). In most cases this only
//  causes GUI presentation glitches, but reminder timer rules rely on correct local time values.

namespace
{
  #define IsLeapYear(y) ((y % 4 == 0) && (y % 100 != 0 || y % 400 == 0))

  int days_from_0(int year)
  {
    year--;
    return 365 * year + (year / 400) - (year / 100) + (year / 4);
  }

  int days_from_1970(int32_t year)
  {
    static const int days_from_0_to_1970 = days_from_0(1970);
    return days_from_0(year) - days_from_0_to_1970;
  }

  int days_from_1jan(int year, int month, int day)
  {
    static const int days[2][12] =
    {
      { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
      { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
    };
    return days[IsLeapYear(year)][month - 1] + day - 1;
  }

  time_t mytimegm(struct tm* time)
  {
    int year = time->tm_year + 1900;
    int month = time->tm_mon;

    if (month > 11)
    {
      year += month / 12;
      month %= 12;
    }
    else if (month < 0)
    {
      int years_diff = (-month + 11) / 12;
      year -= years_diff;
      month += 12 * years_diff;
    }

    month++;

    int day_of_year = days_from_1jan(year, month, time->tm_mday);
    int days_since_epoch = days_from_1970(year) + day_of_year;

    return 3600 * 24 * days_since_epoch + 3600 * time->tm_hour + 60 * time->tm_min + time->tm_sec;
  }
}

CDateTime CPVRTimerInfoTag::ConvertUTCToLocalTime(const CDateTime& utc)
{
  time_t time = 0;
  utc.GetAsTime(time);

  struct tm* tms;
#ifdef HAVE_LOCALTIME_R
  struct tm gbuf;
  tms = localtime_r(&time, &gbuf);
#else
  tms = localtime(&time);
#endif

  return CDateTime(mytimegm(tms));
}

CDateTime CPVRTimerInfoTag::ConvertLocalTimeToUTC(const CDateTime& local)
{
  time_t time = 0;
  local.GetAsTime(time);

  struct tm* tms;

  // obtain dst flag for given datetime
#ifdef HAVE_LOCALTIME_R
  struct tm loc_buf;
  tms = localtime_r(&time, &loc_buf);
#else
  tms = localtime(&time);
#endif

  int isdst = tms->tm_isdst;

#ifdef HAVE_GMTIME_R
  struct tm gm_buf;
  tms = gmtime_r(&time, &gm_buf);
#else
  tms = gmtime(&time);
#endif

  tms->tm_isdst = isdst;
  return CDateTime(mktime(tms));
}

CDateTime CPVRTimerInfoTag::StartAsUTC() const
{
  return m_StartTime;
}

CDateTime CPVRTimerInfoTag::StartAsLocalTime() const
{
  return ConvertUTCToLocalTime(m_StartTime);
}

void CPVRTimerInfoTag::SetStartFromUTC(const CDateTime& start)
{
  m_StartTime = start;
}

void CPVRTimerInfoTag::SetStartFromLocalTime(const CDateTime& start)
{
  m_StartTime = ConvertLocalTimeToUTC(start);
}

CDateTime CPVRTimerInfoTag::EndAsUTC() const
{
  return m_StopTime;
}

CDateTime CPVRTimerInfoTag::EndAsLocalTime() const
{
  return ConvertUTCToLocalTime(m_StopTime);
}

void CPVRTimerInfoTag::SetEndFromUTC(const CDateTime& end)
{
  m_StopTime = end;
}

void CPVRTimerInfoTag::SetEndFromLocalTime(const CDateTime& end)
{
  m_StopTime = ConvertLocalTimeToUTC(end);
}

int CPVRTimerInfoTag::GetDuration() const
{
  time_t start, end;
  m_StartTime.GetAsTime(start);
  m_StopTime.GetAsTime(end);
  return end - start > 0 ? end - start : 3600;
}

CDateTime CPVRTimerInfoTag::FirstDayAsUTC() const
{
  return m_FirstDay;
}

CDateTime CPVRTimerInfoTag::FirstDayAsLocalTime() const
{
  return ConvertUTCToLocalTime(m_FirstDay);
}

void CPVRTimerInfoTag::SetFirstDayFromUTC(const CDateTime& firstDay)
{
  m_FirstDay = firstDay;
}

void CPVRTimerInfoTag::SetFirstDayFromLocalTime(const CDateTime& firstDay)
{
  m_FirstDay = ConvertLocalTimeToUTC(firstDay);
}

void CPVRTimerInfoTag::GetNotificationText(std::string& strText) const
{
  CSingleLock lock(m_critSection);

  int stringID = 0;

  switch (m_state)
  {
  case PVR_TIMER_STATE_ABORTED:
  case PVR_TIMER_STATE_CANCELLED:
      stringID = 19224; // Recording aborted
    break;
  case PVR_TIMER_STATE_SCHEDULED:
    if (IsTimerRule())
      stringID = 19058; // Timer enabled
    else
      stringID = 19225; // Recording scheduled
    break;
  case PVR_TIMER_STATE_RECORDING:
    stringID = 19226; // Recording started
    break;
  case PVR_TIMER_STATE_COMPLETED:
    stringID = 19227; // Recording completed
    break;
  case PVR_TIMER_STATE_CONFLICT_OK:
  case PVR_TIMER_STATE_CONFLICT_NOK:
    stringID = 19277; // Recording conflict
    break;
  case PVR_TIMER_STATE_ERROR:
    stringID = 19278; // Recording error
    break;
  case PVR_TIMER_STATE_DISABLED:
    stringID = 19057; // Timer disabled
    break;
  default:
    break;
  }
  if (stringID != 0)
    strText = StringUtils::Format("%s: '%s'", g_localizeStrings.Get(stringID).c_str(), m_strTitle.c_str());
}

std::string CPVRTimerInfoTag::GetDeletedNotificationText() const
{
  CSingleLock lock(m_critSection);

  int stringID = 0;
  // The state in this case is the state the timer had when it was last seen
  switch (m_state)
  {
  case PVR_TIMER_STATE_RECORDING:
    stringID = 19227; // Recording completed
    break;
  case PVR_TIMER_STATE_SCHEDULED:
  default:
    if (IsTimerRule())
      stringID = 828; // Timer rule deleted
    else
      stringID = 19228; // Timer deleted
  }

  return StringUtils::Format("%s: '%s'", g_localizeStrings.Get(stringID).c_str(), m_strTitle.c_str());
}

void CPVRTimerInfoTag::SetEpgInfoTag(const std::shared_ptr<CPVREpgInfoTag>& tag)
{
  CSingleLock lock(m_critSection);
  m_epgTag = tag;
  m_bProbedEpgTag = true;
}

void CPVRTimerInfoTag::UpdateEpgInfoTag()
{
  CSingleLock lock(m_critSection);
  m_epgTag.reset();
  m_bProbedEpgTag = false;
  GetEpgInfoTag();
}

std::shared_ptr<CPVREpgInfoTag> CPVRTimerInfoTag::GetEpgInfoTag(bool bCreate /* = true */) const
{
  if (!m_epgTag && !m_bProbedEpgTag && bCreate && CServiceBroker::GetPVRManager().EpgsCreated())
  {
    std::shared_ptr<CPVRChannel> channel(m_channel);
    if (!channel)
    {
      channel = CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)->GetGroupAll()->GetByUniqueID(m_iClientChannelUid, m_iClientId);

      CSingleLock lock(m_critSection);
      m_channel = channel;
    }

    if (channel)
    {
      const std::shared_ptr<CPVREpg> epg(channel->GetEPG());
      if (epg)
      {
        CSingleLock lock(m_critSection);
        if (!m_epgTag && m_iEpgUid != EPG_TAG_INVALID_UID)
        {
          m_epgTag = epg->GetTagByBroadcastId(m_iEpgUid);
        }

        if (!m_epgTag && !IsTimerRule() && IsOwnedByClient())
        {
          time_t startTime = 0;
          time_t endTime = 0;

          StartAsUTC().GetAsTime(startTime);
          if (startTime > 0)
            EndAsUTC().GetAsTime(endTime);

          if (startTime > 0 && endTime > 0)
          {
            // try to fetch missing epg tag from backend
            m_epgTag = epg->GetTagBetween(StartAsUTC() - CDateTimeSpan(0, 0, 2, 0), EndAsUTC() + CDateTimeSpan(0, 0, 2, 0), true);
            if (m_epgTag)
              m_iEpgUid = m_epgTag->UniqueBroadcastID();
          }
        }
      }
    }
    m_bProbedEpgTag = true;
  }
  return m_epgTag;
}

bool CPVRTimerInfoTag::HasChannel() const
{
  return m_channel.get() != nullptr;
}

std::shared_ptr<CPVRChannel> CPVRTimerInfoTag::Channel() const
{
  return m_channel;
}

std::shared_ptr<CPVRChannel> CPVRTimerInfoTag::UpdateChannel()
{
  const std::shared_ptr<CPVRChannel> channel(CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)->GetGroupAll()->GetByUniqueID(m_iClientChannelUid, m_iClientId));

  CSingleLock lock(m_critSection);
  m_channel = channel;
  return m_channel;
}

const std::string& CPVRTimerInfoTag::Title() const
{
  return m_strTitle;
}

const std::string& CPVRTimerInfoTag::Summary() const
{
  return m_strSummary;
}

const std::string& CPVRTimerInfoTag::Path() const
{
  return m_strFileNameAndPath;
}

const std::string& CPVRTimerInfoTag::SeriesLink() const
{
  return m_strSeriesLink;
}


/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.filters;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.geoserver.platform.GeoServerExtensions;

/**
 * Simple filter to set X-Frame-Options header to prevent click jacking attacks. This filter is
 * controlled by two system properties:

 * - geoserver.xframe.shouldSetPolicy: controls whether the X-Frame-Options filter should be set at
 * all. Default is true.

 * - geoserver.xframe.policy: controls what the set the X-Frame-Options header to. Default is
 * SAMEORIGIN valid options are DENY, SAMEORIGIN and ALLOW-FROM [uri]

 * These properties can be set via command line -D arg, web.xml init or environment variable.
 */
public class XFrameOptionsFilter implements Filter {

    private static final boolean DEFAULT_SHOULD_SET_POLICY = true;
    private static final String DEFAULT_FRAME_POLICY = "SAMEORIGIN";
    private static final String X_FRAME_OPTIONS = "X-Frame-Options";

    /** The system property to set whether the X-Frame-Options header should be set */
    public static final String GEOSERVER_XFRAME_SHOULD_SET_POLICY =
            "geoserver.xframe.shouldSetPolicy";

    /** The system property for the value of the X-Frame-Options header */
    public static final String GEOSERVER_XFRAME_POLICY = "geoserver.xframe.policy";

    /**
     * Whether the X-Frame-Option header should be set at all. Check this on the fly for easier
     * testing and in order to potentially make this a GUI controlled option in the future.
     */
    private static boolean shouldSetPolicy() {
        boolean shouldSetPolicy = DEFAULT_SHOULD_SET_POLICY;
        if (StringUtils.isNotEmpty(
                GeoServerExtensions.getProperty(GEOSERVER_XFRAME_SHOULD_SET_POLICY))) {
            shouldSetPolicy =
                    Boolean.parseBoolean(
                            GeoServerExtensions.getProperty(GEOSERVER_XFRAME_SHOULD_SET_POLICY));
        }

        return shouldSetPolicy;
    }

    private static String getFramePolicy() {
        String framePolicy = DEFAULT_FRAME_POLICY;
        if (StringUtils.isNotEmpty(GeoServerExtensions.getProperty(GEOSERVER_XFRAME_POLICY))) {
            framePolicy = GeoServerExtensions.getProperty(GEOSERVER_XFRAME_POLICY);
        }

        return framePolicy;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        if (shouldSetPolicy()) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader(X_FRAME_OPTIONS, getFramePolicy());
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}


// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.core.task.v0_6;

/**
 * Defines the interface for combining multi sink and change source
 * functionality. This is typically used by classes performing difference
 * analysis of two data sources.
 *
 * @author Brett Henderson
 */
public interface MultiSinkRunnableChangeSource extends MultiSink, RunnableChangeSource {
	// Interface only combines functionality of its extended interfaces.
}


/*
 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package sun.security.ssl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.MessageFormat;
import java.util.*;
import javax.net.ssl.SSLProtocolException;
import sun.security.ssl.SSLExtension.ExtensionConsumer;

import sun.security.ssl.SSLExtension.SSLExtensionSpec;
import sun.security.ssl.SSLHandshake.HandshakeMessage;

/**
 * Pack of the "psk_key_exchange_modes" extensions.
 */
final class PskKeyExchangeModesExtension {
    static final HandshakeProducer chNetworkProducer =
            new PskKeyExchangeModesProducer();
    static final ExtensionConsumer chOnLoadConsumer =
            new PskKeyExchangeModesConsumer();
    static final HandshakeAbsence chOnLoadAbsence =
            new PskKeyExchangeModesOnLoadAbsence();
    static final HandshakeAbsence chOnTradeAbsence =
            new PskKeyExchangeModesOnTradeAbsence();

    static final SSLStringizer pkemStringizer =
            new PskKeyExchangeModesStringizer();

    enum PskKeyExchangeMode {
        PSK_KE          ((byte)0, "psk_ke"),
        PSK_DHE_KE      ((byte)1, "psk_dhe_ke");

        final byte id;
        final String name;

        PskKeyExchangeMode(byte id, String name) {
            this.id = id;
            this.name = name;
        }

        static PskKeyExchangeMode valueOf(byte id) {
            for(PskKeyExchangeMode pkem : values()) {
                if (pkem.id == id) {
                    return pkem;
                }
            }

            return null;
        }

        static String nameOf(byte id) {
            for (PskKeyExchangeMode pkem : PskKeyExchangeMode.values()) {
                if (pkem.id == id) {
                    return pkem.name;
                }
            }

            return "<UNKNOWN PskKeyExchangeMode TYPE: " + (id & 0x0FF) + ">";
        }
    }

    static final
            class PskKeyExchangeModesSpec implements SSLExtensionSpec {
        private static final PskKeyExchangeModesSpec DEFAULT =
                new PskKeyExchangeModesSpec(new byte[] {
                        PskKeyExchangeMode.PSK_DHE_KE.id});

        final byte[] modes;

        PskKeyExchangeModesSpec(byte[] modes) {
            this.modes = modes;
        }

        PskKeyExchangeModesSpec(ByteBuffer m) throws IOException {
            if (m.remaining() < 2) {
                throw new SSLProtocolException(
                    "Invalid psk_key_exchange_modes extension: " +
                    "insufficient data");
            }

            this.modes = Record.getBytes8(m);
        }

        boolean contains(PskKeyExchangeMode mode) {
            if (modes != null) {
                for (byte m : modes) {
                    if (mode.id == m) {
                        return true;
                    }
                }
            }

            return false;
        }

        @Override
        public String toString() {
            MessageFormat messageFormat = new MessageFormat(
                "\"ke_modes\": '['{0}']'", Locale.ENGLISH);
            if (modes == null || modes.length ==  0) {
                Object[] messageFields = {
                        "<no PSK key exchange modes specified>"
                    };
                return messageFormat.format(messageFields);
            } else {
                StringBuilder builder = new StringBuilder(64);
                boolean isFirst = true;
                for (byte mode : modes) {
                    if (isFirst) {
                        isFirst = false;
                    } else {
                        builder.append(", ");
                    }

                    builder.append(PskKeyExchangeMode.nameOf(mode));
                }

                Object[] messageFields = {
                        builder.toString()
                    };

                return messageFormat.format(messageFields);
            }
        }
    }

    private static final
            class PskKeyExchangeModesStringizer implements SSLStringizer {
        @Override
        public String toString(ByteBuffer buffer) {
            try {
                return (new PskKeyExchangeModesSpec(buffer)).toString();
            } catch (IOException ioe) {
                // For debug logging only, so please swallow exceptions.
                return ioe.getMessage();
            }
        }
    }

    /**
     * Network data consumer of a "psk_key_exchange_modes" extension in
     * the ClientHello handshake message.
     */
    private static final
            class PskKeyExchangeModesConsumer implements ExtensionConsumer {
        // Prevent instantiation of this class.
        private PskKeyExchangeModesConsumer() {
            // blank
        }

        @Override
        public void consume(ConnectionContext context,
            HandshakeMessage message, ByteBuffer buffer) throws IOException {

            // The consuming happens in server side only.
            ServerHandshakeContext shc = (ServerHandshakeContext)context;

            // Is it a supported and enabled extension?
            if (!shc.sslConfig.isAvailable(
                    SSLExtension.PSK_KEY_EXCHANGE_MODES)) {
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                    SSLLogger.fine(
                        "Ignore unavailable psk_key_exchange_modes extension");
                }

                // No session resumption is allowed.
                if (shc.isResumption && shc.resumingSession != null) {
                    shc.isResumption = false;
                    shc.resumingSession = null;
                }

                return;     // ignore the extension
            }

            // Parse the extension.
            PskKeyExchangeModesSpec spec;
            try {
                spec = new PskKeyExchangeModesSpec(buffer);
            } catch (IOException ioe) {
                throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
            }

            // Update the context.
            shc.handshakeExtensions.put(
                    SSLExtension.PSK_KEY_EXCHANGE_MODES, spec);

            // Impact on session resumption.
            //
            // Do the requested modes support session resumption?
            if (shc.isResumption) {     // resumingSession may not be set
                // Note: psk_dhe_ke is the only supported mode now.  If the
                // psk_ke mode is supported in the future, may need an update
                // here.
                if (!spec.contains(PskKeyExchangeMode.PSK_DHE_KE)) {
                    shc.isResumption = false;
                    shc.resumingSession = null;
                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                        SSLLogger.fine(
                            "abort session resumption, " +
                            "no supported psk_dhe_ke PSK key exchange mode");
                    }
                }
            }
        }
    }

    /**
     * Network data producer of a "psk_key_exchange_modes" extension in the
     * ClientHello handshake message.
     */
    private static final
            class PskKeyExchangeModesProducer implements HandshakeProducer {

        // Prevent instantiation of this class.
        private PskKeyExchangeModesProducer() {
            // blank
        }

        @Override
        public byte[] produce(ConnectionContext context,
                HandshakeMessage message) throws IOException {
            // The producing happens in client side only.
            ClientHandshakeContext chc = (ClientHandshakeContext)context;

            // Is it a supported and enabled extension?
            if (!chc.sslConfig.isAvailable(
                    SSLExtension.PSK_KEY_EXCHANGE_MODES)) {
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                    SSLLogger.warning(
                        "Ignore unavailable psk_key_exchange_modes extension");
                }

                return null;
            }

            byte[] extData = new byte[] {0x01, 0x01};   // psk_dhe_ke

            // Update the context.
            chc.handshakeExtensions.put(
                    SSLExtension.PSK_KEY_EXCHANGE_MODES,
                    PskKeyExchangeModesSpec.DEFAULT);

            return extData;
        }
    }
    /**
     * The absence processing if a "psk_key_exchange_modes" extension is
     * not present in the ClientHello handshake message.
     */
    private static final
        class PskKeyExchangeModesOnLoadAbsence implements HandshakeAbsence {

        // Prevent instantiation of this class.
        private PskKeyExchangeModesOnLoadAbsence() {
            // blank
        }

        @Override
        public void absent(ConnectionContext context,
                HandshakeMessage message) throws IOException {
            // The consuming happens in server side only.
            ServerHandshakeContext shc = (ServerHandshakeContext)context;

            // No session resumptio is allowed.
            if (shc.isResumption) {     // resumingSession may not be set
                shc.isResumption = false;
                shc.resumingSession = null;
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                    SSLLogger.fine(
                            "abort session resumption, " +
                            "no supported psk_dhe_ke PSK key exchange mode");
                }
            }
        }
    }

    /**
     * The absence processing if a "signature_algorithms" extension is
     * not present in the ClientHello handshake message.
     */
    private static final
        class PskKeyExchangeModesOnTradeAbsence implements HandshakeAbsence {

        // Prevent instantiation of this class.
        private PskKeyExchangeModesOnTradeAbsence() {
            // blank
        }

        @Override
        public void absent(ConnectionContext context,
                HandshakeMessage message) throws IOException {
            // The consuming happens in server side only.
            ServerHandshakeContext shc = (ServerHandshakeContext)context;

            // A client MUST provide a "psk_key_exchange_modes" extension if
            // it offers a "pre_shared_key" extension.  If clients offer
            // "pre_shared_key" without a "psk_key_exchange_modes" extension,
            // servers MUST abort the handshake.
            SSLExtensionSpec spec =
                shc.handshakeExtensions.get(SSLExtension.CH_PRE_SHARED_KEY);
            if (spec != null) {
                throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
                        "pre_shared_key key extension is offered " +
                        "without a psk_key_exchange_modes extension");
            }
        }
    }
}


/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsSHEntry.h"
#include "nsIDocShellLoadInfo.h"
#include "nsIDocShellTreeItem.h"
#include "nsDocShellEditorData.h"
#include "nsSHEntryShared.h"
#include "nsILayoutHistoryState.h"
#include "nsIContentViewer.h"
#include "nsIStructuredCloneContainer.h"
#include "nsIInputStream.h"
#include "nsIURI.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "nsArray.h"
#include <algorithm>

namespace dom = mozilla::dom;

static uint32_t gEntryID = 0;

nsSHEntry::nsSHEntry()
  : mShared(new nsSHEntryShared())
  , mLoadReplace(false)
  , mReferrerPolicy(mozilla::net::RP_Default)
  , mLoadType(0)
  , mID(gEntryID++)
  , mScrollPositionX(0)
  , mScrollPositionY(0)
  , mParent(nullptr)
  , mURIWasModified(false)
  , mIsSrcdocEntry(false)
  , mScrollRestorationIsManual(false)
{
}

nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
  : mShared(aOther.mShared)
  , mURI(aOther.mURI)
  , mOriginalURI(aOther.mOriginalURI)
  , mLoadReplace(aOther.mLoadReplace)
  , mReferrerURI(aOther.mReferrerURI)
  , mReferrerPolicy(aOther.mReferrerPolicy)
  , mTitle(aOther.mTitle)
  , mPostData(aOther.mPostData)
  , mLoadType(0)         // XXX why not copy?
  , mID(aOther.mID)
  , mScrollPositionX(0)  // XXX why not copy?
  , mScrollPositionY(0)  // XXX why not copy?
  , mParent(aOther.mParent)
  , mURIWasModified(aOther.mURIWasModified)
  , mStateData(aOther.mStateData)
  , mIsSrcdocEntry(aOther.mIsSrcdocEntry)
  , mScrollRestorationIsManual(false)
  , mSrcdocData(aOther.mSrcdocData)
  , mBaseURI(aOther.mBaseURI)
{
}

static bool
ClearParentPtr(nsISHEntry* aEntry, void* /* aData */)
{
  if (aEntry) {
    aEntry->SetParent(nullptr);
  }
  return true;
}

nsSHEntry::~nsSHEntry()
{
  // Null out the mParent pointers on all our kids.
  mChildren.EnumerateForwards(ClearParentPtr, nullptr);
}

NS_IMPL_ISUPPORTS(nsSHEntry, nsISHContainer, nsISHEntry, nsISHEntryInternal)

NS_IMETHODIMP
nsSHEntry::SetScrollPosition(int32_t aX, int32_t aY)
{
  mScrollPositionX = aX;
  mScrollPositionY = aY;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetScrollPosition(int32_t* aX, int32_t* aY)
{
  *aX = mScrollPositionX;
  *aY = mScrollPositionY;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetURIWasModified(bool* aOut)
{
  *aOut = mURIWasModified;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetURIWasModified(bool aIn)
{
  mURIWasModified = aIn;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetURI(nsIURI** aURI)
{
  *aURI = mURI;
  NS_IF_ADDREF(*aURI);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetURI(nsIURI* aURI)
{
  mURI = aURI;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetOriginalURI(nsIURI** aOriginalURI)
{
  *aOriginalURI = mOriginalURI;
  NS_IF_ADDREF(*aOriginalURI);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetOriginalURI(nsIURI* aOriginalURI)
{
  mOriginalURI = aOriginalURI;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetLoadReplace(bool* aLoadReplace)
{
  *aLoadReplace = mLoadReplace;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetLoadReplace(bool aLoadReplace)
{
  mLoadReplace = aLoadReplace;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetReferrerURI(nsIURI** aReferrerURI)
{
  *aReferrerURI = mReferrerURI;
  NS_IF_ADDREF(*aReferrerURI);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetReferrerURI(nsIURI* aReferrerURI)
{
  mReferrerURI = aReferrerURI;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetReferrerPolicy(uint32_t* aReferrerPolicy)
{
  *aReferrerPolicy = mReferrerPolicy;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetReferrerPolicy(uint32_t aReferrerPolicy)
{
  mReferrerPolicy = aReferrerPolicy;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetContentViewer(nsIContentViewer* aViewer)
{
  return mShared->SetContentViewer(aViewer);
}

NS_IMETHODIMP
nsSHEntry::GetContentViewer(nsIContentViewer** aResult)
{
  *aResult = mShared->mContentViewer;
  NS_IF_ADDREF(*aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetAnyContentViewer(nsISHEntry** aOwnerEntry,
                               nsIContentViewer** aResult)
{
  // Find a content viewer in the root node or any of its children,
  // assuming that there is only one content viewer total in any one
  // nsSHEntry tree
  GetContentViewer(aResult);
  if (*aResult) {
#ifdef DEBUG_PAGE_CACHE
    printf("Found content viewer\n");
#endif
    *aOwnerEntry = this;
    NS_ADDREF(*aOwnerEntry);
    return NS_OK;
  }
  // The root SHEntry doesn't have a ContentViewer, so check child nodes
  for (int32_t i = 0; i < mChildren.Count(); i++) {
    nsISHEntry* child = mChildren[i];
    if (child) {
#ifdef DEBUG_PAGE_CACHE
      printf("Evaluating SHEntry child %d\n", i);
#endif
      child->GetAnyContentViewer(aOwnerEntry, aResult);
      if (*aResult) {
        return NS_OK;
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetSticky(bool aSticky)
{
  mShared->mSticky = aSticky;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetSticky(bool* aSticky)
{
  *aSticky = mShared->mSticky;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetTitle(char16_t** aTitle)
{
  // Check for empty title...
  if (mTitle.IsEmpty() && mURI) {
    // Default title is the URL.
    nsAutoCString spec;
    if (NS_SUCCEEDED(mURI->GetSpec(spec))) {
      AppendUTF8toUTF16(spec, mTitle);
    }
  }

  *aTitle = ToNewUnicode(mTitle);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetTitle(const nsAString& aTitle)
{
  mTitle = aTitle;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetPostData(nsIInputStream** aResult)
{
  *aResult = mPostData;
  NS_IF_ADDREF(*aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetPostData(nsIInputStream* aPostData)
{
  mPostData = aPostData;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult)
{
  *aResult = mShared->mLayoutHistoryState;
  NS_IF_ADDREF(*aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState)
{
  mShared->mLayoutHistoryState = aState;
  if (mShared->mLayoutHistoryState) {
    mShared->mLayoutHistoryState->SetScrollPositionOnly(
      !mShared->mSaveLayoutState);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetLoadType(uint32_t* aResult)
{
  *aResult = mLoadType;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetLoadType(uint32_t aLoadType)
{
  mLoadType = aLoadType;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetID(uint32_t* aResult)
{
  *aResult = mID;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetID(uint32_t aID)
{
  mID = aID;
  return NS_OK;
}

nsSHEntryShared*
nsSHEntry::GetSharedState()
{
  return mShared;
}

NS_IMETHODIMP
nsSHEntry::GetIsSubFrame(bool* aFlag)
{
  *aFlag = mShared->mIsFrameNavigation;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetIsSubFrame(bool aFlag)
{
  mShared->mIsFrameNavigation = aFlag;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetCacheKey(nsISupports** aResult)
{
  *aResult = mShared->mCacheKey;
  NS_IF_ADDREF(*aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetCacheKey(nsISupports* aCacheKey)
{
  mShared->mCacheKey = aCacheKey;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetSaveLayoutStateFlag(bool* aFlag)
{
  *aFlag = mShared->mSaveLayoutState;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetSaveLayoutStateFlag(bool aFlag)
{
  mShared->mSaveLayoutState = aFlag;
  if (mShared->mLayoutHistoryState) {
    mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetExpirationStatus(bool* aFlag)
{
  *aFlag = mShared->mExpired;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetExpirationStatus(bool aFlag)
{
  mShared->mExpired = aFlag;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetContentType(nsACString& aContentType)
{
  aContentType = mShared->mContentType;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetContentType(const nsACString& aContentType)
{
  mShared->mContentType = aContentType;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::Create(nsIURI* aURI, const nsAString& aTitle,
                  nsIInputStream* aInputStream,
                  nsILayoutHistoryState* aLayoutHistoryState,
                  nsISupports* aCacheKey, const nsACString& aContentType,
                  nsIPrincipal* aTriggeringPrincipal,
                  nsIPrincipal* aPrincipalToInherit,
                  uint64_t aDocShellID,
                  bool aDynamicCreation)
{
  MOZ_ASSERT(aTriggeringPrincipal,
             "need a valid triggeringPrincipal to create a session history entry");

  mURI = aURI;
  mTitle = aTitle;
  mPostData = aInputStream;

  // Set the LoadType by default to loadHistory during creation
  mLoadType = (uint32_t)nsIDocShellLoadInfo::loadHistory;

  mShared->mCacheKey = aCacheKey;
  mShared->mContentType = aContentType;
  mShared->mTriggeringPrincipal = aTriggeringPrincipal;
  mShared->mPrincipalToInherit = aPrincipalToInherit;
  mShared->mDocShellID = aDocShellID;
  mShared->mDynamicallyCreated = aDynamicCreation;

  // By default all entries are set false for subframe flag.
  // nsDocShell::CloneAndReplace() which creates entries for
  // all subframe navigations, sets the flag to true.
  mShared->mIsFrameNavigation = false;

  // By default we save LayoutHistoryState
  mShared->mSaveLayoutState = true;
  mShared->mLayoutHistoryState = aLayoutHistoryState;

  // By default the page is not expired
  mShared->mExpired = false;

  mIsSrcdocEntry = false;
  mSrcdocData = NullString();

  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::Clone(nsISHEntry** aResult)
{
  *aResult = new nsSHEntry(*this);
  NS_ADDREF(*aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetParent(nsISHEntry** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = mParent;
  NS_IF_ADDREF(*aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetParent(nsISHEntry* aParent)
{
  /* parent not Addrefed on purpose to avoid cyclic reference
   * Null parent is OK
   *
   * XXX this method should not be scriptable if this is the case!!
   */
  mParent = aParent;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetWindowState(nsISupports* aState)
{
  mShared->mWindowState = aState;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetWindowState(nsISupports** aState)
{
  NS_IF_ADDREF(*aState = mShared->mWindowState);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetViewerBounds(const nsIntRect& aBounds)
{
  mShared->mViewerBounds = aBounds;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetViewerBounds(nsIntRect& aBounds)
{
  aBounds = mShared->mViewerBounds;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal)
{
  NS_IF_ADDREF(*aTriggeringPrincipal = mShared->mTriggeringPrincipal);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal)
{
  mShared->mTriggeringPrincipal = aTriggeringPrincipal;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit)
{
  NS_IF_ADDREF(*aPrincipalToInherit = mShared->mPrincipalToInherit);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit)
{
  mShared->mPrincipalToInherit = aPrincipalToInherit;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetBFCacheEntry(nsIBFCacheEntry** aEntry)
{
  NS_ENSURE_ARG_POINTER(aEntry);
  NS_IF_ADDREF(*aEntry = mShared);
  return NS_OK;
}

bool
nsSHEntry::HasBFCacheEntry(nsIBFCacheEntry* aEntry)
{
  return static_cast<nsIBFCacheEntry*>(mShared) == aEntry;
}

NS_IMETHODIMP
nsSHEntry::AdoptBFCacheEntry(nsISHEntry* aEntry)
{
  nsCOMPtr<nsISHEntryInternal> shEntry = do_QueryInterface(aEntry);
  NS_ENSURE_STATE(shEntry);

  nsSHEntryShared* shared = shEntry->GetSharedState();
  NS_ENSURE_STATE(shared);

  mShared = shared;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SharesDocumentWith(nsISHEntry* aEntry, bool* aOut)
{
  NS_ENSURE_ARG_POINTER(aOut);

  nsCOMPtr<nsISHEntryInternal> internal = do_QueryInterface(aEntry);
  NS_ENSURE_STATE(internal);

  *aOut = mShared == internal->GetSharedState();
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::AbandonBFCacheEntry()
{
  mShared = nsSHEntryShared::Duplicate(mShared);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry)
{
  *aIsSrcdocEntry = mIsSrcdocEntry;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetSrcdocData(nsAString& aSrcdocData)
{
  aSrcdocData = mSrcdocData;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetSrcdocData(const nsAString& aSrcdocData)
{
  mSrcdocData = aSrcdocData;
  mIsSrcdocEntry = true;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetBaseURI(nsIURI** aBaseURI)
{
  *aBaseURI = mBaseURI;
  NS_IF_ADDREF(*aBaseURI);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetBaseURI(nsIURI* aBaseURI)
{
  mBaseURI = aBaseURI;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetScrollRestorationIsManual(bool* aIsManual)
{
  *aIsManual = mScrollRestorationIsManual;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetScrollRestorationIsManual(bool aIsManual)
{
  mScrollRestorationIsManual = aIsManual;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetChildCount(int32_t* aCount)
{
  *aCount = mChildren.Count();
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::AddChild(nsISHEntry* aChild, int32_t aOffset)
{
  if (aChild) {
    NS_ENSURE_SUCCESS(aChild->SetParent(this), NS_ERROR_FAILURE);
  }

  if (aOffset < 0) {
    mChildren.AppendObject(aChild);
    return NS_OK;
  }

  //
  // Bug 52670: Ensure children are added in order.
  //
  //  Later frames in the child list may load faster and get appended
  //  before earlier frames, causing session history to be scrambled.
  //  By growing the list here, they are added to the right position.
  //
  //  Assert that aOffset will not be so high as to grow us a lot.
  //
  NS_ASSERTION(aOffset < (mChildren.Count() + 1023), "Large frames array!\n");

  bool newChildIsDyn = false;
  if (aChild) {
    aChild->IsDynamicallyAdded(&newChildIsDyn);
  }

  // If the new child is dynamically added, try to add it to aOffset, but if
  // there are non-dynamically added children, the child must be after those.
  if (newChildIsDyn) {
    int32_t lastNonDyn = aOffset - 1;
    for (int32_t i = aOffset; i < mChildren.Count(); ++i) {
      nsISHEntry* entry = mChildren[i];
      if (entry) {
        bool dyn = false;
        entry->IsDynamicallyAdded(&dyn);
        if (dyn) {
          break;
        } else {
          lastNonDyn = i;
        }
      }
    }
    // InsertObjectAt allows only appending one object.
    // If aOffset is larger than Count(), we must first manually
    // set the capacity.
    if (aOffset > mChildren.Count()) {
      mChildren.SetCount(aOffset);
    }
    if (!mChildren.InsertObjectAt(aChild, lastNonDyn + 1)) {
      NS_WARNING("Adding a child failed!");
      aChild->SetParent(nullptr);
      return NS_ERROR_FAILURE;
    }
  } else {
    // If the new child isn't dynamically added, it should be set to aOffset.
    // If there are dynamically added children before that, those must be
    // moved to be after aOffset.
    if (mChildren.Count() > 0) {
      int32_t start = std::min(mChildren.Count() - 1, aOffset);
      int32_t dynEntryIndex = -1;
      nsISHEntry* dynEntry = nullptr;
      for (int32_t i = start; i >= 0; --i) {
        nsISHEntry* entry = mChildren[i];
        if (entry) {
          bool dyn = false;
          entry->IsDynamicallyAdded(&dyn);
          if (dyn) {
            dynEntryIndex = i;
            dynEntry = entry;
          } else {
            break;
          }
        }
      }

      if (dynEntry) {
        nsCOMArray<nsISHEntry> tmp;
        tmp.SetCount(aOffset - dynEntryIndex + 1);
        mChildren.InsertObjectsAt(tmp, dynEntryIndex);
        NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?");
      }
    }

    // Make sure there isn't anything at aOffset.
    if (aOffset < mChildren.Count()) {
      nsISHEntry* oldChild = mChildren[aOffset];
      if (oldChild && oldChild != aChild) {
        NS_ERROR("Adding a child where we already have a child? This may misbehave");
        oldChild->SetParent(nullptr);
      }
    }

    mChildren.ReplaceObjectAt(aChild, aOffset);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::RemoveChild(nsISHEntry* aChild)
{
  NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE);
  bool childRemoved = false;
  bool dynamic = false;
  aChild->IsDynamicallyAdded(&dynamic);
  if (dynamic) {
    childRemoved = mChildren.RemoveObject(aChild);
  } else {
    int32_t index = mChildren.IndexOfObject(aChild);
    if (index >= 0) {
      mChildren.ReplaceObjectAt(nullptr, index);
      childRemoved = true;
    }
  }
  if (childRemoved) {
    aChild->SetParent(nullptr);

    // reduce the child count, i.e. remove empty children at the end
    for (int32_t i = mChildren.Count() - 1; i >= 0 && !mChildren[i]; --i) {
      if (!mChildren.RemoveObjectAt(i)) {
        break;
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetChildAt(int32_t aIndex, nsISHEntry** aResult)
{
  if (aIndex >= 0 && aIndex < mChildren.Count()) {
    *aResult = mChildren[aIndex];
    // yes, mChildren can have holes in it.  AddChild's offset parameter makes
    // that possible.
    NS_IF_ADDREF(*aResult);
  } else {
    *aResult = nullptr;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::ReplaceChild(nsISHEntry* aNewEntry)
{
  NS_ENSURE_STATE(aNewEntry);

  uint64_t docshellID;
  aNewEntry->GetDocshellID(&docshellID);

  uint64_t otherID;
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
    if (mChildren[i] && NS_SUCCEEDED(mChildren[i]->GetDocshellID(&otherID)) &&
        docshellID == otherID) {
      mChildren[i]->SetParent(nullptr);
      mChildren.ReplaceObjectAt(aNewEntry, i);
      return aNewEntry->SetParent(this);
    }
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSHEntry::AddChildShell(nsIDocShellTreeItem* aShell)
{
  NS_ASSERTION(aShell, "Null child shell added to history entry");
  mShared->mChildShells.AppendObject(aShell);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell)
{
  NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex));
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::ClearChildShells()
{
  mShared->mChildShells.Clear();
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetRefreshURIList(nsIMutableArray** aList)
{
  NS_IF_ADDREF(*aList = mShared->mRefreshURIList);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetRefreshURIList(nsIMutableArray* aList)
{
  mShared->mRefreshURIList = aList;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SyncPresentationState()
{
  return mShared->SyncPresentationState();
}

void
nsSHEntry::RemoveFromBFCacheSync()
{
  mShared->RemoveFromBFCacheSync();
}

void
nsSHEntry::RemoveFromBFCacheAsync()
{
  mShared->RemoveFromBFCacheAsync();
}

nsDocShellEditorData*
nsSHEntry::ForgetEditorData()
{
  // XXX jlebar Check how this is used.
  return mShared->mEditorData.forget();
}

void
nsSHEntry::SetEditorData(nsDocShellEditorData* aData)
{
  NS_ASSERTION(!(aData && mShared->mEditorData),
               "We're going to overwrite an owning ref!");
  if (mShared->mEditorData != aData) {
    mShared->mEditorData = aData;
  }
}

bool
nsSHEntry::HasDetachedEditor()
{
  return mShared->mEditorData != nullptr;
}

NS_IMETHODIMP
nsSHEntry::GetStateData(nsIStructuredCloneContainer** aContainer)
{
  NS_ENSURE_ARG_POINTER(aContainer);
  NS_IF_ADDREF(*aContainer = mStateData);
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetStateData(nsIStructuredCloneContainer* aContainer)
{
  mStateData = aContainer;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::IsDynamicallyAdded(bool* aAdded)
{
  *aAdded = mShared->mDynamicallyCreated;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::HasDynamicallyAddedChild(bool* aAdded)
{
  *aAdded = false;
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
    nsISHEntry* entry = mChildren[i];
    if (entry) {
      entry->IsDynamicallyAdded(aAdded);
      if (*aAdded) {
        break;
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetDocshellID(uint64_t* aID)
{
  *aID = mShared->mDocShellID;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetDocshellID(uint64_t aID)
{
  mShared->mDocShellID = aID;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::GetLastTouched(uint32_t* aLastTouched)
{
  *aLastTouched = mShared->mLastTouched;
  return NS_OK;
}

NS_IMETHODIMP
nsSHEntry::SetLastTouched(uint32_t aLastTouched)
{
  mShared->mLastTouched = aLastTouched;
  return NS_OK;
}


/**
 * Author: Hans Engel
 * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun)
 */

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("clojure", function (options) {
    var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", CHARACTER = "string-2",
        ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword";
    var INDENT_WORD_SKIP = options.indentUnit || 2;
    var NORMAL_INDENT_UNIT = options.indentUnit || 2;

    function makeKeywords(str) {
        var obj = {}, words = str.split(" ");
        for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
        return obj;
    }

    var atoms = makeKeywords("true false nil");

    var keywords = makeKeywords(
      "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle");

    var builtins = makeKeywords(
        "* *' *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* *command-line-args* *compile-files* *compile-path* *compiler-options* *data-readers* *e *err* *file* *flush-on-newline* *fn-loader* *in* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *unchecked-math* *use-context-classloader* *verbose-defrecords* *warn-on-reflection* + +' - -' -> ->> ->ArrayChunk ->Vec ->VecNode ->VecSeq -cache-protocol-fn -reset-methods .. / < <= = == > >= EMPTY-NODE accessor aclone add-classpath add-watch agent agent-error agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint biginteger binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* bound? butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec dec' decimal? declare default-data-readers definline definterface defmacro defmethod defmulti defn defn- defonce defprotocol defrecord defstruct deftype delay delay? deliver denominator deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq error-handler error-mode eval even? every-pred every? ex-data ex-info extend extend-protocol extend-type extenders extends? false? ffirst file-seq filter filterv find find-keyword find-ns find-protocol-impl find-protocol-method find-var first flatten float float-array float? floats flush fn fn? fnext fnil for force format frequencies future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator group-by hash hash-combine hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc inc' init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt keep keep-indexed key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map-indexed map? mapcat mapv max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod munge name namespace namespace-munge neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext nthrest num number? numerator object-array odd? or parents partial partition partition-all partition-by pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-dup print-method print-simple print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int rand-nth range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string realized? reduce reduce-kv reductions ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure reify release-pending-sends rem remove remove-all-methods remove-method remove-ns remove-watch repeat repeatedly replace replicate require reset! reset-meta! resolve rest restart-agent resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-error-handler! set-error-mode! set-validator! set? short short-array shorts shuffle shutdown-agents slurp some some-fn sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-symbol? spit split-at split-with str string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync take take-last take-nth take-while test the-ns thread-bound? time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-add-int unchecked-byte unchecked-char unchecked-dec unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-inc-int unchecked-int unchecked-long unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int unchecked-remainder-int unchecked-short unchecked-subtract unchecked-subtract-int underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector-of vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision with-redefs with-redefs-fn xml-seq zero? zipmap *default-data-reader-fn* as-> cond-> cond->> reduced reduced? send-via set-agent-send-executor! set-agent-send-off-executor! some-> some->>");

    var indentKeys = makeKeywords(
        // Built-ins
        "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " +

        // Binding forms
        "let letfn binding loop for doseq dotimes when-let if-let " +

        // Data structures
        "defstruct struct-map assoc " +

        // clojure.test
        "testing deftest " +

        // contrib
        "handler-case handle dotrace deftrace");

    var tests = {
        digit: /\d/,
        digit_or_colon: /[\d:]/,
        hex: /[0-9a-f]/i,
        sign: /[+-]/,
        exponent: /e/i,
        keyword_char: /[^\s\(\[\;\)\]]/,
        symbol: /[\w*+!\-\._?:<>\/]/
    };

    function stateStack(indent, type, prev) { // represents a state stack object
        this.indent = indent;
        this.type = type;
        this.prev = prev;
    }

    function pushStack(state, indent, type) {
        state.indentStack = new stateStack(indent, type, state.indentStack);
    }

    function popStack(state) {
        state.indentStack = state.indentStack.prev;
    }

    function isNumber(ch, stream){
        // hex
        if ( ch === '0' && stream.eat(/x/i) ) {
            stream.eatWhile(tests.hex);
            return true;
        }

        // leading sign
        if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) {
          stream.eat(tests.sign);
          ch = stream.next();
        }

        if ( tests.digit.test(ch) ) {
            stream.eat(ch);
            stream.eatWhile(tests.digit);

            if ( '.' == stream.peek() ) {
                stream.eat('.');
                stream.eatWhile(tests.digit);
            }

            if ( stream.eat(tests.exponent) ) {
                stream.eat(tests.sign);
                stream.eatWhile(tests.digit);
            }

            return true;
        }

        return false;
    }

    // Eat character that starts after backslash \
    function eatCharacter(stream) {
        var first = stream.next();
        // Read special literals: backspace, newline, space, return.
        // Just read all lowercase letters.
        if (first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) {
            return;
        }
        // Read unicode character: \u1000 \uA0a1
        if (first === "u") {
            stream.match(/[0-9a-z]{4}/i, true);
        }
    }

    return {
        startState: function () {
            return {
                indentStack: null,
                indentation: 0,
                mode: false
            };
        },

        token: function (stream, state) {
            if (state.indentStack == null && stream.sol()) {
                // update indentation, but only if indentStack is empty
                state.indentation = stream.indentation();
            }

            // skip spaces
            if (stream.eatSpace()) {
                return null;
            }
            var returnType = null;

            switch(state.mode){
                case "string": // multi-line string parsing mode
                    var next, escaped = false;
                    while ((next = stream.next()) != null) {
                        if (next == "\"" && !escaped) {

                            state.mode = false;
                            break;
                        }
                        escaped = !escaped && next == "\\";
                    }
                    returnType = STRING; // continue on in string mode
                    break;
                default: // default parsing mode
                    var ch = stream.next();

                    if (ch == "\"") {
                        state.mode = "string";
                        returnType = STRING;
                    } else if (ch == "\\") {
                        eatCharacter(stream);
                        returnType = CHARACTER;
                    } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
                        returnType = ATOM;
                    } else if (ch == ";") { // comment
                        stream.skipToEnd(); // rest of the line is a comment
                        returnType = COMMENT;
                    } else if (isNumber(ch,stream)){
                        returnType = NUMBER;
                    } else if (ch == "(" || ch == "[" || ch == "{" ) {
                        var keyWord = '', indentTemp = stream.column(), letter;
                        /**
                        Either
                        (indent-word ..
                        (non-indent-word ..
                        (;something else, bracket, etc.
                        */

                        if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) {
                            keyWord += letter;
                        }

                        if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) ||
                                                   /^(?:def|with)/.test(keyWord))) { // indent-word
                            pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
                        } else { // non-indent word
                            // we continue eating the spaces
                            stream.eatSpace();
                            if (stream.eol() || stream.peek() == ";") {
                                // nothing significant after
                                // we restart indentation the user defined spaces after
                                pushStack(state, indentTemp + NORMAL_INDENT_UNIT, ch);
                            } else {
                                pushStack(state, indentTemp + stream.current().length, ch); // else we match
                            }
                        }
                        stream.backUp(stream.current().length - 1); // undo all the eating

                        returnType = BRACKET;
                    } else if (ch == ")" || ch == "]" || ch == "}") {
                        returnType = BRACKET;
                        if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : (ch == "]" ? "[" :"{"))) {
                            popStack(state);
                        }
                    } else if ( ch == ":" ) {
                        stream.eatWhile(tests.symbol);
                        return ATOM;
                    } else {
                        stream.eatWhile(tests.symbol);

                        if (keywords && keywords.propertyIsEnumerable(stream.current())) {
                            returnType = KEYWORD;
                        } else if (builtins && builtins.propertyIsEnumerable(stream.current())) {
                            returnType = BUILTIN;
                        } else if (atoms && atoms.propertyIsEnumerable(stream.current())) {
                            returnType = ATOM;
                        } else returnType = null;
                    }
            }

            return returnType;
        },

        indent: function (state) {
            if (state.indentStack == null) return state.indentation;
            return state.indentStack.indent;
        },

        lineComment: ";;"
    };
});

CodeMirror.defineMIME("text/x-clojure", "clojure");

});


class BookmarkButton extends React.Component {
  constructor(props) {
    super(props);

    this.state = { bookmarked: this.props.bookmarked };

    this.onUnbookmarkClick = this.onUnbookmarkClick.bind(this);
    this.onBookmarkClick = this.onBookmarkClick.bind(this);
  }

  componentWillMount() {
    const { bookmarkableType, bookmarkableId } = this.props;
    this.token = PubSub.subscribe('BookmarkButton:onClick', (msg, data) => {
      if (bookmarkableType === data.type && bookmarkableId === data.id) {
        this.setState({ bookmarked: data.bookmarked });
      }
    });
  }

  render () {
    return (

        {this.renderBookmarkButton()}

    );
  }

  renderBookmarkButton() {
    if (this.state.bookmarked) {
      return (
        <button className="unbookmark-button" onClick={this.onUnbookmarkClick}>

          Unbookmark
        </button>
      );
    } else {
      return (
        <button className="bookmark-button" onClick={this.onBookmarkClick}>

          Bookmark
        </button>
      );
    }
  }

  onUnbookmarkClick(e) {
    $.ajax({
      url: this.props.unbookmarkEndpoint,
      method: 'DELETE',
      dataType: 'json',
      success: (data) => {
        this.setState({ bookmarked: data.bookmarked });
        PubSub.publish('BookmarkButton:onClick', data);
      }
    });
  }

  onBookmarkClick(e) {
    $.ajax({
      url: this.props.bookmarkEndpoint,
      method: 'POST',
      dataType: 'json',
      success: (data) => {
        this.setState({ bookmarked: data.bookmarked });
        PubSub.publish('BookmarkButton:onClick', data);
      }
    });
  }
}

BookmarkButton.propTypes = {
  bookmarked: React.PropTypes.bool.isRequired,
  unbookmarkEndpoint: React.PropTypes.string.isRequired,
  bookmarkEndpoint: React.PropTypes.string.isRequired,
  bookmarkableType: React.PropTypes.string.isRequired,
  bookmarkableId: React.PropTypes.number.isRequired
};


#
# Copyright © 2012 - 2020 Michal Čihař <michal@cihar.com>
#
# This file is part of Weblate <https://weblate.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 3 of the License, 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 this program.  If not, see <https://www.gnu.org/licenses/>.
#
default_app_config = "weblate.vcs.apps.VCSConfig"


package com.netflix.dyno.jedis.operation;

import com.netflix.dyno.connectionpool.Operation;
import com.netflix.dyno.jedis.OpName;
import redis.clients.jedis.Jedis;

import java.util.List;

/**
 * A poor man's solution for multikey operation. This is similar to
 * basekeyoperation just that it takes a list of keys as arguments. For
 * token aware, we just use the first key in the list. Ideally we should be
 * doing a scatter gather
 */
public abstract class MultiKeyOperation<T> implements Operation<Jedis, T> {

    private final List<String> keys;
    private final List<byte[]> binaryKeys;
    private final OpName op;

    public MultiKeyOperation(final List keys, final OpName o) {
        Object firstKey = (keys != null && keys.size() > 0) ? keys.get(0) : null;

        if (firstKey != null) {
            if (firstKey instanceof String) {//string key
                this.keys = keys;
                this.binaryKeys = null;
            } else if (firstKey instanceof byte[]) {//binary key
                this.keys = null;
                this.binaryKeys = keys;
            } else {//something went wrong here
                this.keys = null;
                this.binaryKeys = null;
            }
        } else {
            this.keys = null;
            this.binaryKeys = null;
        }

        this.op = o;
    }

    @Override
    public String getName() {
        return op.name();
    }

    /**
     * Sends back only the first key of the multi key operation.
     * @return
     */
    @Override
    public String getStringKey() {
        return (this.keys != null) ? this.keys.get(0) : null;
    }

    public byte[] getBinaryKey() {
        return (binaryKeys != null) ? binaryKeys.get(0) : null;
    }

}