/*
  device.cpp

  Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
*/

#include "config.h"
#include "device.h"

namespace RDM {

/**
 * @brief Device::Device
 * @param id
 * @param parent
 */
Device::Device(UID id, Device* parent)
  : DMX::Device()
  , deviceModelID(0)
  , deviceModelDescription("Basic RDM Device")
  , deviceProductCategory(PRODUCT_CATEGORY_NOT_DECLARED)
  , id_(id)
  , parent_(parent)
  , status_reporting_threshold_(STATUS_ADVISORY)
{
  id_.manufacturer = MY_ESTA_MANUFACTURER_ID;

  queued_statuses_.emplace(STATUS_ADVISORY, std::queue<StatusPtr>());
  queued_statuses_.emplace(STATUS_WARNING,  std::queue<StatusPtr>());
  queued_statuses_.emplace(STATUS_ERROR,    std::queue<StatusPtr>());

  /// 9.2.3 Required Sub-Device Messages
  /// Devices supporting the use of sub-devices shall support the
  /// SUPPORTED_PARAMETERS message in order for the controller to determine
  /// which additional messages are supported by the sub-devices.
  parameters_.try_emplace(SUPPORTED_PARAMETERS, new Parameter());
  parameters_.at(SUPPORTED_PARAMETERS)->getAction(std::bind(
                                          &Device::actionGetSupportedParameters,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.3.4 Clear Status ID (CLEAR_STATUS_ID)
  /// This parameter is used to clear the status message queue.
  parameters_.try_emplace(CLEAR_STATUS_ID, new Parameter());
  parameters_.at(SUPPORTED_PARAMETERS)->setAction(std::bind(
                                          &Device::actionSetClearStatusId,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  ///10.3.5 Get/Set Sub-Device Status Reporting Threshold
  /// (SUB_DEVICE_STATUS_REPORT_THRESHOLD)
  /// This parameter is used to set the verbosity of Sub-Device reporting using
  /// the Status Type codes as enumerated in Table A-4 .
  parameters_.try_emplace(SUB_DEVICE_STATUS_REPORT_THRESHOLD, new Parameter());
  parameters_.at(SUB_DEVICE_STATUS_REPORT_THRESHOLD)->getAction(std::bind(
                                          &Device::actionGetSubdeviceThreshold,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  parameters_.at(SUB_DEVICE_STATUS_REPORT_THRESHOLD)->setAction(std::bind(
                                          &Device::actionSetSubdeviceThreshold,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.5.1 Get Device Info (DEVICE_INFO)
  /// This parameter is used to retrieve a variety of information about the
  /// device that is normally required by a controller.
  parameters_.try_emplace(DEVICE_INFO, new Parameter());
  parameters_.at(DEVICE_INFO)->getAction(std::bind(
                                          &Device::actionGetDeviceInfo,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.5.2 Get Product Detail ID List (PRODUCT_DETAIL_ID_LIST)
  /// This parameter shall be used for requesting technology details for a
  /// device.
  parameters_.try_emplace(PRODUCT_DETAIL_ID_LIST, new Parameter());
  parameters_.at(PRODUCT_DETAIL_ID_LIST)->getAction(std::bind(
                                          &Device::actionGetProductDetailIdList,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.5.3 Get Device Model Description (DEVICE_MODEL_DESCRIPTION)
  /// This parameter provides a text description of up to 32 characters for the
  /// device model type.
  parameters_.try_emplace(DEVICE_MODEL_DESCRIPTION, new Parameter());
  parameters_.at(DEVICE_MODEL_DESCRIPTION)->getAction(std::bind(
                                          &Device::actionGetDevModelDescription,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.5.4 Get Manufacturer Label (MANUFACTURER_LABEL)
  /// This parameter provides an ASCII text response with the Manufacturer name
  /// for the device of up to 32 characters.
  parameters_.try_emplace(MANUFACTURER_LABEL, new Parameter());
  parameters_.at(MANUFACTURER_LABEL)->getAction(std::bind(
                                          &Device::actionGetManufacturerLabel,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.5.7 Get Language Capabilities (LANGUAGE_CAPABILITIES)
  /// This parameter is used to identify languages that the device supports for
  /// using the LANGUAGE parameter.
  parameters_.try_emplace(LANGUAGE_CAPABILITIES, new Parameter());
  parameters_.at(LANGUAGE_CAPABILITIES)->getAction(std::bind(
                                          &Device::actionGetLanguage,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.5.8 Get/Set Language (LANGUAGE)
  /// This parameter is used to change the language of the messages from
  /// the device.
  parameters_.try_emplace(LANGUAGE, new Parameter());
  parameters_.at(LANGUAGE)->getAction(std::bind(
                                          &Device::actionGetLanguage,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  parameters_.at(LANGUAGE)->setAction(std::bind(
                                          &Device::actionSetLanguage,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.5.9 Get Software Version Label (SOFTWARE_VERSION_LABEL)
  /// This parameter is used to get a descriptive ASCII text label for the
  /// device’s operating software version.
  parameters_.try_emplace(SOFTWARE_VERSION_LABEL, new Parameter());
  parameters_.at(SOFTWARE_VERSION_LABEL)->getAction(std::bind(
                                          &Device::actionGetSoftwareVersionLabel,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  ///10.6.1 Get/Set DMX512 Personality (DMX_PERSONALITY)
  /// This parameter is used to set the responder’s DMX512 Personality.
  parameters_.try_emplace(DMX_PERSONALITY, new Parameter());
  parameters_.at(DMX_PERSONALITY)->getAction(std::bind(
                                          &Device::actionGetDmxPersonality,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  parameters_.at(DMX_PERSONALITY)->setAction(std::bind(
                                          &Device::actionSetDmxPersonality,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.6.2 Get DMX512 Personality Description (DMX_PERSONALITY_DESCRIPTION)
  /// This parameter is used to get a descriptive ASCII text label for a given
  /// DMX512 Personality.
  parameters_.try_emplace(DMX_PERSONALITY_DESCRIPTION, new Parameter());
  parameters_.at(DMX_PERSONALITY_DESCRIPTION)->getAction(std::bind(
                                          &Device::actionGetDmxPersonalityDesc,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.6.3 Get/Set DMX512 Starting Address (DMX_START_ADDRESS)
  /// This parameter is used to set or get the DMX512 start address.
  parameters_.try_emplace(DMX_START_ADDRESS, new Parameter());
  parameters_.at(DMX_START_ADDRESS)->getAction(std::bind(
                                          &Device::actionGetDmxStartAddress,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  parameters_.at(DMX_START_ADDRESS)->setAction(std::bind(
                                          &Device::actionSetDmxStartAddress,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.7.1 Get Sensor Definition (SENSOR_DEFINITION)
  /// This parameter is used to retrieve the definition of a specific sensor.
  parameters_.try_emplace(SENSOR_DEFINITION, new Parameter());
  parameters_.at(SENSOR_DEFINITION)->getAction(std::bind(
                                          &Device::actionSensorDispatch,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.7.2 Get/Set Sensor (SENSOR_VALUE)
  /// This parameter shall be used to retrieve or reset sensor data.
  parameters_.try_emplace(SENSOR_VALUE, new Parameter());
  parameters_.at(SENSOR_VALUE)->getAction(std::bind(
                                          &Device::actionSensorDispatch,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  parameters_.at(SENSOR_VALUE)->setAction(std::bind(
                                          &Device::actionSensorDispatch,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.7.3 Record Sensors (RECORD_SENSORS)
  /// This parameter instructs devices such as dimming racks that monitor load
  /// changes to store the current value for monitoring sensor changes.
  parameters_.try_emplace(RECORD_SENSORS, new Parameter());
  parameters_.at(RECORD_SENSORS)->setAction(std::bind(
                                          &Device::actionSensorDispatch,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.11.1 Get/Set Identify Device (IDENTIFY_DEVICE)
  /// This parameter is used for the user to physically identify the device
  /// represented by the UID.
  parameters_.try_emplace(IDENTIFY_DEVICE, new Parameter());
  parameters_.at(IDENTIFY_DEVICE)->getAction(std::bind(
                                          &Device::actionGetIdentifyDevice,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  parameters_.at(IDENTIFY_DEVICE)->setAction(std::bind(
                                          &Device::actionSetIdentifyDevice,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
  /// 10.11.2 Reset Device (RESET_DEVICE)
  /// This parameter is used to instruct the responder to reset itself.
  parameters_.try_emplace(RESET_DEVICE, new Parameter());
  parameters_.at(RESET_DEVICE)->setAction(std::bind(
                                          &Device::actionSetResetDevice,
                                          this, std::placeholders::_1,
                                                std::placeholders::_2));
}


/**
 * @brief Device::~Device
 */
Device::~Device()
{
  for( auto& [_, device] : sub_devices_)
    delete device;
  for (auto& [_, parameter] : parameters_)
    delete parameter;
  for (auto sensor : sensors_)
    delete sensor;
}


/**
 * @brief Device::addSubDevice
 * @param number
 * @param dev
 *
 * Root devices (ie. Respoders) shall override with a meaningful implimentation
 */
void Device::addSubDevice(uint16_t number, Device *dev)
{
  if (parent_)
    return;

  if (sub_devices_.count(number))
    delete sub_devices_.at(number);
  sub_devices_[number] = dev;
}


/**
 * @brief Device::subDevice
 * @param number
 * @return
 */
Device* Device::subDevice(uint16_t number)
{
  if (parent_)
    return nullptr;

  if (sub_devices_.count(number))
    return sub_devices_.at(number);
  return nullptr;
}


/**
 * @brief Device::subDeviceCount
 * @return
 */
uint16_t Device::subDeviceCount() const
{
  if (parent_)
    return parent_->subDeviceCount();
  return sub_devices_.size();
}


/**
 * @brief Device::addProductDetailId
 * @param id
 */
void Device::addProductDetailId(uint16_t id)
{
  product_detail_list_.push_back(id);
  while (product_detail_list_.size() > 6)
      product_detail_list_.pop_front();
}


/**
 * @brief Device::get
 * @param message
 * @param response
 */
void Device::get(const MsgPtr message, MsgPtr response)
{
  if (!actionPrep_(message, response))
    return;
  parameters_.at(message->parameterId)->get(message, response);
}


/**
 * @brief Device::set
 * @param message
 * @param response
 */
void Device::set(const MsgPtr message, MsgPtr response)
{
  if (!actionPrep_(message, response))
    return;
  parameters_.at(message->parameterId)->set(message, response);
}


/**
 * @brief Device::identify
 * @param state
 */
void Device::identify(bool state)
{
  identifying_ = state;
}


/**
 * @brief Device::reset
 * @param hard
 */
void Device::reset(bool hard)
{
  (void)hard;
}


/**
 * @brief Device::enqueueMessage
 * @param message
 * @param urgent
 */
void Device::enqueueMessage(MsgPtr message, bool urgent)
{
  /// only root devices have a message queue
  if (parent_)
    return;

  message->destination = controller_uid_;

  if (urgent)
    queued_messages_.push_front(message);
  else
    queued_messages_.push_back(message);
}


/**
 * @brief Device::enqueueStatus
 * @param status
 */
void Device::enqueueStatus(StatusPtr status)
{
  switch (status->status_type) {
    case STATUS_ERROR:
      if (status_reporting_threshold_ != STATUS_ADVISORY &&
          status_reporting_threshold_ != STATUS_WARNING)
        queued_statuses_.at(STATUS_ERROR).push(status);
      break;
    case STATUS_ERROR_CLEARED:
      if (status_reporting_threshold_ != STATUS_ADVISORY &&
          status_reporting_threshold_ != STATUS_WARNING)
        queued_statuses_.at(STATUS_ERROR).push(status);
      break;
    case STATUS_WARNING:
      if (status_reporting_threshold_ != STATUS_ADVISORY)
        queued_statuses_.at(STATUS_WARNING).push(status);
      break;
    case STATUS_WARNING_CLEARED:
      if (status_reporting_threshold_ != STATUS_ADVISORY)
        queued_statuses_.at(STATUS_WARNING).push(status);
      break;
    case STATUS_ADVISORY:
      queued_statuses_.at(STATUS_ADVISORY).push(status);
      break;
    case STATUS_ADVISORY_CLEARED:
      queued_statuses_.at(STATUS_ADVISORY).push(status);
      break;
    default:
      break;
    }
}


/**
 * @brief Device::actionPrep_
 * @param message
 * @param response
 * @return
 */
bool Device::actionPrep_(const MsgPtr message, MsgPtr response)
{
  if (!parameters_.count(message->parameterId))
    {
      response->nak(NR_UNKNOWN_PID);
      return false;
    }

  if (message->parameterId == last_rx_pid_)
    ++ack_overflow_page;
  else {
    ack_overflow_page = 0;
    last_rx_pid_ = message->parameterId;
    }
  return true;
}


/**
 * @brief Device::actionGetSupportedParameters
 * @param message
 * @param response
 */
void Device::actionGetSupportedParameters(const MsgPtr message, MsgPtr response)
{  
  if (!message->requiredLength(0, response))
    return;

  uint count = parameters_.size();
  uint length = count * sizeof(PID);
  uint lastPage = length / 0xfe;
  uint first = ack_overflow_page * ( 0xfe / sizeof(PID) );
  if (first >= count) {
    ack_overflow_page = 0;
    first = 0;
    }

  if (length > 0xfe && ack_overflow_page != lastPage)
    response->responseType = RESPONSE_TYPE_ACK_OVERFLOW;
  else
    response->responseType = RESPONSE_TYPE_ACK;

  auto pid = parameters_.begin();
  if (first != 0)
    std::advance(pid, first);
  while (pid != parameters_.end() && response->length() < 0xfe)
    {
      response->appendData(pid->first);
      pid++;
    }
}


/**
 * @brief Device::actionGetDeviceInfo
 * @param message
 * @param response
 */
void Device::actionGetDeviceInfo(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  response->appendData(RDM_PROTOCOL_VERSION);
  response->appendData(deviceModelID);
  response->appendData(deviceProductCategory);
  response->appendData(LIB_VERSION);
  response->appendData(DMX::Device::footprint());
  response->appendData(DMX::Device::personality());
  response->appendData(DMX::Device::personalityCount());
  response->appendData(DMX::Device::address());
  response->appendData(subDeviceCount());
  response->appendData<uint8_t>(sensors_.size());
}


/**
 * @brief Device::actionSetClearStatusId
 * @param message
 * @param response
 */
void Device::actionSetClearStatusId(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  for (auto& [_, queue] : queued_statuses_)
    while (!queue.empty())
      queue.pop();

  response->responseType = RESPONSE_TYPE_ACK;
}


/**
 * @brief Device::actionGetSubdeviceThreshold
 * @param message
 * @param response
 */
void Device::actionGetSubdeviceThreshold(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  response->appendData(status_reporting_threshold_);
}


/**
 * @brief Device::actionSetSubdeviceThreshold
 * @param message
 * @param response
 */
void Device::actionSetSubdeviceThreshold(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(1, response))
    return;

  uint8_t threshold = message->data()->front();

  switch (threshold) {
    case STATUS_ERROR:
      status_reporting_threshold_ = threshold;
      while (!queued_statuses_.at(STATUS_WARNING).empty())
        queued_statuses_.at(STATUS_WARNING).pop();
      while (!queued_statuses_.at(STATUS_ADVISORY).empty())
        queued_statuses_.at(STATUS_ADVISORY).pop();
      break;
    case STATUS_WARNING:
      status_reporting_threshold_ = threshold;
      while (!queued_statuses_.at(STATUS_ADVISORY).empty())
        queued_statuses_.at(STATUS_ADVISORY).pop();
      break;
    case STATUS_ADVISORY:
      status_reporting_threshold_ = threshold;
      break;
    default:
      response->nak(NR_DATA_OUT_OF_RANGE);
      return;
    }

  response->responseType = RESPONSE_TYPE_ACK;
}


/**
 * @brief Device::actionGetProductDetailIdList
 * @param message
 * @param response
 */
void Device::actionGetProductDetailIdList(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  if (product_detail_list_.empty())
    {
      response->appendData(PRODUCT_DETAIL_NOT_DECLARED);
      return;
    }
  for ( uint16_t detail : product_detail_list_ )
    response->appendData(detail);
}


/**
 * @brief Device::actionGetDevModelDescription
 * @param message
 * @param response
 */
void Device::actionGetDevModelDescription(const MsgPtr message, MsgPtr response)
{  
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  for (size_t i = 0; i < deviceModelDescription.size(); i++)
    {
      if (i > 32)
        break;
      response->appendData(deviceModelDescription.at(i));
    }
}


/**
 * @brief Device::actionGetManufacturerLabel
 * @param message
 * @param response
 */
void Device::actionGetManufacturerLabel(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  std::string label = std::string(MY_ESTA_MANUFACTURER_LABEL);
  for (size_t i = 0; i < label.size(); i++)
    {
      if (i > 32)
        break;
      response->appendData(label.at(i));
    }
}


/**
 * @brief Device::actionGetLanguage
 * @param message
 * @param response
 */
void Device::actionGetLanguage(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  std::string label = std::string("en");
  for (char& c : label)
    response->appendData(c);
}


/**
 * @brief Device::actionSetLanguage
 * @param message
 * @param response
 */
void Device::actionSetLanguage(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(2, response))
    return;

  std::string s;
  for ( char c : *message->data())
    s += c;

  if (s != "en")
    {
      response->nak(NR_DATA_OUT_OF_RANGE);
      return;
    }

  response->responseType = RESPONSE_TYPE_ACK;
}


/**
 * @brief Device::actionGetSoftwareVersionLabel
 * @param message
 * @param response
 */
void Device::actionGetSoftwareVersionLabel(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  std::string label = std::string(LIB_VERSION_LABEL);
  for (size_t i = 0; i < label.size(); i++)
    {
      if (i > 32)
        break;
      response->appendData(label.at(i));
    }
}


/**
 * @brief Device::actionGetDmxPersonality
 * @param message
 * @param response
 */
void Device::actionGetDmxPersonality(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  response->appendData(DMX::Device::personality());
  response->appendData(DMX::Device::personalityCount());
}


/**
 * @brief Device::actionSetDmxPersonality
 * @param message
 * @param response
 */
void Device::actionSetDmxPersonality(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(1, response))
    return;

  uint8_t mode = message->data()->front();

  if ( mode == 0 || mode > DMX::Device::personalityCount())
    {
      response->nak(NR_DATA_OUT_OF_RANGE);
      return;
    }

  setPersonality(mode);

  response->responseType = RESPONSE_TYPE_ACK;
}


/**
 * @brief Device::actionGetDmxPersonalityDesc
 * @param message
 * @param response
 */
void Device::actionGetDmxPersonalityDesc(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(1, response))
    return;

  uint8_t mode = message->data()->front();

  if ( mode == 0 || mode > DMX::Device::personalityCount())
    {
      response->nak(NR_DATA_OUT_OF_RANGE);
      return;
    }
  response->responseType = RESPONSE_TYPE_ACK;
  response->appendData(mode);
  response->appendData(personalities_.at(mode)->footprint());
  for (size_t i = 0; i < personalities_.at(mode)->description().size(); i++)
    {
      if (i > 32)
        break;
      response->appendData(personalities_.at(mode)->description().at(i));
    }
}


/**
 * @brief Device::actionGetDmxStartAddress
 * @param message
 * @param response
 */
void Device::actionGetDmxStartAddress(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  if (footprint() == 0)
    response->appendData<uint16_t>(0xFFFF);
  else
    response->appendData(address());
}


/**
 * @brief Device::actionSetDmxStartAddress
 * @param message
 * @param response
 */
void Device::actionSetDmxStartAddress(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(2, response))
    return;

  uint16_t addr = Message::readType<uint16_t>(*message->data(), 0);

  if (!setAddress(addr))
    {
      response->nak(NR_DATA_OUT_OF_RANGE);
      return;
    }
  response->responseType = RESPONSE_TYPE_ACK;
}


/**
 * @brief Device::actionSensorDispatch
 * @param message
 * @param response
 */
void Device::actionSensorDispatch(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(1, response))
    return;

  uint8_t index = message->data()->front();

  switch (message->commandClass) {
    case GET_COMMAND:
      {
        if (index == 0xFF || index >= sensors_.size())
          {
            response->nak(NR_DATA_OUT_OF_RANGE);
            return;
          }
        auto sensor = sensors_.at(index);
        switch (message->parameterId) {
          case SENSOR_DEFINITION:
            sensor->actionGetSensorDefinition(index, response);
            break;
          case SENSOR_VALUE:
            sensor->actionGetSensorValue(index, response);
            break;
          }
      }
      break;
    case SET_COMMAND:
      {
        if (index >= sensors_.size() && index != 0xFF)
          {
            response->nak(NR_DATA_OUT_OF_RANGE);
            return;
          }
        auto setSensor = [index, message, response](Sensor * sensor)
        {
          switch (message->parameterId) {
            case SENSOR_VALUE:
              sensor->actionSetSensorValue(index, response);
              break;
            case RECORD_SENSORS:
              sensor->actionSetRecordSensors(response);
              break;
            }
        };
        if (index == 0xff)
          for (Sensor* s : sensors_)
            setSensor(s);
        else
          setSensor(sensors_.at(index));
      }
      break;
    }
}


/**
 * @brief Device::actionGetIdentifyDevice
 * @param message
 * @param response
 */
void Device::actionGetIdentifyDevice(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(0, response))
    return;

  response->responseType = RESPONSE_TYPE_ACK;
  response->appendData<uint8_t>(identifying_);
}


/**
 * @brief Device::actionSetIdentifyDevice
 * @param message
 * @param response
 */
void Device::actionSetIdentifyDevice(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(1, response))
    return;

  identify(message->data()->front());

  response->responseType = RESPONSE_TYPE_ACK;
}


/**
 * @brief Device::actionSetResetDevice
 * @param message
 * @param response
 */
void Device::actionSetResetDevice(const MsgPtr message, MsgPtr response)
{
  if (!message->requiredLength(1, response))
    return;

  switch (message->data()->front()) {
    case 0x01:
      reset(false);
      break;
    case 0xff:
      reset(true);
      break;
    default:
      response->nak(NR_FORMAT_ERROR);
      return;
    }

  response->responseType = RESPONSE_TYPE_ACK;
}

} // namespace RDM
