/******************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtKnx module.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
******************************************************************************/

#include "qknxnetipdevicedib.h"
#include "qknxutils.h"

QT_BEGIN_NAMESPACE

/*!
    \class QKnxNetIpDeviceDibProxy

    \inmodule QtKnx
    \ingroup qtknx-netip

    \brief The QKnxNetIpDeviceDibProxy class provides the means to read the
    device information of a KNXnet/IP device from the generic \l QKnxNetIpDib
    class and to create a KNXnet/IP device information block (DIB) structure.

    \note When using QKnxNetIpDeviceDibProxy, care must be taken to ensure that
    the referenced KNXnet/IP DIB structure outlives the proxy on all code paths,
    lest the proxy ends up referencing deleted data.

    Reading the device information can be achieved like this:
    \code
        auto dib = QKnxNetIpDib::fromBytes(...);

        QKnxNetIpDeviceDibProxy proxy(dib);
        if (!proxy.isValid())
            return;

        auto mediumType = proxy.mediumType();
        auto programmingMode = proxy.deviceStatus();
        ...
        auto deviceName = proxy.deviceName();
    \endcode

    \sa builder(), {Qt KNXnet/IP Connection Classes}
*/

/*!
    \internal
    \fn QKnxNetIpDeviceDibProxy::QKnxNetIpDeviceDibProxy()
*/

/*!
    \internal
    \fn QKnxNetIpDeviceDibProxy::~QKnxNetIpDeviceDibProxy()
*/

/*!
    \internal
    \fn QKnxNetIpDeviceDibProxy::QKnxNetIpDeviceDibProxy(const QKnxNetIpDib &&)
*/

/*!
    Constructs a proxy object with the specified KNXnet/IP DIB structure
    \a dib to read the device information of a KNXnet/IP device.
*/
QKnxNetIpDeviceDibProxy::QKnxNetIpDeviceDibProxy(const QKnxNetIpDib &dib)
    : m_dib(dib)
{}

/*!
    Returns \c true if the KNXnet/IP structure to create the object is a valid
    KNXnet/IP DIB structure; otherwise returns \c false.
*/
bool QKnxNetIpDeviceDibProxy::isValid() const
{
    return m_dib.isValid() && m_dib.size() == 54
        && m_dib.code() == QKnxNetIp::DescriptionType::DeviceInfo;
}

/*!
    Returns the description type of this KNXnet/IP structure if the
    object that was passed during construction was valid; otherwise
    returns \l QKnx::NetIp::Unknown.
*/
QKnxNetIp::DescriptionType QKnxNetIpDeviceDibProxy::descriptionType() const
{
    if (isValid())
        return m_dib.code();
    return QKnxNetIp::DescriptionType::Unknown;
}

/*!
    Returns the medium type contained inside the KNXnet/IP DIB structure
    if the object that was passed during construction was valid; otherwise
    returns \l QKnx::Unknown.
*/
QKnx::MediumType QKnxNetIpDeviceDibProxy::mediumType() const
{
    if (isValid())
        return QKnx::MediumType(m_dib.constData().value(0));
    return QKnx::MediumType::Unknown;
}

/*!
    Returns the device status contained inside the KNXnet/IP DIB structure.

    \sa QKnx::NetIp::ProgrammingMode
*/
QKnxNetIp::ProgrammingMode QKnxNetIpDeviceDibProxy::deviceStatus() const
{
    return QKnxNetIp::ProgrammingMode(m_dib.constData().value(1));
}

/*!
    Returns the individual address contained inside the KNXnet/IP DIB
    structure if the object that was passed during construction was valid;
    otherwise returns a default constructed \l QKnxAddress.
*/
QKnxAddress QKnxNetIpDeviceDibProxy::individualAddress() const
{
    if (isValid())
        return { QKnxAddress::Type::Individual, m_dib.constData().mid(2, 2) };
    return {};
}

/*!
    Returns the KNX project installation ID contained inside the KNXnet/IP
    DIB structure.
*/
quint16 QKnxNetIpDeviceDibProxy::projectInstallationId() const
{
    return QKnxUtils::QUint16::fromBytes(m_dib.constData(), 4);
}

/*!
    Returns the device serial number contained inside the KNXnet/IP DIB
    structure if the object that was passed during construction was valid;
    otherwise returns an empty byte array.
*/
QKnxByteArray QKnxNetIpDeviceDibProxy::serialNumber() const
{
    if (isValid())
        return m_dib.constData().mid(6, 6);
    return {};
}

/*!
    Returns the multicast address contained inside the KNXnet/IP DIB structure
    if the object that was passed during construction was valid; otherwise
    returns a default constructed \l QHostAddress.
*/
QHostAddress QKnxNetIpDeviceDibProxy::multicastAddress() const
{
    if (isValid())
        return QKnxUtils::HostAddress::fromBytes(m_dib.constData(), 12);
    return {};
}

/*!
    Returns the MAC address contained inside the KNXnet/IP DIB structure if the
    object that was passed during construction was valid; otherwise returns an
    empty byte array.
*/
QKnxByteArray QKnxNetIpDeviceDibProxy::macAddress() const
{
    if (isValid())
        return m_dib.constData().mid(16, 6);
    return {};
}

/*!
    Returns the device name contained inside the KNXnet/IP DIB structure if the
    object that was passed during construction was valid; otherwise returns an
    empty byte array.
*/
QByteArray QKnxNetIpDeviceDibProxy::deviceName() const
{
    if (isValid()) {
        auto tmp = m_dib.constData().mid(22, 30);
        return QByteArray((const char *) tmp.constData());
    }
    return {};
}

/*!
    Returns a builder object to create a KNXnet/IP device information DIB
    structure.
*/
QKnxNetIpDeviceDibProxy::Builder QKnxNetIpDeviceDibProxy::builder()
{
    return QKnxNetIpDeviceDibProxy::Builder();
}


namespace QKnxPrivate
{
    /*!
        \internal
    */
    static bool isMediumType(QKnx::MediumType type)
    {
        switch (type) {
        case QKnx::MediumType::Unknown:
        case QKnx::MediumType::TP:
        case QKnx::MediumType::PL:
        case QKnx::MediumType::RF:
        case QKnx::MediumType::NetIP:
            return true;
        default:
            break;
        }
        return false;
    }
}


/*!
    \class QKnxNetIpDeviceDibProxy::Builder

    \inmodule QtKnx
    \inheaderfile QKnxNetIpDeviceDibProxy

    \brief The QKnxNetIpDeviceDibProxy::Builder class creates a KNXnet/IP device
    information DIB structure.

    A KNXnet/IP device information DIB structure contains specific information
    about a KNXnet/IP device.

    The common way to create such a DIB structure is:
    \code
         // setup the device information

         auto dib = QKnxNetIpDeviceDibProxy::builder()
            .setMediumType(mediumType)
            .setDeviceStatus(programmingMode);
            .setIndividualAddress(individualAddress)
            .setProjectInstallationId(projectId)
            .setSerialNumber(serialNumber)
            .setMulticastAddress(multicastAddress)
            .setMacAddress(macAddress)
            .setDeviceName(deviceName)
            .create();
    \endcode
*/

/*!
    Sets the medium type to \a mediumType if the passed argument is a valid
    \l QKnx::MediumType and returns a reference to the builder.
*/
QKnxNetIpDeviceDibProxy::Builder &
    QKnxNetIpDeviceDibProxy::Builder::setMediumType(QKnx::MediumType mediumType)
{
    if (QKnxPrivate::isMediumType(mediumType))
        m_mediumType = mediumType;
    return *this;
}

/*!
    Sets the device status to \a mode if the passed argument is a valid
    \l QKnx::NetIp::ProgrammingMode and returns a reference to the builder.
*/
QKnxNetIpDeviceDibProxy::Builder &
    QKnxNetIpDeviceDibProxy::Builder::setDeviceStatus(QKnxNetIp::ProgrammingMode mode)
{
    if (QKnxNetIp::isProgrammingMode(mode))
        m_progMode = mode;
    return *this;
}

/*!
    Sets the individual address to \a address if the passed argument is a valid
    \l QKnxAddress and of type \l {QKnxAddress::Type} {QKnxAddress::Individual}
    and returns a reference to the builder.
*/
QKnxNetIpDeviceDibProxy::Builder &
    QKnxNetIpDeviceDibProxy::Builder::setIndividualAddress(const QKnxAddress &address)
{
    if (address.isValid() && address.type() == QKnxAddress::Type::Individual)
        m_address = address;
    return *this;
}

/*!
    Sets the KNX project installation ID to \a projectId and returns a
    reference to the builder.
*/
QKnxNetIpDeviceDibProxy::Builder &
    QKnxNetIpDeviceDibProxy::Builder::setProjectInstallationId(quint16 projectId)
{
    m_projectId = projectId;
    return *this;
}

/*!
    Sets the serial number to \a serialNumber and returns a reference to the
    builder.

    \note The serial number must contain exactly 6 bytes.
*/
QKnxNetIpDeviceDibProxy::Builder &
    QKnxNetIpDeviceDibProxy::Builder::setSerialNumber(const QKnxByteArray &serialNumber)
{
    if (serialNumber.size() == 6)
        m_serialNumber = serialNumber;
    return *this;
}

/*!
    Sets the multicast address to \a multicastAddress if the passed argument is
    a valid \l QHostAddress and returns a reference to the builder.
*/
QKnxNetIpDeviceDibProxy::Builder &
    QKnxNetIpDeviceDibProxy::Builder::setMulticastAddress(const QHostAddress &multicastAddress)
{
    if (multicastAddress == QHostAddress::AnyIPv4 || !multicastAddress.isMulticast())
        m_multicastAddress = multicastAddress;
    return *this;
}

/*!
    Sets the MAC address to \a macAddress and returns a reference to the
    builder. By default it is set to the KNXnet/IP System Setup Multicast
    Address.

    \note The MAC address must contain exactly 6 bytes.
*/
QKnxNetIpDeviceDibProxy::Builder &
    QKnxNetIpDeviceDibProxy::Builder::setMacAddress(const QKnxByteArray &macAddress)
{
    if (macAddress.size() == 6)
        m_macAddress = macAddress;
    return *this;
}

/*!
    Sets the device name to \a deviceName and returns a reference to the
    builder.

    \note The size of the device name may not contain more than 30 characters.
*/
QKnxNetIpDeviceDibProxy::Builder &
    QKnxNetIpDeviceDibProxy::Builder::setDeviceName(const QByteArray &deviceName)
{
    m_deviceName = deviceName;
    return *this;
}

/*!
    Creates and returns a generic QKnxNetIpDib containing the specific device
    information of a KNXnet/IP device.

    \note The returned structure may be invalid depending on the values used
    during setup.

    \sa isValid()
*/
QKnxNetIpDib QKnxNetIpDeviceDibProxy::Builder::create() const
{
    if (!QKnxPrivate::isMediumType(m_mediumType)
        || !QKnxNetIp::isProgrammingMode(m_progMode)
        || !m_address.isValid()
        || m_serialNumber.size() != 6
        || m_multicastAddress.isNull()
        || m_macAddress.size() != 6) {
            return { QKnxNetIp::DescriptionType::DeviceInfo };
    }

    QKnxByteArray data;
    data.append(quint8(m_mediumType));
    data.append(quint8(m_progMode));
    data.append(m_address.bytes());
    data.append(QKnxUtils::QUint16::bytes(m_projectId));
    data.append(m_serialNumber);
    data.append(QKnxUtils::HostAddress::bytes(m_multicastAddress));
    data.append(m_macAddress);
    data.append(QKnxByteArray::fromByteArray(m_deviceName));

    if (data.size() < 52)
        data.append(QKnxByteArray(52 - data.size(), 0));
    data.resize(52); // size enforced by 7.5.4.2 Device information DIB

    return { QKnxNetIp::DescriptionType::DeviceInfo, data };
}

QT_END_NAMESPACE
