// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "qtlv_p.h"

#include "qnearfieldtagtype1_p.h"

#include <QtCore/QVariant>

#include <QtCore/QDebug>

QT_BEGIN_NAMESPACE

QPair<int, int> qParseReservedMemoryControlTlv(const QByteArray &tlvData)
{
    quint8 position = tlvData.at(0);
    int pageAddr = position >> 4;
    int byteOffset = position & 0x0f;

    int size = quint8(tlvData.at(1));
    if (size == 0)
        size = 256;

    quint8 pageControl = tlvData.at(2);
    int bytesPerPage = pageControl & 0x0f;

    if (!bytesPerPage)
        return qMakePair(0, 0);

    int byteAddress = pageAddr * (1 << bytesPerPage) + byteOffset;
    return qMakePair(byteAddress, size);
}

QPair<int, int> qParseLockControlTlv(const QByteArray &tlvData)
{
    quint8 position = tlvData.at(0);
    int pageAddr = position >> 4;
    int byteOffset = position & 0x0f;

    int size = quint8(tlvData.at(1));
    if (size == 0)
        size = 256;
    size = size / 8;

    quint8 pageControl = tlvData.at(2);
    int bytesPerPage = pageControl & 0x0f;

    if (!bytesPerPage)
        return qMakePair(0, 0);

    int byteAddress = pageAddr * (1 << bytesPerPage) + byteOffset;
    return qMakePair(byteAddress, size);
}

QTlvReader::QTlvReader(QNearFieldTargetPrivate *target)
:   m_target(target), m_index(-1)
{
    if (qobject_cast<QNearFieldTagType1 *>(m_target)) {
        addReservedMemory(0, 12);   // skip uid, cc
        addReservedMemory(104, 16); // skip reserved block D, lock block E

        addReservedMemory(120, 8);  // skip reserved block F
    }
}

QTlvReader::QTlvReader(const QByteArray &data)
:   m_target(0), m_rawData(data), m_index(-1)
{
}

void QTlvReader::addReservedMemory(int offset, int length)
{
    m_reservedMemory.insert(offset, length);
}

/*!
    Returns the number of bytes of reserved memory found so far.  The actual number of reserved
    bytes will not be known until atEnd() returns true.
*/
int QTlvReader::reservedMemorySize() const
{
    int total = 0;

    QMap<int, int>::ConstIterator i;
    for (i = m_reservedMemory.constBegin(); i != m_reservedMemory.constEnd(); ++i)
        total += i.value();

    return total;
}

/*!
    Returns the request id that the TLV reader is currently waiting on.
*/
QNearFieldTarget::RequestId QTlvReader::requestId() const
{
    return m_requestId;
}

bool QTlvReader::atEnd() const
{
    if (m_index == -1)
        return false;

    if (m_requestId.isValid())
        return false;

    return (m_index == m_tlvData.size()) || (tag() == 0xfe);
}

/*!
    Moves to the next TLV. Returns true on success; otherwise returns false.
*/
bool QTlvReader::readNext()
{
    if (atEnd())
        return false;

    // Move to next TLV
    if (m_index == -1) {
        m_index = 0;
    } else if (m_requestId.isValid()) {
        // do nothing
    } else if (tag() == 0x00 || tag() == 0xfe) {
        ++m_index;
    } else {
        int tlvLength = length();
        m_index += (tlvLength < 0xff) ? tlvLength + 2 : tlvLength + 4;
    }

    // Ensure that tag byte is available
    if (!readMoreData(m_index))
        return false;

    // Ensure that length byte(s) are available
    if (length() == -1)
        return false;

    // Ensure that data bytes are available
    int tlvLength = length();

    int dataOffset = (tlvLength < 0xff) ? m_index + 2 : m_index + 4;

    if (!readMoreData(dataOffset + tlvLength - 1))
        return false;

    switch (tag()) {
    case 0x01: { // Lock Control TLV
        QPair<int, int> locked = qParseLockControlTlv(data());
        addReservedMemory(locked.first, locked.second);
        break;
    }
    case 0x02: { // Reserved Memory Control TLV
        QPair<int, int> reserved = qParseReservedMemoryControlTlv(data());
        addReservedMemory(reserved.first, reserved.second);
        break;
    }
    }

    return true;
}

quint8 QTlvReader::tag() const
{
    return m_tlvData.at(m_index);
}

int QTlvReader::length()
{
    if (tag() == 0x00 || tag() == 0xfe)
        return 0;

    if (!readMoreData(m_index + 1))
        return -1;

    quint8 shortLength = m_tlvData.at(m_index + 1);
    if (shortLength != 0xff)
        return shortLength;

    if (!readMoreData(m_index + 3))
        return -1;

    quint16 longLength = (quint8(m_tlvData.at(m_index + 2)) << 8) |
                          quint8(m_tlvData.at(m_index + 3));

    if (longLength < 0xff || longLength == 0xffff) {
        qWarning("Invalid 3 byte length");
        return 0;
    }

    return longLength;
}

QByteArray QTlvReader::data()
{
    int tlvLength = length();

    int dataOffset = (tlvLength < 0xff) ? m_index + 2 : m_index + 4;

    if (!readMoreData(dataOffset + tlvLength - 1))
        return QByteArray();

    return m_tlvData.mid(dataOffset, tlvLength);
}

bool QTlvReader::readMoreData(int sparseOffset)
{
    while (sparseOffset >= m_tlvData.size()) {
        int absOffset = absoluteOffset(m_tlvData.size());

        QByteArray data;

        if (!m_rawData.isEmpty()) {
            data = m_rawData.mid(absOffset, dataLength(absOffset));
        } else if (QNearFieldTagType1 *tag = qobject_cast<QNearFieldTagType1 *>(m_target)) {
            quint8 segment = absOffset / 128;

            if (m_requestId.isValid()) {
                QVariant v = m_target->requestResponse(m_requestId);
                if (!v.isValid())
                    return false;

                m_requestId = QNearFieldTarget::RequestId();

                data = v.toByteArray();

                if (absOffset < 120)
                    data = data.mid(2);

                int length = dataLength(absOffset);

                data = data.mid(absOffset - (segment * 128), length);
            } else {
                m_requestId = (absOffset < 120) ? tag->readAll() : tag->readSegment(segment);

                return false;
            }
        }

        if (data.isEmpty() && sparseOffset >= m_tlvData.size())
            return false;

        m_tlvData.append(data);
    }

    return true;
}

int QTlvReader::absoluteOffset(int sparseOffset) const
{
    int absoluteOffset = sparseOffset;
    const QList<int> offsets = m_reservedMemory.keys();
    for (const int offset : offsets) {
        if (offset <= absoluteOffset)
            absoluteOffset += m_reservedMemory.value(offset);
    }

    return absoluteOffset;
}

/*!
    Returns the length of the contiguous non-reserved data block starting from absolute offset
    \a startOffset.  -1 is return as the length of the last contiguous data block.
*/
int QTlvReader::dataLength(int startOffset) const
{
    const QList<int> offsets = m_reservedMemory.keys();
    for (const int offset : offsets) {
        if (offset <= startOffset)
            continue;

        return offset - startOffset;
    }

    return -1;
}


QTlvWriter::QTlvWriter(QNearFieldTargetPrivate *target)
:   m_target(target), m_rawData(0), m_index(0), m_tagMemorySize(-1)
{
    if (qobject_cast<QNearFieldTagType1 *>(m_target)) {
        addReservedMemory(0, 12);   // skip uid, cc
        addReservedMemory(104, 16); // skip reserved block D, lock block E

        addReservedMemory(120, 8);  // skip reserved block F
    }
}

QTlvWriter::QTlvWriter(QByteArray *data)
:   m_target(0), m_rawData(data), m_index(0), m_tagMemorySize(-1)
{
}

QTlvWriter::~QTlvWriter()
{
    if (m_rawData)
        process(true);
}

void QTlvWriter::addReservedMemory(int offset, int length)
{
    m_reservedMemory.insert(offset, length);
}

void QTlvWriter::writeTlv(quint8 tagType, const QByteArray &data)
{
    m_buffer.append(tagType);

    if (tagType != 0x00 && tagType != 0xfe) {
        int length = data.size();
        if (length < 0xff) {
            m_buffer.append(quint8(length));
        } else {
            m_buffer.append(char(0xff));
            m_buffer.append(quint16(length) >> 8);
            m_buffer.append(quint16(length) & 0x00ff);
        }

        m_buffer.append(data);
    }

    process();

    switch (tagType) {
    case 0x01: {    // Lock Control TLV
        QPair<int, int> locked = qParseLockControlTlv(data);
        addReservedMemory(locked.first, locked.second);
        break;
    }
    case 0x02: {    // Reserved Memory Control TLV
        QPair<int, int> reserved = qParseReservedMemoryControlTlv(data);
        addReservedMemory(reserved.first, reserved.second);
        break;
    }
    }
}

/*!
    Processes more of the TLV writer process. Returns true if the TLVs have been successfully
    written to the target or buffer; otherwise returns false.

    A false return value indicates that an NFC request is pending (if requestId() returns a valid
    request identifier) or the write process has failed (requestId() returns an invalid request
    identifier).
*/
bool QTlvWriter::process(bool all)
{
    if (m_requestId.isValid()) {
        QVariant v = m_target->requestResponse(m_requestId);
        if (!v.isValid())
            return false;
    }

    if (m_tagMemorySize == -1) {
        if (m_rawData)
            m_tagMemorySize = m_rawData->size();
        else if (QNearFieldTagType1 *tag = qobject_cast<QNearFieldTagType1 *>(m_target)) {
            if (m_requestId.isValid()) {
                m_tagMemorySize = 8 * (tag->requestResponse(m_requestId).toUInt() + 1);
                m_requestId = QNearFieldTarget::RequestId();
            } else {
                m_requestId = tag->readByte(10);
                return false;
            }
        }
    }

    while (!m_buffer.isEmpty()) {
        int spaceRemaining = moveToNextAvailable();
        if (spaceRemaining < 1)
            return false;

        int length = qMin(spaceRemaining, m_buffer.size());

        if (m_rawData) {
            m_rawData->replace(m_index, length, m_buffer);
            m_index += length;
            m_buffer = m_buffer.mid(length);
        } else if (QNearFieldTagType1 *tag = qobject_cast<QNearFieldTagType1 *>(m_target)) {
            int bufferIndex = 0;

            // static memory - can only use writeByte()
            while (m_index < 120 && bufferIndex < length) {
                if (m_requestId.isValid()) {
                    if (!m_target->requestResponse(m_requestId).toBool())
                        return false;

                    m_requestId = QNearFieldTarget::RequestId();

                    ++m_index;
                    ++bufferIndex;
                } else {
                    m_requestId = tag->writeByte(m_index, m_buffer.at(bufferIndex));
                    m_buffer = m_buffer.mid(bufferIndex);
                    return false;
                }
            }


            // dynamic memory - writeBlock() full
            while (m_index >= 120 && (m_index % 8 == 0) && bufferIndex + 8 < length) {
                if (m_requestId.isValid()) {
                    if (!m_target->requestResponse(m_requestId).toBool())
                        return false;

                    m_requestId = QNearFieldTarget::RequestId();

                    m_index += 8;
                    bufferIndex += 8;
                } else {
                    m_requestId = tag->writeBlock(m_index / 8, m_buffer.mid(bufferIndex, 8));
                    m_buffer = m_buffer.mid(bufferIndex);
                    return false;
                }
            }

            // partial block
            int currentBlock = m_index / 8;
            int nextBlock = currentBlock + 1;
            int currentBlockStart = currentBlock * 8;
            int nextBlockStart = nextBlock * 8;

            int fillLength = qMin(nextBlockStart - m_index, spaceRemaining - bufferIndex);

            if (fillLength && (all || m_buffer.size() - bufferIndex >= fillLength) &&
                (m_buffer.size() != bufferIndex)) {
                // sufficient data available
                if (m_requestId.isValid()) {
                    const QVariant v = tag->requestResponse(m_requestId);
                    if (v.typeId() == QMetaType::QByteArray) {
                        // read in block
                        QByteArray block = v.toByteArray();

                        int fill = qMin(fillLength, m_buffer.size() - bufferIndex);

                        for (int i = m_index - currentBlockStart; i < fill; ++i)
                            block[i] = m_buffer.at(bufferIndex++);

                        // now write block
                        m_requestId = tag->writeBlock(currentBlock, block);
                        return false;
                    } else if (v.typeId() == QMetaType::Bool) {
                        m_requestId = QNearFieldTarget::RequestId();
                        int fill = qMin(fillLength, m_buffer.size() - bufferIndex);
                        bufferIndex = fill - (m_index - currentBlockStart);

                        // write complete
                        if (!v.toBool())
                            return false;
                    }
                } else {
                    // read in block
                    m_requestId = tag->readBlock(currentBlock);
                    m_buffer = m_buffer.mid(bufferIndex);
                    return false;
                }
            }

            m_buffer = m_buffer.mid(bufferIndex);
        }
    }

    return true;
}

QNearFieldTarget::RequestId QTlvWriter::requestId() const
{
    return m_requestId;
}

int QTlvWriter::moveToNextAvailable()
{
    int length = -1;

    // move index to next available byte
    QMap<int, int>::ConstIterator i;
    for (i = m_reservedMemory.constBegin(); i != m_reservedMemory.constEnd(); ++i) {
        if (m_index < i.key()) {
            length = i.key() - m_index;
            break;
        } else if (m_index == i.key()) {
            m_index += i.value();
        } else if (m_index > i.key() && m_index < (i.key() + i.value())) {
            m_index = i.key() + i.value();
        }
    }

    if (length == -1)
        return m_tagMemorySize - m_index;

    Q_ASSERT(length != -1);

    return length;
}

QT_END_NAMESPACE
