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

namespace RDM {

/**
 * @brief Message::Message
 */
Message::Message()
  : failure_mode(0)
{

}


/**
 * @brief Message::Message
 * @param obj
 */
Message::Message(const Message &obj)
  : source(obj.source)
  , destination(obj.destination)
  , transaction(obj.transaction)
  , portID(obj.portID)
  , messageCount(obj.messageCount)
  , subDevice(obj.subDevice)
  , parameterId(obj.parameterId)
  , failure_mode(0)
{
  data_.insert(data_.end(), obj.data()->begin(), obj.data()->end());
}


/**
 * @brief Message::Message
 * @param data
 */
Message::Message(const std::vector<uint8_t> &data)
  : failure_mode(0)
{
  read(data);
}


/**
 * @brief Message::read
 * @param data
 */
void Message::read(const std::vector<uint8_t> &data)
{
  if (data.size() < 9) // SC + SC_SUB + LENGTH + DESTINATION
    {
      short_message = true;
      return;
    }

  // 6.2.1 START Code
  // This field shall contain the defined RDM START Code (SC_RDM). Controllers
  // and Responders shall always send SC_RDM in this slot, and any packet
  // containing a value other than SC_RDM is outside the scope of this standard.
  if (data[0] != SC_RDM)
    {
      incorrect_sc = true;
      return;
    }

  // 6.2.2 Sub START Code
  // This field shall contain the Sub-START Code within RDM that defines this
  // packet structure (SC_SUB_MESSAGE). Future versions of this standard which
  // may have additional or different packet structures would use this field to
  // identify the packet structure being used.
  // Controllers shall always send SC_SUB_MESSAGE in this slot, and Responders
  // shall ignore any packets containing other values.
  if (data[1] != SC_SUB_MESSAGE)
    {
      incorrect_sub_sc = true;
      return;
    }

  // 6.2.3 Message Length
  // The Message Length value is defined as the number of slots in the RDM
  // Packet including the START Code and excluding the Checksum. Each slot is
  // an 8-bit value.
  // The Message Length field points to the Checksum High Slot.
  uint8_t length = data[2];
  if (length + 2 != data.size())
    {
      length_mismatch = true;
      return;
    }

  // 6.2.4 Destination UID
  // The Destination UID is the UID of the target device(s).
  destination.manufacturer = readType<uint16_t>(data, 3);
  destination.device = readType<uint32_t>(data, 5);

  // 6.2.5 Source UID
  // The Source UID is the UID of the device originating this packet.
  source.manufacturer = readType<uint16_t>(data, 9);
  source.device = readType<uint32_t>(data, 11);

  // 6.2.6 Transaction Number (TN)
  // Controller generated packets increment this field every time an RDM packet
  // is transmitted.
  // Responders shall reply with their Transaction Number set to the Transaction
  // Number contained in the controller packet to which they are responding.
  transaction = data[15];

  // 6.2.7 Port ID / Response Type
  // This field serves different functions depending on whether the message is
  // being generated by the controller or the responder.
  portID = data[16];

  // 6.2.8 Message Count
  // The message count field is used by a responder to indicate that additional
  // data is now available for collection by a controller. This data (which
  // might be unrelated to the current message transaction) should be collected
  // by the controller using the GET:QUEUED_MESSAGE command.
  messageCount = data[17];

  // 6.2.9 Sub-Device Field
  // Sub-devices should be used in devices containing a repetitive number of
  // similar modules, such as a dimmer rack. The Sub-Device field allows
  // Parameter messages to be addressed to a specific module within the device
  // to set or get properties of that module.
  // The 16-bit sub-device field provides a range of 512 valid sub-devices,
  // addressed from 1 - 512.
  // The value of 0xFFFF is reserved as a SUB_DEVICE_ALL_CALL. A value of 0x0000
  // shall be used to address the root or base properties of the device that do
  // not belong to any sub-device module.
  // The Parameter ID designates which parameter on the sub-device is being
  // addressed. The use of Sub-Devices is described further in Section 9.
  subDevice = readType<uint16_t>(data, 18);

  // 6.2.10.1 Command Class (CC)
  commandClass = data[20];

  // 6.2.10.2 Parameter ID (PID)
  // The Parameter ID is a 16-bit number that identifies a specific type of
  // Parameter Data. The Parameter ID (PID) may represent either a well known
  // Parameter such as those defined in this document, or a
  // Manufacturer-specific parameter whose details are either published by the
  // Manufacturer for third-party support or proprietary for the Manufacturer’s
  // own use.
  parameterId = readType<uint16_t>(data, 21);

  // 6.2.10.3 Parameter Data Length (PDL)
  // The Parameter Data Length (PDL) is the number of slots included in the
  // Parameter Data area that it precedes. When this field is set to 0x00 it
  // indicates that there is no Parameter Data following.
  uint8_t pdl = data[23];

  // 6.2.10.4 Parameter Data (PD)
  // The Parameter Data is of variable length.
  for (int i = 0; i < pdl; i++)
    appendData(data[24+i]);

  // 6.2.11 Checksum
  // If the checksum field in the packet does not match the calculated checksum,
  // then the packet shall be discarded and no response sent.
  auto chksum = readType<uint16_t>(data, data.size() - 2);
  if (chksum != checksum())
    {
      checksum_fail = true;
      return;
    }
}


/**
 * @brief Message::write
 * @param data
 */
void Message::write(std::vector<uint8_t> &data) const
{
  if (commandClass == DISCOVERY_COMMAND_RESPONSE &&
      parameterId == DISC_UNIQUE_BRANCH)
    {
      writeDiscBranch(data);
      return;
    }
  data.push_back(SC_RDM);
  data.push_back(SC_SUB_MESSAGE);
  data.push_back(24 + length());
  writeType(data, destination.manufacturer);
  writeType(data, destination.device);
  writeType(data, source.manufacturer);
  writeType(data, source.device);
  data.push_back(transaction);
  data.push_back(portID);
  data.push_back(messageCount);
  writeType(data, subDevice);
  data.push_back(commandClass);
  writeType(data, parameterId);
  data.push_back(length());
  data.insert(data.end(), data_.begin(), data_.end());
  writeType(data, checksum());
}


/**
 * @brief Message::writeDiscBranch
 * @param data
 */
void Message::writeDiscBranch(std::vector<uint8_t> &data) const
{
  for (int i = 0; i < 7; i++)
    data.push_back(0xfe);     //!< Response Preamble bytes
  data.push_back(0xaa);       //!< Preamble separator byte

  uint16_t sum = 0;
  for ( const uint8_t& v : data_)
    sum = addSum_(sum, v);

  std::vector<uint8_t> d = data_;
  writeType<uint16_t>(d, sum);

  for ( uint8_t& v : d)
    {
      data.push_back(v | 0xaa);
      data.push_back(v | 0x55);
    }
}


/**
 * @brief Message::nak
 * @param reason
 */
void Message::nak(uint16_t reason)
{
  data_.clear();
  responseType = RESPONSE_TYPE_NACK_REASON;
  appendData(reason);

}


/**
 * @brief Message::data
 * @return
 */
const std::vector<uint8_t>* Message::data() const
{
  return &data_;
}


/**
 * @brief Message::length
 * @return
 */
uint8_t Message::length() const
{
  return data_.size();
}


/**
 * @brief Message::checksum
 * @return
 */
uint16_t Message::checksum() const
{
  uint16_t sum = 0;

  sum = addSum_(sum, SC_RDM);
  sum = addSum_(sum, SC_SUB_MESSAGE);
  sum = addSum_(sum, 24 + length());
  sum = addSum_(sum, source.manufacturer);
  sum = addSum_(sum, source.device);
  sum = addSum_(sum, destination.manufacturer);
  sum = addSum_(sum, destination.device);
  sum = addSum_(sum, transaction);
  sum = addSum_(sum, portID);
  sum = addSum_(sum, messageCount);
  sum = addSum_(sum, subDevice);
  sum = addSum_(sum, commandClass);
  sum = addSum_(sum, parameterId);
  sum = addSum_(sum, length());
  for (uint8_t val : data_)
    sum = addSum_(sum, val);

  return sum;
}


/**
 * @brief Message::requiredLength
 * @param length
 * @param response
 * @return
 */
bool Message::requiredLength(const size_t length, MsgPtr response) const
{
  if (data_.size() != length)
    {
      response->nak(NR_FORMAT_ERROR);
      return false;
    }
  return true;
}

} // namespace RDM
