/*
 * This file is part of the libCEC(R) library.
 *
 * libCEC(R) is Copyright (C) 2011-2015 Pulse-Eight Limited.  All rights reserved.
 * libCEC(R) is an original work, containing original code.
 *
 * libCEC(R) is a trademark of Pulse-Eight Limited.
 *
 * This program is dual-licensed; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 *
 *
 * Alternatively, you can license this library under a commercial license,
 * please contact Pulse-Eight Licensing for more information.
 *
 * For more information contact:
 * Pulse-Eight Licensing       <license@pulse-eight.com>
 *     http://www.pulse-eight.com/
 *     http://www.pulse-eight.net/
 */

#include "env.h"
#include "platform/util/timeutils.h"
#include "CECClient.h"

#include "CECProcessor.h"
#include "LibCEC.h"
#include "CECTypeUtils.h"
#include "devices/CECPlaybackDevice.h"
#include "devices/CECAudioSystem.h"
#include "devices/CECTV.h"
#include "implementations/CECCommandHandler.h"
#include <stdio.h>

using namespace CEC;

#define LIB_CEC     m_processor->GetLib()
#define ToString(x) CCECTypeUtils::ToString(x)

CCECClient::CCECClient(CCECProcessor *processor, const libcec_configuration &configuration) :
    m_processor(processor),
    m_bInitialised(false),
    m_bRegistered(false),
    m_iCurrentButton(CEC_USER_CONTROL_CODE_UNKNOWN),
    m_initialButtontime(0),
    m_updateButtontime(0),
    m_repeatButtontime(0),
    m_releaseButtontime(0),
    m_pressedButtoncount(0),
    m_releasedButtoncount(0),
    m_bSeenButtonRelease(false),
    m_iPreventForwardingPowerOffCommand(0),
    m_iLastKeypressTime(0),
    m_iLastKeyreleaseTime(0)
{
  m_lastKeypress.keycode = CEC_USER_CONTROL_CODE_UNKNOWN;
  m_lastKeypress.duration = 0;
  m_configuration.Clear();
  // set the initial configuration
  SetConfiguration(configuration);
  CreateThread(false);
}

CCECClient::~CCECClient(void)
{
  StopThread();
  CCallbackWrap* cb;
  while (!m_callbackCalls.IsEmpty())
    if (m_callbackCalls.Pop(cb, 0))
      delete cb;

  // unregister the client
  if (m_processor && IsRegistered())
    m_processor->UnregisterClient(this);
}

bool CCECClient::IsInitialised(void)
{
  CLockObject lock(m_mutex);
  return m_bInitialised && m_processor;
}

void CCECClient::SetInitialised(bool bSetTo)
{
  CLockObject lock(m_mutex);
  m_bInitialised = bSetTo;
}

bool CCECClient::IsRegistered(void)
{
  CLockObject lock(m_mutex);
  return m_bRegistered && m_processor;
}

void CCECClient::SetRegistered(bool bSetTo)
{
  CLockObject lock(m_mutex);
  m_bRegistered = bSetTo;
}

bool CCECClient::OnRegister(void)
{
  // return false if already initialised
  if (IsInitialised())
    return true;

  // get all device we control
  CECDEVICEVEC devices;
  m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);

  // return false when no devices were found
  if (devices.empty())
  {
    LIB_CEC->AddLog(CEC_LOG_WARNING, "cannot find the primary device (logical address %x)", GetPrimaryLogicalAddress());
    return false;
  }

  // mark as initialised
  SetInitialised(true);

  // configure all devices
  for (CECDEVICEVEC::iterator it = devices.begin(); it != devices.end(); it++)
  {
    // only set our OSD name for the primary device
    if ((*it)->GetLogicalAddress() == GetPrimaryLogicalAddress())
      (*it)->SetOSDName(m_configuration.strDeviceName);

    // set the default menu language for devices we control
    (*it)->SetMenuLanguage(std::string(m_configuration.strDeviceLanguage, 3));
  }

  // set the physical address, unless already resolved (e.g. via SetHDMIPort during RegisterClient)
  if (!CLibCEC::IsValidPhysicalAddress(m_configuration.iPhysicalAddress) ||
      m_configuration.iPhysicalAddress == CEC_PHYSICAL_ADDRESS_TV)
    SetPhysicalAddress(m_configuration);

  // make the primary device the active source if the option is set
  if (m_configuration.bActivateSource == 1)
    GetPrimaryDevice()->ActivateSource(500);

  SaveConfiguration(m_configuration);

  return true;
}

bool CCECClient::SetHDMIPort(const cec_logical_address iBaseDevice, const uint8_t iPort, bool bForce /* = false */)
{
  bool bReturn(false);

  // limit the HDMI port range to 1-15
  if (iPort < CEC_MIN_HDMI_PORTNUMBER ||
      iPort > CEC_MAX_HDMI_PORTNUMBER)
    return bReturn;

  // update the configuration
  {
    CLockObject lock(m_mutex);
    if (m_configuration.baseDevice == iBaseDevice &&
        m_configuration.iHDMIPort == iPort &&
        CLibCEC::IsValidPhysicalAddress(m_configuration.iPhysicalAddress) &&
        m_configuration.iPhysicalAddress > 0)
      return true;
    m_configuration.baseDevice = iBaseDevice;
    m_configuration.iHDMIPort  = iPort;
    m_configuration.bAutodetectAddress = 0;
  }

  LIB_CEC->AddLog(CEC_LOG_NOTICE, "setting HDMI port to %d on device %s (%d)", iPort, ToString(iBaseDevice), (int)iBaseDevice);

  // don't continue if the connection isn't opened
  if (!m_processor->CECInitialised() && !bForce)
    return true;

  // get the PA of the base device
  uint16_t iPhysicalAddress(CEC_INVALID_PHYSICAL_ADDRESS);
  CCECBusDevice *baseDevice = m_processor->GetDevice(iBaseDevice);
  if (baseDevice)
    iPhysicalAddress = baseDevice->GetPhysicalAddress(GetPrimaryLogicalAddress());

  // add our port number
  if (iPhysicalAddress <= CEC_MAX_PHYSICAL_ADDRESS)
  {
    if (iPhysicalAddress == 0)
      iPhysicalAddress += 0x1000 * iPort;
    else if (iPhysicalAddress % 0x1000 == 0)
      iPhysicalAddress += 0x100 * iPort;
    else if (iPhysicalAddress % 0x100 == 0)
      iPhysicalAddress += 0x10 * iPort;
    else if (iPhysicalAddress % 0x10 == 0)
      iPhysicalAddress += iPort;

    bReturn = true;
  }

  // set the default address when something went wrong
  if (!bReturn)
  {
    uint16_t iEepromAddress = m_processor->GetPhysicalAddressFromEeprom();
    if (CLibCEC::IsValidPhysicalAddress(iEepromAddress))
    {
      LIB_CEC->AddLog(CEC_LOG_WARNING, "failed to set the physical address to %04X, setting it to the value that was saved in the eeprom: %04X", iPhysicalAddress, iEepromAddress);
      iPhysicalAddress = iEepromAddress;
      bReturn = true;
    }
    else
    {
      LIB_CEC->AddLog(CEC_LOG_WARNING, "failed to set the physical address to %04X, setting it to the default value %04X", iPhysicalAddress, CEC_DEFAULT_PHYSICAL_ADDRESS);
      iPhysicalAddress = CEC_DEFAULT_PHYSICAL_ADDRESS;
    }
  }

  return SetPhysicalAddress(iPhysicalAddress);
}

void CCECClient::ResetPhysicalAddress(void)
{
  LIB_CEC->AddLog(CEC_LOG_DEBUG, "resetting HDMI port and base device to defaults");
  SetHDMIPort(CECDEVICE_TV, CEC_DEFAULT_HDMI_PORT);
}

bool CCECClient::SetPhysicalAddress(const libcec_configuration &configuration)
{
  // override the physical address from configuration.iPhysicalAddress if it's set
  if (CLibCEC::IsValidPhysicalAddress(configuration.iPhysicalAddress) &&
    (configuration.iPhysicalAddress != CEC_PHYSICAL_ADDRESS_TV) &&
    SetPhysicalAddress(configuration.iPhysicalAddress))
  {
    if (m_configuration.bAutodetectAddress == 0)
      LIB_CEC->AddLog(CEC_LOG_DEBUG, "using provided physical address %04X", configuration.iPhysicalAddress);
    CLockObject lock(m_mutex);
    m_configuration.baseDevice       = CECDEVICE_UNKNOWN;
    m_configuration.iHDMIPort        = CEC_HDMI_PORTNUMBER_NONE;
    m_configuration.iPhysicalAddress = configuration.iPhysicalAddress;
    return true;
  }

  // try to autodetect the address
  if (AutodetectPhysicalAddress())
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "using auto-detected physical address %04X", m_configuration.iPhysicalAddress);
    {
      CLockObject lock(m_mutex);
      m_configuration.baseDevice       = CECDEVICE_UNKNOWN;
      m_configuration.iHDMIPort        = CEC_HDMI_PORTNUMBER_NONE;
      m_configuration.iPhysicalAddress = configuration.iPhysicalAddress;
    }
    SetDevicePhysicalAddress(m_configuration.iPhysicalAddress);
    return true;
  }

  // use the base device + hdmi port settings
  if ((configuration.baseDevice != CECDEVICE_UNKNOWN) &&
    (configuration.iHDMIPort != CEC_HDMI_PORTNUMBER_NONE) &&
    SetHDMIPort(configuration.baseDevice, configuration.iHDMIPort))
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "using device/input physical address %04X", m_configuration.iPhysicalAddress);
    return true;
  }

  // reset to defaults if something went wrong
  if (m_processor->CECInitialised()) {
    ResetPhysicalAddress();
  }
  return false;
}

bool CCECClient::SetPhysicalAddress(const uint16_t iPhysicalAddress)
{
  // update the configuration
  {
    CLockObject lock(m_mutex);
    if (m_configuration.iPhysicalAddress == iPhysicalAddress)
      return true;
    m_configuration.iPhysicalAddress = iPhysicalAddress;
  }
  LIB_CEC->AddLog(CEC_LOG_DEBUG, "changing physical address to %04X", iPhysicalAddress);

  // set the physical address for each device
  SetDevicePhysicalAddress(iPhysicalAddress);

  // and send back the updated configuration
  QueueConfigurationChanged(m_configuration);
  return true;
}

void CCECClient::SetSupportedDeviceTypes(void)
{
  cec_device_type_list types;
  types.Clear();

  // get the command handler for the tv
  CCECCommandHandler *tvHandler = m_processor->GetTV()->GetHandler();
  if (!tvHandler)
    return;

  // check all device types
  for (uint8_t iPtr = 0; iPtr < 5; iPtr++)
  {
    if (m_configuration.deviceTypes.types[iPtr] == CEC_DEVICE_TYPE_RESERVED)
      continue;

    // get the supported device type. the handler will replace types it doesn't support by one it does support
    cec_device_type type = tvHandler->GetReplacementDeviceType(m_configuration.deviceTypes.types[iPtr]);
    if (!types.IsSet(type))
      types.Add(type);
  }
  m_processor->GetTV()->MarkHandlerReady();

  // set the new type list
  m_configuration.deviceTypes = types;

  SaveConfiguration(m_configuration);
}

bool CCECClient::AllocateLogicalAddresses(void)
{
  // reset all previous LAs that were set
  m_configuration.logicalAddresses.Clear();

  // get the supported device types from the command handler of the TV
  SetSupportedDeviceTypes();

  // display an error if no device types are set
  if (m_configuration.deviceTypes.IsEmpty())
  {
    LIB_CEC->AddLog(CEC_LOG_ERROR, "no device types given");
    return false;
  }

  // check each entry of the list
  for (uint8_t iPtr = 0; iPtr < 5; iPtr++)
  {
    if (m_configuration.deviceTypes.types[iPtr] == CEC_DEVICE_TYPE_RESERVED)
      continue;

    // find an LA for this type
    cec_logical_address address(CECDEVICE_UNKNOWN);
    if (m_configuration.deviceTypes.types[iPtr] == CEC_DEVICE_TYPE_TV)
      address = CECDEVICE_TV;
    if (m_configuration.deviceTypes.types[iPtr] == CEC_DEVICE_TYPE_RECORDING_DEVICE)
      address = AllocateLogicalAddressRecordingDevice();
    if (m_configuration.deviceTypes.types[iPtr] == CEC_DEVICE_TYPE_TUNER)
      address = AllocateLogicalAddressTuner();
    if (m_configuration.deviceTypes.types[iPtr] == CEC_DEVICE_TYPE_PLAYBACK_DEVICE)
      address = AllocateLogicalAddressPlaybackDevice();
    if (m_configuration.deviceTypes.types[iPtr] == CEC_DEVICE_TYPE_AUDIO_SYSTEM)
      address = AllocateLogicalAddressAudioSystem();

    // display an error if no LA could be allocated
    if (address == CECDEVICE_UNKNOWN)
    {
      LIB_CEC->AddLog(CEC_LOG_ERROR, "%s - failed to allocate device '%d', type '%s'", __FUNCTION__, iPtr, ToString(m_configuration.deviceTypes.types[iPtr]));
      return false;
    }

    // display the registered LA
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - device '%d', type '%s', LA '%X'", __FUNCTION__, iPtr, ToString(m_configuration.deviceTypes.types[iPtr]), address);
    m_configuration.logicalAddresses.Set(address);
  }

  SaveConfiguration(m_configuration);

  return true;
}

cec_logical_address CCECClient::AllocateLogicalAddressRecordingDevice(void)
{
  cec_logical_address retVal(CECDEVICE_UNKNOWN);

  LIB_CEC->AddLog(CEC_LOG_DEBUG, "detecting logical address for type 'recording device'");
  if (m_processor->TryLogicalAddress(CECDEVICE_RECORDINGDEVICE1, m_configuration.cecVersion))
    retVal = CECDEVICE_RECORDINGDEVICE1;
  else if (m_processor->TryLogicalAddress(CECDEVICE_RECORDINGDEVICE2, m_configuration.cecVersion))
    retVal = CECDEVICE_RECORDINGDEVICE2;
  else if (m_processor->TryLogicalAddress(CECDEVICE_RECORDINGDEVICE3, m_configuration.cecVersion))
    retVal = CECDEVICE_RECORDINGDEVICE3;

  return retVal;
}

cec_logical_address CCECClient::AllocateLogicalAddressTuner(void)
{
  cec_logical_address retVal(CECDEVICE_UNKNOWN);

  LIB_CEC->AddLog(CEC_LOG_DEBUG, "detecting logical address for type 'tuner'");
  if (m_processor->TryLogicalAddress(CECDEVICE_TUNER1, m_configuration.cecVersion))
    retVal = CECDEVICE_TUNER1;
  else if (m_processor->TryLogicalAddress(CECDEVICE_TUNER2, m_configuration.cecVersion))
    retVal = CECDEVICE_TUNER2;
  else if (m_processor->TryLogicalAddress(CECDEVICE_TUNER3, m_configuration.cecVersion))
    retVal = CECDEVICE_TUNER3;
  else if (m_processor->TryLogicalAddress(CECDEVICE_TUNER4, m_configuration.cecVersion))
    retVal = CECDEVICE_TUNER4;

  return retVal;
}

cec_logical_address CCECClient::AllocateLogicalAddressPlaybackDevice(void)
{
  cec_logical_address retVal(CECDEVICE_UNKNOWN);

  LIB_CEC->AddLog(CEC_LOG_DEBUG, "detecting logical address for type 'playback device'");
  if (m_processor->TryLogicalAddress(CECDEVICE_PLAYBACKDEVICE1, m_configuration.cecVersion))
    retVal = CECDEVICE_PLAYBACKDEVICE1;
  else if (m_processor->TryLogicalAddress(CECDEVICE_PLAYBACKDEVICE2, m_configuration.cecVersion))
    retVal = CECDEVICE_PLAYBACKDEVICE2;
  else if (m_processor->TryLogicalAddress(CECDEVICE_PLAYBACKDEVICE3, m_configuration.cecVersion))
    retVal = CECDEVICE_PLAYBACKDEVICE3;

  return retVal;
}

cec_logical_address CCECClient::AllocateLogicalAddressAudioSystem(void)
{
  cec_logical_address retVal(CECDEVICE_UNKNOWN);

  LIB_CEC->AddLog(CEC_LOG_DEBUG, "detecting logical address for type 'audiosystem'");
  if (m_processor->TryLogicalAddress(CECDEVICE_AUDIOSYSTEM, m_configuration.cecVersion))
    retVal = CECDEVICE_AUDIOSYSTEM;

  return retVal;
}

CCECBusDevice *CCECClient::GetDeviceByType(const cec_device_type type) const
{
  // get all devices that match our logical addresses
  CECDEVICEVEC devices;
  m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);

  // filter the type we need
  CCECDeviceMap::FilterType(type, devices);

  return devices.empty() ?
      NULL :
      *devices.begin();
}

bool CCECClient::ChangeDeviceType(const cec_device_type from, const cec_device_type to)
{
  if (from == to)
    return true;

  LIB_CEC->AddLog(CEC_LOG_NOTICE, "changing device type '%s' into '%s'", ToString(from), ToString(to));

  {
    CLockObject lock(m_mutex);

    // get the previous device that was allocated
    CCECBusDevice *previousDevice = GetDeviceByType(from);
    if (!previousDevice)
      return false;

    // change the type in the device type list
    bool bChanged(false);
    for (uint8_t iPtr = 0; iPtr < 5; iPtr++)
    {
      if (m_configuration.deviceTypes.types[iPtr] == CEC_DEVICE_TYPE_RESERVED)
        continue;

      if (m_configuration.deviceTypes.types[iPtr] == from)
      {
        bChanged = true;
        m_configuration.deviceTypes.types[iPtr] = to;
      }
      else if (m_configuration.deviceTypes.types[iPtr] == to && bChanged)
      {
        // ensure that dupes are removed
        m_configuration.deviceTypes.types[iPtr] = CEC_DEVICE_TYPE_RESERVED;
      }
    }
  }

  // re-register the client to set the new ackmask
  if (!m_processor->RegisterClient(this))
    return false;

  SaveConfiguration(m_configuration);

  return true;
}

bool CCECClient::SetLogicalAddress(const cec_logical_address iLogicalAddress)
{
  bool bReturn(true);

  if (GetPrimaryLogicalAddress() != iLogicalAddress)
  {
    LIB_CEC->AddLog(CEC_LOG_NOTICE, "setting primary logical address to %1x", iLogicalAddress);
    {
      CLockObject lock(m_mutex);
      m_configuration.logicalAddresses.primary = iLogicalAddress;
      m_configuration.logicalAddresses.Set(iLogicalAddress);
    }

    bReturn = m_processor->RegisterClient(this);

    if (bReturn)
      SaveConfiguration(m_configuration);
  }

  return bReturn;
}

bool CCECClient::Transmit(const cec_command &data, bool bIsReply)
{
  return m_processor ? m_processor->Transmit(data, bIsReply) : false;
}

bool CCECClient::SendPowerOnDevices(const cec_logical_address address /* = CECDEVICE_TV */)
{
  // if the broadcast address if set as destination, read the wakeDevices setting
  if (address == CECDEVICE_BROADCAST)
  {
    CECDEVICEVEC devices;
    m_processor->GetDevices()->GetWakeDevices(m_configuration, devices);
    return m_processor->PowerOnDevices(GetPrimaryLogicalAddress(), devices);
  }

  return m_processor->PowerOnDevice(GetPrimaryLogicalAddress(), address);
}

bool CCECClient::SendStandbyDevices(const cec_logical_address address /* = CECDEVICE_BROADCAST */)
{
  // if the broadcast address if set as destination, read the standbyDevices setting
  if (address == CECDEVICE_BROADCAST)
  {
    CECDEVICEVEC devices;
    m_processor->GetDevices()->GetPowerOffDevices(m_configuration, devices);
    return m_processor->StandbyDevices(GetPrimaryLogicalAddress(), devices);
  }

  return m_processor->StandbyDevice(GetPrimaryLogicalAddress(), address);
}

bool CCECClient::SendSetActiveSource(const cec_device_type type /* = CEC_DEVICE_TYPE_RESERVED */)
{
  // get the devices that are controlled by us
  CECDEVICEVEC devices;
  m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);

  // filter out the device that matches the given type
  if (type != CEC_DEVICE_TYPE_RESERVED)
    CCECDeviceMap::FilterType(type, devices);

  // no devices left, re-fetch the list of devices that are controlled by us
  if (devices.empty())
    m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);

  if (!devices.empty())
  {
    // get the first device from the list
    CCECBusDevice *device = *devices.begin();

    // and activate it
    if (!m_processor->CECInitialised())
      device->MarkAsActiveSource();
    else if (device->HasValidPhysicalAddress())
      return device->ActivateSource();
  }

  return false;
}

CCECPlaybackDevice *CCECClient::GetPlaybackDevice(void)
{
  CCECPlaybackDevice *device(NULL);
  CECDEVICEVEC devices;

  // get the playback devices
  m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);
  CCECDeviceMap::FilterType(CEC_DEVICE_TYPE_PLAYBACK_DEVICE, devices);

  // no matches, get the recording devices
  if (devices.empty())
  {
    m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);
    CCECDeviceMap::FilterType(CEC_DEVICE_TYPE_RECORDING_DEVICE, devices);
  }

  // get the first device that matches, and cast it to CCECPlaybackDevice
  if (!devices.empty())
    device = (*devices.begin())->AsPlaybackDevice();

  return device;
}

cec_logical_address CCECClient::GetPrimaryLogicalAddress(void)
{
  CLockObject lock(m_mutex);
  return m_configuration.logicalAddresses.primary;
}

CCECBusDevice *CCECClient::GetPrimaryDevice(void)
{
  return m_processor->GetDevice(GetPrimaryLogicalAddress());
}

bool CCECClient::SendSetDeckControlMode(const cec_deck_control_mode mode, bool bSendUpdate /* = true */)
{
  // find a playback device that we control
  CCECPlaybackDevice *device = GetPlaybackDevice();
  if (device)
  {
    // and set the deck control mode if there is a match
    device->SetDeckControlMode(mode);
    if (bSendUpdate)
      return device->TransmitDeckStatus(CECDEVICE_TV, false);
    return true;
  }

  // no match
  return false;
}

bool CCECClient::SendSetDeckInfo(const cec_deck_info info, bool bSendUpdate /* = true */)
{
  // find a playback device that we control
  CCECPlaybackDevice *device = GetPlaybackDevice();
  if (device)
  {
    // and set the deck status if there is a match
    device->SetDeckStatus(info);
    if (bSendUpdate)
      return device->AsPlaybackDevice()->TransmitDeckStatus(CECDEVICE_TV, false);
    return true;
  }

  // no match
  return false;
}

bool CCECClient::SendSetMenuState(const cec_menu_state state, bool bSendUpdate /* = true */)
{
  CECDEVICEVEC devices;

  // set the menu state for all devices that are controlled by us
  m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);
  for (CECDEVICEVEC::iterator it = devices.begin(); it != devices.end(); it++)
  {
    (*it)->SetMenuState(state);
    if (bSendUpdate)
      (*it)->TransmitMenuState(CECDEVICE_TV, false);
  }

  return true;
}

bool CCECClient::SendSetInactiveView(void)
{
  CECDEVICEVEC devices;

  // mark all devices that are controlled by us as inactive source
  m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);
  for (CECDEVICEVEC::iterator it = devices.begin(); it != devices.end(); it++)
  {
    if ((*it)->IsActiveSource())
    {
      (*it)->MarkAsInactiveSource();
      return (*it)->TransmitInactiveSource();
    }
  }

  return true;
}

bool CCECClient::SendSetOSDString(const cec_logical_address iLogicalAddress, const cec_display_control duration, const char *strMessage)
{
  CCECBusDevice *primary = GetPrimaryDevice();
  if (primary)
    return primary->TransmitOSDString(iLogicalAddress, duration, strMessage, false);

  return false;
}

cec_version CCECClient::GetDeviceCecVersion(const cec_logical_address iAddress)
{
  CCECBusDevice *device = m_processor->GetDevice(iAddress);
  if (device)
    return device->GetCecVersion(GetPrimaryLogicalAddress());
  return CEC_VERSION_UNKNOWN;
}

std::string CCECClient::GetDeviceMenuLanguage(const cec_logical_address iAddress)
{
  CCECBusDevice *device = m_processor->GetDevice(iAddress);
  return !!device ?
      device->GetMenuLanguage(GetPrimaryLogicalAddress()) :
      "??";
}

std::string CCECClient::GetDeviceOSDName(const cec_logical_address iAddress)
{
  CCECBusDevice *device = m_processor->GetDevice(iAddress);
  return !!device?
      device->GetOSDName(GetPrimaryLogicalAddress()) :
      "";
}

uint16_t CCECClient::GetDevicePhysicalAddress(const cec_logical_address iAddress)
{
  CCECBusDevice *device = m_processor->GetDevice(iAddress);
  if (device)
    return device->GetPhysicalAddress(GetPrimaryLogicalAddress());
  return CEC_INVALID_PHYSICAL_ADDRESS;
}

cec_power_status CCECClient::GetDevicePowerStatus(const cec_logical_address iAddress)
{
  CCECBusDevice *device = m_processor->GetDevice(iAddress);
  if (device)
    return device->GetPowerStatus(GetPrimaryLogicalAddress());
  return CEC_POWER_STATUS_UNKNOWN;
}

uint32_t CCECClient::GetDeviceVendorId(const cec_logical_address iAddress)
{
  CCECBusDevice *device = m_processor->GetDevice(iAddress);
  if (device)
    return device->GetVendorId(GetPrimaryLogicalAddress());
  return CEC_VENDOR_UNKNOWN;
}

uint8_t CCECClient::SendVolumeUp(bool bSendRelease /* = true */)
{
  cec_logical_address primary(GetPrimaryLogicalAddress());
  CCECAudioSystem* audio(m_processor->GetAudioSystem());

  if (primary == CECDEVICE_UNKNOWN)
    return (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;

  if (!audio || !audio->IsPresent())
  {
    CCECTV* tv(m_processor->GetTV());
    tv->TransmitVolumeUp(primary, bSendRelease);
    return (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;
  }
  else
  {
    return audio->VolumeUp(primary, bSendRelease);
  }
}

uint8_t CCECClient::SendVolumeDown(bool bSendRelease /* = true */)
{
  cec_logical_address primary(GetPrimaryLogicalAddress());
  CCECAudioSystem* audio(m_processor->GetAudioSystem());

  if (primary == CECDEVICE_UNKNOWN)
    return (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;

  if (!audio || !audio->IsPresent())
  {
    CCECTV* tv(m_processor->GetTV());
    tv->TransmitVolumeDown(primary, bSendRelease);
    return (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;
  }
  else
  {
    return audio->VolumeDown(primary, bSendRelease);
  }
}

uint8_t CCECClient::SendMuteAudio(void)
{
  cec_logical_address primary(GetPrimaryLogicalAddress());
  CCECAudioSystem* audio(m_processor->GetAudioSystem());

  if (primary == CECDEVICE_UNKNOWN)
    return (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;

  if (!audio || !audio->IsPresent())
  {
    CCECTV* tv(m_processor->GetTV());
    tv->TransmitMuteAudio(primary);
    return (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;
  }
  else
  {
    return audio->MuteAudio(primary);
  }

}

uint8_t CCECClient::AudioToggleMute(void)
{
  CCECBusDevice *device = GetPrimaryDevice();
  CCECAudioSystem *audio = m_processor->GetAudioSystem();

  return device && audio && audio->IsPresent() ?
      audio->MuteAudio(device->GetLogicalAddress()) :
      (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;
}

uint8_t CCECClient::AudioMute(void)
{
  CCECBusDevice *device = GetPrimaryDevice();
  CCECAudioSystem *audio = m_processor->GetAudioSystem();
  uint8_t iStatus = device && audio && audio->IsPresent() ? audio->GetAudioStatus(device->GetLogicalAddress()) : (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;
  if ((iStatus & CEC_AUDIO_MUTE_STATUS_MASK) != CEC_AUDIO_MUTE_STATUS_MASK)
    iStatus = audio->MuteAudio(device->GetLogicalAddress());

  return iStatus;
}

uint8_t CCECClient::AudioUnmute(void)
{
  CCECBusDevice *device = GetPrimaryDevice();
  CCECAudioSystem *audio = m_processor->GetAudioSystem();
  uint8_t iStatus = device && audio && audio->IsPresent() ? audio->GetAudioStatus(device->GetLogicalAddress()) : (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;
  if ((iStatus & CEC_AUDIO_MUTE_STATUS_MASK) == CEC_AUDIO_MUTE_STATUS_MASK)
    iStatus = audio->MuteAudio(device->GetLogicalAddress());

  return iStatus;
}

uint8_t CCECClient::AudioStatus(void)
{
  CCECBusDevice *device = GetPrimaryDevice();
  CCECAudioSystem *audio = m_processor->GetAudioSystem();
  return device && audio && audio->IsPresent() ? audio->GetAudioStatus(device->GetLogicalAddress()) : (uint8_t)CEC_AUDIO_VOLUME_STATUS_UNKNOWN;
}

uint8_t CCECClient::SystemAudioModeStatus(void)
{
  CCECBusDevice *device = GetPrimaryDevice();
  CCECAudioSystem *audio = m_processor->GetAudioSystem();
  return device && audio && audio->IsPresent() ? audio->GetSystemAudioModeStatus(device->GetLogicalAddress()) : (uint8_t)CEC_SYSTEM_AUDIO_STATUS_UNKNOWN;
}

bool CCECClient::SendKeypress(const cec_logical_address iDestination, const cec_user_control_code key, bool bWait /* = true */)
{
  CCECBusDevice *dest = m_processor->GetDevice(iDestination);

  return dest ?
      dest->TransmitKeypress(GetPrimaryLogicalAddress(), key, bWait) :
      false;
}

bool CCECClient::SendKeyRelease(const cec_logical_address iDestination, bool bWait /* = true */)
{
  CCECBusDevice *dest = m_processor->GetDevice(iDestination);

  return dest ?
      dest->TransmitKeyRelease(GetPrimaryLogicalAddress(), bWait) :
      false;
}

bool CCECClient::GetCurrentConfiguration(libcec_configuration &configuration)
{
  CLockObject lock(m_mutex);
  snprintf(configuration.strDeviceName, LIBCEC_OSD_NAME_SIZE, "%s", m_configuration.strDeviceName);
  configuration.deviceTypes               = m_configuration.deviceTypes;
  configuration.bAutodetectAddress        = m_configuration.bAutodetectAddress;
  configuration.iPhysicalAddress          = m_configuration.iPhysicalAddress;
  configuration.baseDevice                = m_configuration.baseDevice;
  configuration.iHDMIPort                 = m_configuration.iHDMIPort;
  configuration.clientVersion             = m_configuration.clientVersion;
  configuration.serverVersion             = LIBCEC_VERSION_CURRENT;
  configuration.tvVendor                  = m_configuration.tvVendor;
  configuration.bGetSettingsFromROM       = m_configuration.bGetSettingsFromROM;
  configuration.bActivateSource           = m_configuration.bActivateSource;
  configuration.wakeDevices               = m_configuration.wakeDevices;
  configuration.powerOffDevices           = m_configuration.powerOffDevices;
  configuration.logicalAddresses          = m_configuration.logicalAddresses;
  configuration.iFirmwareVersion          = m_configuration.iFirmwareVersion;
  memmove(configuration.strDeviceLanguage,  m_configuration.strDeviceLanguage, 3);
  configuration.iFirmwareBuildDate        = m_configuration.iFirmwareBuildDate;
  configuration.bMonitorOnly              = m_configuration.bMonitorOnly;
  configuration.cecVersion                = m_configuration.cecVersion;
  configuration.adapterType               = m_configuration.adapterType;
  configuration.iDoubleTapTimeoutMs       = m_configuration.iDoubleTapTimeoutMs;
  configuration.iButtonRepeatRateMs       = m_configuration.iButtonRepeatRateMs;
  configuration.iButtonReleaseDelayMs     = m_configuration.iButtonReleaseDelayMs;
  configuration.bAutoWakeAVR              = m_configuration.bAutoWakeAVR;
#if CEC_LIB_VERSION_MAJOR >= 5
  configuration.bAutoPowerOn              = m_configuration.bAutoPowerOn;
#endif
#if CEC_LIB_VERSION_MAJOR >= 8
  configuration.bAutonomousMode           = m_configuration.bAutonomousMode;
  configuration.iButtonRepeatDelayMs      = m_configuration.iButtonRepeatDelayMs;
  configuration.iDeviceVendorId           = m_configuration.iDeviceVendorId;
#endif

  return true;
}

bool CCECClient::SetConfiguration(const libcec_configuration &configuration)
{
  bool notifyUpper = false;
  libcec_configuration defaultSettings;
  bool bIsRunning(m_processor && m_processor->CECInitialised());
  CCECBusDevice *primary = bIsRunning ? GetPrimaryDevice() : NULL;
  uint16_t iPA = primary ? primary->GetCurrentPhysicalAddress() : CEC_INVALID_PHYSICAL_ADDRESS;

  // update the callbacks
  if (!!configuration.callbacks)
    EnableCallbacks(configuration.callbackParam, configuration.callbacks);

  // update the client version
  SetClientVersion(configuration.clientVersion);

  // update the OSD name
  std::string strOSDName(configuration.strDeviceName);
  SetOSDName(strOSDName);

  // update the TV vendor override
  SetTVVendorOverride((cec_vendor_id)configuration.tvVendor);

  {
    CLockObject lock(m_mutex);
    bool activeSourceChanged = (configuration.bActivateSource != m_configuration.bActivateSource);
    bool wakeTvChanged = (configuration.wakeDevices.IsSet(CECDEVICE_TV) != m_configuration.wakeDevices.IsSet(CECDEVICE_TV));

    // just copy these
    m_configuration.bActivateSource            = configuration.bActivateSource;
    m_configuration.bGetSettingsFromROM        = configuration.bGetSettingsFromROM;
    m_configuration.wakeDevices                = configuration.wakeDevices;
    m_configuration.powerOffDevices            = configuration.powerOffDevices;
    memmove(m_configuration.strDeviceLanguage,   configuration.strDeviceLanguage, 3);
    m_configuration.bMonitorOnly               = configuration.bMonitorOnly;
    m_configuration.cecVersion                 = configuration.cecVersion;
    m_configuration.adapterType                = configuration.adapterType;
    m_configuration.iDoubleTapTimeoutMs        = configuration.iDoubleTapTimeoutMs;
    m_configuration.deviceTypes                = configuration.deviceTypes;
    m_configuration.comboKey                   = configuration.comboKey;
    m_configuration.iComboKeyTimeoutMs         = configuration.iComboKeyTimeoutMs;
    m_configuration.iButtonRepeatRateMs        = configuration.iButtonRepeatRateMs;
    m_configuration.iButtonReleaseDelayMs      = configuration.iButtonReleaseDelayMs;
    m_configuration.bAutoWakeAVR               = configuration.bAutoWakeAVR;
#if CEC_LIB_VERSION_MAJOR >= 5
    if ((configuration.bAutoPowerOn == 0) || (configuration.bAutoPowerOn == 1))
      m_configuration.bAutoPowerOn             = configuration.bAutoPowerOn;
#endif
#if CEC_LIB_VERSION_MAJOR >= 8
    if ((configuration.bAutonomousMode == 0) || (configuration.bAutonomousMode == 1))
      m_configuration.bAutonomousMode          = configuration.bAutonomousMode;
    m_configuration.iButtonRepeatDelayMs       = configuration.iButtonRepeatDelayMs;
    m_configuration.iDeviceVendorId            = configuration.iDeviceVendorId;
#endif

    if (activeSourceChanged)
    {
      // activate source will wake up the TV too
      if ((m_configuration.bActivateSource == 1) && !m_configuration.wakeDevices.IsSet(CECDEVICE_TV))
      {
        m_configuration.wakeDevices.Set(CECDEVICE_TV);
        LIB_CEC->AddLog(CEC_LOG_DEBUG, "enable tv wake up");
        notifyUpper = true;
      }
    }
    if (wakeTvChanged)
    {
      // disabling waking up the TV disables activate source too
      if (!m_configuration.wakeDevices.IsSet(CECDEVICE_TV) && (m_configuration.bActivateSource != 0))
      {
        m_configuration.bActivateSource = 0;
        LIB_CEC->AddLog(CEC_LOG_DEBUG, "disable active source");
        notifyUpper = true;
      }
    }
  }

  bool bNeedReinit(false);

  // device types
  if (m_processor->CECInitialised() &&
    SetDeviceTypes(configuration.deviceTypes))
  {
    // the device type changed. just copy the rest, and re-register
    {
      CLockObject lock(m_mutex);
      m_configuration.iPhysicalAddress = configuration.iPhysicalAddress;
      m_configuration.baseDevice       = configuration.baseDevice;
      m_configuration.iHDMIPort        = configuration.iHDMIPort;
      bNeedReinit = true;
    }
  }
  else if (
    configuration.baseDevice != CECDEVICE_UNKNOWN &&
    configuration.iHDMIPort > 0 &&
    configuration.iHDMIPort <= 0xF)
  {
    // set the configured device+port
    SetHDMIPort(configuration.baseDevice, configuration.iHDMIPort);
  }
  else
  {
    // set the physical address
    SetPhysicalAddress(configuration);
  }

  SaveConfiguration(m_configuration);

  if (!primary)
    primary = GetPrimaryDevice();

  if (bNeedReinit || !primary || primary->GetCurrentPhysicalAddress() != iPA)
  {
    // PA or device type changed
    m_processor->RegisterClient(this);
  }
  else if (primary && configuration.bActivateSource == 1 && bIsRunning && !primary->IsActiveSource())
  {
    // activate the source if we're not already the active source
    primary->ActivateSource();
  }

  if (notifyUpper)
  {
    QueueConfigurationChanged(m_configuration);
  }

  return true;
}

void CCECClient::AddCommand(const cec_command &command)
{
  // don't forward the standby opcode more than once every 10 seconds
  if (command.opcode == CEC_OPCODE_STANDBY)
  {
    CLockObject lock(m_mutex);
    if (m_iPreventForwardingPowerOffCommand != 0 &&
        m_iPreventForwardingPowerOffCommand > GetTimeMs())
      return;
    else
      m_iPreventForwardingPowerOffCommand = GetTimeMs() + CEC_FORWARD_STANDBY_MIN_INTERVAL;
  }

  if (command.destination == CECDEVICE_BROADCAST || GetLogicalAddresses().IsSet(command.destination))
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, ">> %s (%X) -> %s (%X): %s (%2X)", ToString(command.initiator), command.initiator, ToString(command.destination), command.destination, ToString(command.opcode), command.opcode);
    CallbackAddCommand(command);
  }
}

void CCECClient::AddKey(bool bSendComboKey /* = false */, bool bButtonRelease /* = false */)
{
  cec_keypress key;
  key.keycode = CEC_USER_CONTROL_CODE_UNKNOWN;

  {
    CLockObject lock(m_mutex);
    // the device sends its own release messages, so we can relax the synthesized
    // release into a stuck-key backstop and stop cutting long-presses short
    if (bButtonRelease)
      m_bSeenButtonRelease = true;

    if (m_iCurrentButton != CEC_USER_CONTROL_CODE_UNKNOWN)
    {
      unsigned int duration = (unsigned int) (GetTimeMs() - m_updateButtontime);
      key.duration = (unsigned int) (GetTimeMs() - m_initialButtontime);

      if (duration > m_configuration.iComboKeyTimeoutMs ||
          m_configuration.iComboKeyTimeoutMs == 0 ||
          m_iCurrentButton != m_configuration.comboKey ||
          bSendComboKey)
      {
        key.keycode = m_iCurrentButton;

        m_iCurrentButton = CEC_USER_CONTROL_CODE_UNKNOWN;
        m_initialButtontime = 0;
        m_updateButtontime = 0;
        m_repeatButtontime = 0;
        m_releaseButtontime = 0;
        m_pressedButtoncount = 0;
        m_releasedButtoncount = 0;
      }
    }
  }

  // we don't forward releases when supporting repeating keys
  if (bButtonRelease && m_configuration.iButtonRepeatRateMs)
    return;

  if (key.keycode != CEC_USER_CONTROL_CODE_UNKNOWN)
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "key released: %s (%1x) D:%dms", ToString(key.keycode), key.keycode, key.duration);
    QueueAddKey(key);
  }
}

void CCECClient::AddKey(const cec_keypress &key)
{
  if (key.keycode > CEC_USER_CONTROL_CODE_MAX ||
      key.keycode < CEC_USER_CONTROL_CODE_SELECT)
  {
    // send back the previous key if there is one
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "Unexpected key %s (%1x) D:%dms", ToString(key.keycode), key.keycode, key.duration);
    AddKey();
    return;
  }
  bool isrepeat = false;
  cec_keypress transmitKey(key);
  cec_user_control_code comboKey(m_configuration.comboKey);

  {
    CLockObject lock(m_mutex);
    if (m_configuration.iComboKeyTimeoutMs > 0 && m_iCurrentButton == comboKey && key.duration == 0)
    {
      // stop + ok -> exit
      if (key.keycode == CEC_USER_CONTROL_CODE_SELECT)
        transmitKey.keycode = CEC_USER_CONTROL_CODE_EXIT;
      // stop + pause -> root menu
      else if (key.keycode == CEC_USER_CONTROL_CODE_PAUSE)
        transmitKey.keycode = CEC_USER_CONTROL_CODE_ROOT_MENU;
      // stop + play -> dot (which is handled as context menu in xbmc)
      else if (key.keycode == CEC_USER_CONTROL_CODE_PLAY)
        transmitKey.keycode = CEC_USER_CONTROL_CODE_DOT;
      // default, send back the previous key
      else
      {
        LIB_CEC->AddLog(CEC_LOG_DEBUG, "Combo key %s (%1x) D%dms:", ToString(key.keycode), key.keycode, key.duration);
        AddKey(true);
      }
    }

    LIB_CEC->AddLog(CEC_LOG_DEBUG, "key pressed: %s (%1x) current(%lx) duration(%d)", ToString(transmitKey.keycode), transmitKey.keycode, m_iCurrentButton, key.duration);

    // the delay after which we synthesize a release for a held key. only relevant
    // when not auto-repeating: in repeat mode a real release clears the held state
    // right away and the duration is carried on the repeats. once the device has
    // proven it sends its own releases, stretch this to a stuck-key backstop so a
    // long-press isn't cut short by a fake release beating the real one.
    int64_t iReleaseDelayMs = m_configuration.iButtonReleaseDelayMs ? m_configuration.iButtonReleaseDelayMs : CEC_BUTTON_TIMEOUT;
    if (m_bSeenButtonRelease && !m_configuration.iButtonRepeatRateMs)
      iReleaseDelayMs = std::max(iReleaseDelayMs, (int64_t)CEC_BUTTON_RELEASE_BACKSTOP_MS);

    if (m_iCurrentButton == key.keycode)
    {
      m_updateButtontime = GetTimeMs();
      m_releaseButtontime = m_updateButtontime + iReleaseDelayMs;
      // want to have seen some updated before considering a repeat
      if (m_configuration.iButtonRepeatRateMs)
      {
        if (!m_repeatButtontime && m_pressedButtoncount > 1)
#if CEC_LIB_VERSION_MAJOR >= 8
          m_repeatButtontime = m_initialButtontime + m_configuration.iButtonRepeatDelayMs;
#else
          m_repeatButtontime = m_initialButtontime + CEC_BUTTON_REPEAT_DELAY_MS;
#endif
        isrepeat = true;
      }
      m_pressedButtoncount++;
    }
    else
    {
      if (m_iCurrentButton != transmitKey.keycode)
      {
        LIB_CEC->AddLog(CEC_LOG_DEBUG, "Changed key %s (%1x) D:%dms cur:%lx", ToString(transmitKey.keycode), transmitKey.keycode, transmitKey.duration, m_iCurrentButton);
        AddKey();
      }
      if (key.duration == 0)
      {
        m_iCurrentButton = transmitKey.keycode;
        if (m_iCurrentButton == CEC_USER_CONTROL_CODE_UNKNOWN)
        {
          m_initialButtontime = 0;
          m_updateButtontime = 0;
          m_repeatButtontime = 0;
          m_releaseButtontime = 0;
          m_pressedButtoncount = 0;
          m_releasedButtoncount = 0;
        }
        else
        {
          m_initialButtontime = GetTimeMs();
          m_updateButtontime = m_initialButtontime;
          m_repeatButtontime = 0; // set this on next update
          m_releaseButtontime = m_initialButtontime + iReleaseDelayMs;
          m_pressedButtoncount = 1;
          m_releasedButtoncount = 0;
        }
      }
    }
  }

  if (!isrepeat && (key.keycode != comboKey || key.duration > 0))
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "key pressed: %s (%1x, %d)", ToString(transmitKey.keycode), transmitKey.keycode, transmitKey.duration);
    QueueAddKey(transmitKey);
  }
}

void CCECClient::SetCurrentButton(const cec_user_control_code iButtonCode)
{
  // push a keypress to the buffer with 0 duration and another with the duration set when released
  cec_keypress key;
  key.duration = 0;
  key.keycode = iButtonCode;

  LIB_CEC->AddLog(CEC_LOG_DEBUG, "SetCurrentButton %s (%1x) D:%dms cur:%lx", ToString(key.keycode), key.keycode, key.duration);
  AddKey(key);
}

uint16_t CCECClient::CheckKeypressTimeout(void)
{
  // time when we'd like to be called again
  uint64_t timeout = CEC_PROCESSOR_SIGNAL_WAIT_TIME;
  cec_keypress key;
  key.keycode = CEC_USER_CONTROL_CODE_UNKNOWN;
  key.duration = 0;

  if (m_iCurrentButton == CEC_USER_CONTROL_CODE_UNKNOWN)
	  return (uint16_t)timeout;
  {
    CLockObject lock(m_mutex);
    uint64_t iNow = GetTimeMs();
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s T:%.3f", __FUNCTION__, iNow*1e-3);
    cec_user_control_code comboKey(m_configuration.comboKey);
    uint32_t iTimeoutMs(m_configuration.iComboKeyTimeoutMs);

    if (m_iCurrentButton == comboKey && iTimeoutMs > 0 && iNow - m_updateButtontime >= iTimeoutMs)
    {
      key.duration = (unsigned int) (iNow - m_initialButtontime);
      key.keycode = m_iCurrentButton;

      m_iCurrentButton = CEC_USER_CONTROL_CODE_UNKNOWN;
      m_initialButtontime = 0;
      m_updateButtontime = 0;
      m_repeatButtontime = 0;
      m_releaseButtontime = 0;
      m_pressedButtoncount = 0;
      m_releasedButtoncount = 0;
    }
    else if (m_iCurrentButton != comboKey && m_releaseButtontime && iNow >= (uint64_t)m_releaseButtontime)
    {
      // the release delay expired without a release command. only synthesize a
      // release for a key the device isn't repeating: a device that re-sends the
      // pressed command (m_pressedButtoncount > 1) sends its own release too, so
      // emitting one here would surface as intermediate key-up events between
      // repeats (#724). a key that was pressed once still gets the synthesized
      // release so it isn't stuck pressed (#704).
      key.duration = (unsigned int) (iNow - m_initialButtontime);
      if (m_pressedButtoncount <= 1)
        key.keycode = m_iCurrentButton;

      m_iCurrentButton = CEC_USER_CONTROL_CODE_UNKNOWN;
      m_initialButtontime = 0;
      m_updateButtontime = 0;
      m_repeatButtontime = 0;
      m_releaseButtontime = 0;
      m_pressedButtoncount = 0;
      m_releasedButtoncount = 0;
    }
    else if (m_iCurrentButton != comboKey && m_repeatButtontime && iNow >= (uint64_t)m_repeatButtontime)
    {
      key.duration = (unsigned int) (iNow - m_initialButtontime);
      key.keycode = m_iCurrentButton;
      m_repeatButtontime = iNow + m_configuration.iButtonRepeatRateMs;
      timeout = std::min((uint64_t)timeout, m_repeatButtontime - iNow);
    }
    else
    {
      if (m_iCurrentButton == comboKey && iTimeoutMs > 0)
        timeout = std::min((uint64_t)timeout, m_updateButtontime - iNow + iTimeoutMs);
      if (m_iCurrentButton != comboKey && m_releaseButtontime)
        timeout = std::min((uint64_t)timeout, m_releaseButtontime - iNow);
      if (m_iCurrentButton != comboKey && m_repeatButtontime)
        timeout = std::min((uint64_t)timeout, m_repeatButtontime - iNow);
      if (timeout > CEC_PROCESSOR_SIGNAL_WAIT_TIME)
      {
        LIB_CEC->AddLog(CEC_LOG_ERROR, "Unexpected timeout: %d (%.3f %.3f %.3f) k:%02x", timeout, iNow*1e-3, m_updateButtontime*1e-3, m_releaseButtontime*1e-3, m_iCurrentButton);
        timeout = CEC_PROCESSOR_SIGNAL_WAIT_TIME;
      }
    }
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "Key %s: %s (duration:%d) (%1x) timeout:%dms (rel:%d,rep:%d,prs:%d,rel:%d)", ToString(m_iCurrentButton), key.keycode == CEC_USER_CONTROL_CODE_UNKNOWN ? "idle" : m_repeatButtontime ? "repeated" : "released", key.duration,
        m_iCurrentButton, timeout, (int)(m_releaseButtontime ? m_releaseButtontime - iNow : 0), (int)(m_repeatButtontime ? m_repeatButtontime - iNow : 0), m_pressedButtoncount, m_releasedButtoncount);
  }

  if (key.keycode != CEC_USER_CONTROL_CODE_UNKNOWN)
    QueueAddKey(key);

  // never return 0. this is the delay after which the processor thread should
  // poll us again, and it hands it straight to CCECInputBuffer::Pop(), where 0
  // means "wait forever" -- the opposite of SyncedBuffer::Pop(), where 0 means
  // "return immediately". a 0 slipping out of the arithmetic above would park
  // the processor on the input queue until the next incoming frame instead of
  // servicing the next keypress deadline, so clamp to a 1ms poll.
  return (uint16_t)std::max<uint64_t>(timeout, 1);
}

void CCECClient::ResetKeypressState(void)
{
  CLockObject lock(m_mutex);
  m_iCurrentButton      = CEC_USER_CONTROL_CODE_UNKNOWN;
  m_initialButtontime   = 0;
  m_updateButtontime    = 0;
  m_repeatButtontime    = 0;
  m_releaseButtontime   = 0;
  m_pressedButtoncount  = 0;
  m_releasedButtoncount = 0;
  m_bSeenButtonRelease  = false;
}

bool CCECClient::EnableCallbacks(void *cbParam, ICECCallbacks *callbacks)
{
  CLockObject lock(m_cbMutex);
  m_configuration.callbackParam = cbParam;
  m_configuration.callbacks     = callbacks;
  return true;
}

bool CCECClient::PingAdapter(void)
{
  return m_processor ? m_processor->PingAdapter() : false;
}

std::string CCECClient::GetConnectionInfo(void)
{
  std::string strLog;
  strLog = StringUtils::Format("libCEC version = %s, client version = %s, firmware version = %d",
                               CCECTypeUtils::VersionToString(m_configuration.serverVersion).c_str(),
                               CCECTypeUtils::VersionToString(m_configuration.clientVersion).c_str(),
                               m_configuration.iFirmwareVersion);
  if (m_configuration.iFirmwareBuildDate != CEC_FW_BUILD_UNKNOWN)
  {
    time_t buildTime = (time_t)m_configuration.iFirmwareBuildDate;
    strLog += StringUtils::Format(", firmware build date: %s", asctime(gmtime(&buildTime)));
    strLog = strLog.substr(0, strLog.length() > 0 ? (size_t)(strLog.length() - 1) : 0); // strip \n added by asctime
    strLog.append(" +0000");
  }

  // log the addresses that are being used
  if (!m_configuration.logicalAddresses.IsEmpty())
  {
    strLog.append(", logical address(es) = ");
    CECDEVICEVEC devices;
    m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);
    for (CECDEVICEVEC::iterator it = devices.begin(); it != devices.end(); it++)
      strLog += StringUtils::Format("%s (%X) ", (*it)->GetLogicalAddressName(), (*it)->GetLogicalAddress());
  }

  if (!CLibCEC::IsValidPhysicalAddress(m_configuration.iPhysicalAddress))
    strLog += StringUtils::Format(", base device: %s (%X), HDMI port number: %d", ToString(m_configuration.baseDevice), m_configuration.baseDevice, m_configuration.iHDMIPort);
  uint16_t iPhysicalAddress = GetPrimaryDevice()->GetPhysicalAddress(GetLogicalAddresses().primary, false);
  strLog += StringUtils::Format(", physical address: %x.%x.%x.%x", (iPhysicalAddress >> 12) & 0xF, (iPhysicalAddress >> 8) & 0xF, (iPhysicalAddress >> 4) & 0xF, iPhysicalAddress & 0xF);

  strLog += StringUtils::Format(", %s", LIB_CEC->GetLibInfo());

  std::string strReturn(strLog.c_str());
  return strReturn;
}

void CCECClient::SetTVVendorOverride(const cec_vendor_id id)
{
  {
    CLockObject lock(m_mutex);
    if (m_configuration.tvVendor == id)
    {
      return;
    }
    m_configuration.tvVendor = id;
  }

  if (id != CEC_VENDOR_UNKNOWN)
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - vendor id '%s'", __FUNCTION__, ToString(id));

    CCECBusDevice *tv = m_processor ? m_processor->GetTV() : NULL;
    if (tv)
      tv->SetVendorId((uint32_t)id);
  }

  SaveConfiguration(m_configuration);
}

cec_vendor_id CCECClient::GetTVVendorOverride(void)
{
  CLockObject lock(m_mutex);
  return (cec_vendor_id)m_configuration.tvVendor;
}

void CCECClient::SetOSDName(const std::string &strDeviceName)
{
  {
    CLockObject lock(m_mutex);
    char buf[LIBCEC_OSD_NAME_SIZE + 1] = { 0 };
    strncpy(buf, strDeviceName.c_str(), LIBCEC_OSD_NAME_SIZE);
    if (!strncmp(m_configuration.strDeviceName, buf, LIBCEC_OSD_NAME_SIZE))
      return;
    strncpy(m_configuration.strDeviceName, buf, LIBCEC_OSD_NAME_SIZE - 1);
    m_configuration.strDeviceName[LIBCEC_OSD_NAME_SIZE - 1] = 0;
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - using OSD name '%s'", __FUNCTION__, buf);
  }


  CCECBusDevice *primary = GetPrimaryDevice();
  if (primary && primary->GetCurrentOSDName() != strDeviceName.c_str())
  {
    primary->SetOSDName(strDeviceName);
    if (m_processor && m_processor->CECInitialised())
      primary->TransmitOSDName(CECDEVICE_TV, false);
  }

  SaveConfiguration(m_configuration);
}

std::string CCECClient::GetOSDName(void)
{
  CLockObject lock(m_mutex);
  std::string strOSDName(m_configuration.strDeviceName);
  return strOSDName;
}

void CCECClient::SetWakeDevices(const cec_logical_addresses &addresses)
{
  {
    CLockObject lock(m_mutex);
    m_configuration.wakeDevices = addresses;
  }
  SaveConfiguration(m_configuration);
}

cec_logical_addresses CCECClient::GetWakeDevices(void)
{
  CLockObject lock(m_mutex);
  return m_configuration.wakeDevices;
}

bool CCECClient::AutodetectPhysicalAddress(void)
{
  uint16_t iPhysicalAddress = !!m_processor ?
    m_processor->GetDetectedPhysicalAddress() :
    CEC_INVALID_PHYSICAL_ADDRESS;

  CLockObject lock(m_mutex);
  if (CLibCEC::IsValidPhysicalAddress(iPhysicalAddress) &&
    (iPhysicalAddress != CEC_PHYSICAL_ADDRESS_TV))
  {
    m_configuration.iPhysicalAddress   = iPhysicalAddress;
    m_configuration.iHDMIPort          = CEC_HDMI_PORTNUMBER_NONE;
    m_configuration.baseDevice         = CECDEVICE_UNKNOWN;
    m_configuration.bAutodetectAddress = 1;
    return true;
  }

  m_configuration.bAutodetectAddress = 0;
  return false;
}

void CCECClient::SetClientVersion(uint32_t version)
{
  bool changed;
  {
    CLockObject lock(m_mutex);
    changed = (m_configuration.clientVersion != version);
    m_configuration.clientVersion = version;
  }
  if (changed)
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - using client version '%s'", __FUNCTION__, CCECTypeUtils::VersionToString(version).c_str());
  }
}

uint32_t CCECClient::GetClientVersion(void)
{
  CLockObject lock(m_mutex);
  return m_configuration.clientVersion;
}

bool CCECClient::SetDeviceTypes(const cec_device_type_list &deviceTypes)
{
  bool bNeedReinit(false);

  {
    CLockObject lock(m_mutex);
    bNeedReinit = m_processor && m_processor->CECInitialised() &&
        (m_configuration.deviceTypes != deviceTypes);
    m_configuration.deviceTypes = deviceTypes;
  }

  
  if (bNeedReinit)
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - using primary device type '%s'", __FUNCTION__, ToString(deviceTypes[0]));
    SaveConfiguration(m_configuration);
  }

  return bNeedReinit;
}

cec_device_type_list CCECClient::GetDeviceTypes(void)
{
  cec_device_type_list retVal;
  CLockObject lock(m_mutex);
  retVal = m_configuration.deviceTypes;
  return retVal;
}

bool CCECClient::SetDevicePhysicalAddress(const uint16_t iPhysicalAddress)
{
  if (!CLibCEC::IsValidPhysicalAddress(iPhysicalAddress))
  {
    LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - not setting invalid physical address %04x", __FUNCTION__, iPhysicalAddress);
    return false;
  }

  // reconfigure all devices
  cec_logical_address reactivateSource(CECDEVICE_UNKNOWN);
  CECDEVICEVEC devices;
  m_processor->GetDevices()->GetByLogicalAddresses(devices, m_configuration.logicalAddresses);
  for (CECDEVICEVEC::iterator it = devices.begin(); it != devices.end(); it++)
  {
    // if this device was the active source, reactivate it afterwards
    if ((*it)->IsActiveSource())
      reactivateSource = (*it)->GetLogicalAddress();

    // mark the device as inactive source
    if (IsInitialised())
      (*it)->MarkAsInactiveSource();

    // set the new physical address
    (*it)->SetPhysicalAddress(iPhysicalAddress);

    // and transmit it
    if (IsInitialised())
      (*it)->TransmitPhysicalAddress(false);
  }

  // reactivate the previous active source
  if (reactivateSource != CECDEVICE_UNKNOWN &&
      m_processor->CECInitialised() &&
      IsInitialised())
  {
    CCECBusDevice *device = m_processor->GetDevice(reactivateSource);
    if (device)
      device->ActivateSource();
  }

  SaveConfiguration(m_configuration);

  return true;
}

bool CCECClient::SwitchMonitoring(bool bEnable)
{
  LIB_CEC->AddLog(CEC_LOG_NOTICE, "== %s monitoring mode ==", bEnable ? "enabling" : "disabling");

  if (m_processor)
  {
    m_processor->SwitchMonitoring(bEnable);
    m_configuration.bMonitorOnly = bEnable;
    return bEnable ? true: m_processor->RegisterClient(this);
  }

  return false;
}

bool CCECClient::PollDevice(const cec_logical_address iAddress)
{
  // try to find the primary device
  CCECBusDevice *primary = GetPrimaryDevice();
  // poll the destination, with the primary as source
  if (primary)
    return primary->TransmitPoll(iAddress, true);

  return m_processor ? m_processor->PollDevice(iAddress) : false;
}

cec_logical_addresses CCECClient::GetActiveDevices(void)
{
  CECDEVICEVEC activeDevices;
  if (m_processor)
    m_processor->GetDevices()->GetActive(activeDevices);
  return CCECDeviceMap::ToLogicalAddresses(activeDevices);
}

bool CCECClient::IsActiveDevice(const cec_logical_address iAddress)
{
  cec_logical_addresses activeDevices = GetActiveDevices();
  return activeDevices.IsSet(iAddress);
}

bool CCECClient::IsActiveDeviceType(const cec_device_type type)
{
  return m_processor->GetDevices()->IsActiveType(type, false);
}

cec_logical_address CCECClient::GetActiveSource(void)
{
  return m_processor ? m_processor->GetActiveSource() : CECDEVICE_UNKNOWN;
}

bool CCECClient::IsActiveSource(const cec_logical_address iAddress)
{
  return m_processor ? m_processor->IsActiveSource(iAddress) : false;
}

bool CCECClient::SetStreamPath(const cec_logical_address iAddress)
{
  uint16_t iPhysicalAddress = GetDevicePhysicalAddress(iAddress);
  if (iPhysicalAddress != CEC_INVALID_PHYSICAL_ADDRESS)
    return SetStreamPath(iPhysicalAddress);
  return false;
}

bool CCECClient::SetStreamPath(const uint16_t iPhysicalAddress)
{
  bool bReturn(false);

  CCECBusDevice *device = GetDeviceByType(CEC_DEVICE_TYPE_TV);
  if (device)
  {
    device->SetStreamPath(iPhysicalAddress);
    bReturn = device->GetHandler()->TransmitSetStreamPath(iPhysicalAddress, false);
    device->MarkHandlerReady();
  }
  else
  {
    LIB_CEC->AddLog(CEC_LOG_ERROR, "only the TV is allowed to send CEC_OPCODE_SET_STREAM_PATH");
  }

  return bReturn;
}

cec_logical_addresses CCECClient::GetLogicalAddresses(void)
{
  cec_logical_addresses addresses;
  CLockObject lock(m_mutex);
  addresses = m_configuration.logicalAddresses;
  return addresses;
}

bool CCECClient::CanSaveConfiguration(void)
{
  return m_processor ? m_processor->CanSaveConfiguration() : false;
}

bool CCECClient::SaveConfiguration(const libcec_configuration &configuration)
{
  return m_processor && IsRegistered() ?
      m_processor->SaveConfiguration(configuration) :
      false;
}

void CCECClient::RescanActiveDevices(void)
{
  if (m_processor)
    m_processor->RescanActiveDevices();
}

bool CCECClient::IsLibCECActiveSource(void)
{
  bool bReturn(false);
  if (m_processor)
  {
    cec_logical_address activeSource = m_processor->GetActiveSource();
    CCECBusDevice *device = m_processor->GetDevice(activeSource);
    if (device)
      bReturn = device->IsHandledByLibCEC() && !device->GetHandler()->ActiveSourcePending();
  }
  return bReturn;
}

void CCECClient::SourceActivated(const cec_logical_address logicalAddress)
{
  LIB_CEC->AddLog(CEC_LOG_NOTICE, ">> source activated: %s (%x)", ToString(logicalAddress), logicalAddress);
  QueueSourceActivated(true, logicalAddress);
}

void CCECClient::SourceDeactivated(const cec_logical_address logicalAddress)
{
  LIB_CEC->AddLog(CEC_LOG_NOTICE, ">> source deactivated: %s (%x)", ToString(logicalAddress), logicalAddress);
  QueueSourceActivated(false, logicalAddress);
}

void CCECClient::CallbackAddCommand(const cec_command &command)
{
  CLockObject lock(m_cbMutex);
  if (m_configuration.callbacks && !!m_configuration.callbacks->commandReceived)
    m_configuration.callbacks->commandReceived(m_configuration.callbackParam, &command);
}

uint32_t CCECClient::DoubleTapTimeoutMS(void)
{
  CLockObject lock(m_mutex);
  return m_configuration.iDoubleTapTimeoutMs;
}

void CCECClient::QueueAddCommand(const cec_command& command)
{
  m_callbackCalls.Push(new CCallbackWrap(command));
}

void CCECClient::QueueAddKey(const cec_keypress& key)
{
  m_callbackCalls.Push(new CCallbackWrap(key));
}

void CCECClient::QueueAddLog(const cec_log_message_cpp& message)
{
  m_callbackCalls.Push(new CCallbackWrap(message));
}

void CCECClient::QueueAlert(const libcec_alert type, const libcec_parameter& param)
{
  m_callbackCalls.Push(new CCallbackWrap(type, param));
}

void CCECClient::QueueConfigurationChanged(const libcec_configuration& config)
{
  m_callbackCalls.Push(new CCallbackWrap(config));
}

int CCECClient::QueueMenuStateChanged(const cec_menu_state newState)
{
  CCallbackWrap *wrapState = new CCallbackWrap(newState);
  m_callbackCalls.Push(wrapState);
  int result(wrapState->Result(1000));

  if (wrapState->m_keepResult)
    delete wrapState;
  return result;
}

void CCECClient::QueueSourceActivated(bool bActivated, const cec_logical_address logicalAddress)
{
  m_callbackCalls.Push(new CCallbackWrap(bActivated, logicalAddress));
}

int CCECClient::QueueCommandHandler(const cec_command& command)
{
  CCallbackWrap *wrapState = new CCallbackWrap(command, true);
  m_callbackCalls.Push(wrapState);
  int result(wrapState->Result(1000));

  if (wrapState->m_keepResult)
    delete wrapState;
  return result;
}

void* CCECClient::Process(void)
{
  CCallbackWrap* cb(NULL);
  while (!IsStopped())
  {
    if (m_callbackCalls.Pop(cb, 500))
    {
      bool keepResult = cb->m_keepResult;
      try
      {
        switch (cb->m_type)
        {
        case CCallbackWrap::CEC_CB_LOG_MESSAGE:
          CallbackAddLog(cb->m_message);
          break;
        case CCallbackWrap::CEC_CB_KEY_PRESS:
          CallbackAddKey(cb->m_key);
          break;
        case CCallbackWrap::CEC_CB_COMMAND:
          AddCommand(cb->m_command);
          break;
        case CCallbackWrap::CEC_CB_ALERT:
          CallbackAlert(cb->m_alertType, cb->m_alertParam);
          break;
        case CCallbackWrap::CEC_CB_CONFIGURATION:
          CallbackConfigurationChanged(cb->m_config);
          break;
        case CCallbackWrap::CEC_CB_MENU_STATE:
          keepResult = cb->Report(CallbackMenuStateChanged(cb->m_menuState));
          break;
        case CCallbackWrap::CEC_CB_SOURCE_ACTIVATED:
          CallbackSourceActivated(cb->m_bActivated, cb->m_logicalAddress);
          break;
        case CCallbackWrap::CEC_CB_COMMAND_HANDLER:
          keepResult = cb->Report(CallbackCommandHandler(cb->m_command));
          if (!keepResult)
            LIB_CEC->AddLog(CEC_LOG_WARNING, "Command callback timeout occured !");
	  break;
        default:
          break;
        }

        if (!keepResult)
          delete cb;
      } catch (...)
      {
         // don't log a warning but let the app deal with this
         delete cb;
      }
    }
  }
  return NULL;
}

void CCECClient::CallbackAddKey(const cec_keypress &key)
{
  CLockObject lock(m_cbMutex);
  if (!!m_configuration.callbacks &&
      !!m_configuration.callbacks->keyPress)
  {
    int64_t now = GetTimeMs();
    // drop a repeated press of the same key within the double tap timeout, so a
    // single physical press reported twice by the device isn't delivered twice
    if (key.duration == 0 && m_configuration.iDoubleTapTimeoutMs &&
        m_lastKeypress.keycode == key.keycode &&
        now - m_iLastKeypressTime < DoubleTapTimeoutMS())
      return;
    // a device that double-reports a press also double-reports its release, so
    // drop the extra release too. only the second release in a burst is dropped:
    // a forwarded press resets m_iLastKeyreleaseTime, so the first release after
    // any press always gets through and no press is left without a release.
    if (key.duration != 0 && m_configuration.iDoubleTapTimeoutMs &&
        m_lastKeypress.keycode == key.keycode &&
        m_iLastKeyreleaseTime != 0 &&
        now - m_iLastKeyreleaseTime < DoubleTapTimeoutMS())
      return;
    if (key.duration == 0)
    {
      m_iLastKeypressTime = now;
      m_iLastKeyreleaseTime = 0;
    }
    else
      m_iLastKeyreleaseTime = now;
    m_lastKeypress = key;
    m_configuration.callbacks->keyPress(m_configuration.callbackParam, &key);
  }
}

void CCECClient::CallbackAddLog(const cec_log_message_cpp &message)
{
  CLockObject lock(m_cbMutex);
  if (!!m_configuration.callbacks &&
      !!m_configuration.callbacks->logMessage)
  {
    cec_log_message toClient;
    toClient.level   = message.level;
    toClient.message = message.message.c_str();
    toClient.time    = message.time;
    m_configuration.callbacks->logMessage(m_configuration.callbackParam, &toClient);
  }
}

void CCECClient::CallbackConfigurationChanged(const libcec_configuration &config)
{
  CLockObject lock(m_cbMutex);
  if (!!m_configuration.callbacks &&
      !!m_configuration.callbacks->configurationChanged &&
      m_processor->CECInitialised())
  {
    m_configuration.callbacks->configurationChanged(m_configuration.callbackParam, &config);
  }
}

void CCECClient::CallbackSourceActivated(bool bActivated, const cec_logical_address logicalAddress)
{
  CLockObject lock(m_cbMutex);
  if (!!m_configuration.callbacks &&
      !!m_configuration.callbacks->sourceActivated)
  {
    m_configuration.callbacks->sourceActivated(m_configuration.callbackParam, logicalAddress, bActivated ? 1 : 0);
  }
}

void CCECClient::CallbackAlert(const libcec_alert type, const libcec_parameter &param)
{
  CLockObject lock(m_cbMutex);
  if (!!m_configuration.callbacks &&
      !!m_configuration.callbacks->alert)
  {
    m_configuration.callbacks->alert(m_configuration.callbackParam, type, param);
  }
}

int CCECClient::CallbackMenuStateChanged(const cec_menu_state newState)
{
  LIB_CEC->AddLog(CEC_LOG_DEBUG, ">> %s: %s", ToString(CEC_OPCODE_MENU_REQUEST), ToString(newState));

  CLockObject lock(m_cbMutex);
  if (!!m_configuration.callbacks &&
     !!m_configuration.callbacks->menuStateChanged)
  {
    return m_configuration.callbacks->menuStateChanged(m_configuration.callbackParam, newState);
  }
  return 0;
}

int CCECClient::CallbackCommandHandler(const cec_command &command)
{
  CLockObject lock(m_cbMutex);
  if (!!m_configuration.callbacks &&
     !!m_configuration.callbacks->commandHandler)
  {
    return m_configuration.callbacks->commandHandler(m_configuration.callbackParam, &command);
  }
  return 0;
}

bool CCECClient::AudioEnable(bool enable)
{
  CCECBusDevice* device = GetPrimaryDevice();
  CCECAudioSystem* audio = m_processor->GetAudioSystem();

  return !!audio ?
      audio->AudioEnable(device, enable) :
      false;
}

#if CEC_LIB_VERSION_MAJOR >= 5
bool CCECClient::GetStats(struct cec_adapter_stats* stats)
{
  return !!m_processor ?
      m_processor->GetStats(stats) :
      false;
}
#endif
