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

#ifndef QCANBUSFRAME_H
#define QCANBUSFRAME_H

#include <QtCore/qmetatype.h>
#include <QtCore/qobject.h>
#include <QtSerialBus/qtserialbusglobal.h>

QT_BEGIN_NAMESPACE

class QDataStream;

class Q_SERIALBUS_EXPORT QCanBusFrame
{
    Q_GADGET

public:
    using FrameId = quint32;

    class TimeStamp {
    public:
        constexpr TimeStamp(qint64 s = 0, qint64 usec = 0) noexcept
            : secs(s), usecs(usec) {}

        constexpr static TimeStamp fromMicroSeconds(qint64 usec) noexcept
        { return TimeStamp(usec / 1000000, usec % 1000000); }

        constexpr qint64 seconds() const noexcept { return secs; }
        constexpr qint64 microSeconds() const noexcept { return usecs; }

    private:
        qint64 secs;
        qint64 usecs;
    };

    enum FrameType {
        UnknownFrame        = 0x0,
        DataFrame           = 0x1,
        ErrorFrame          = 0x2,
        RemoteRequestFrame  = 0x3,
        InvalidFrame        = 0x4
    };
    Q_ENUM(FrameType)

    explicit QCanBusFrame(FrameType type = DataFrame) noexcept :
        isExtendedFrame(0x0),
        version(Qt_5_10),
        isFlexibleDataRate(0x0),
        isBitrateSwitch(0x0),
        isErrorStateIndicator(0x0),
        isLocalEcho(0x0),
        reserved0(0x0)
    {
        Q_UNUSED(reserved0);
        ::memset(reserved, 0, sizeof(reserved));
        setFrameId(0x0);
        setFrameType(type);
    }

    enum FrameError {
        NoError                     = 0,
        TransmissionTimeoutError    = (1 << 0),
        LostArbitrationError        = (1 << 1),
        ControllerError             = (1 << 2),
        ProtocolViolationError      = (1 << 3),
        TransceiverError            = (1 << 4),
        MissingAcknowledgmentError  = (1 << 5),
        BusOffError                 = (1 << 6),
        BusError                    = (1 << 7),
        ControllerRestartError      = (1 << 8),
        UnknownError                = (1 << 9),
        AnyError                    = 0x1FFFFFFFU
        //only 29 bits usable
    };
    Q_DECLARE_FLAGS(FrameErrors, FrameError)
    Q_FLAGS(FrameErrors)
    Q_ENUM(FrameError)

    explicit QCanBusFrame(QCanBusFrame::FrameId identifier, const QByteArray &data) :
        format(DataFrame),
        isExtendedFrame(0x0),
        version(Qt_5_10),
        isFlexibleDataRate(data.size() > 8 ? 0x1 : 0x0),
        isBitrateSwitch(0x0),
        isErrorStateIndicator(0x0),
        isLocalEcho(0x0),
        reserved0(0x0),
        load(data)
    {
        ::memset(reserved, 0, sizeof(reserved));
        setFrameId(identifier);
    }

    bool isValid() const noexcept
    {
        if (format == InvalidFrame)
            return false;

        // long id used, but extended flag not set
        if (!isExtendedFrame && (canId & 0x1FFFF800U))
            return false;

        if (!isValidFrameId)
            return false;

        // maximum permitted payload size in CAN or CAN FD
        const qsizetype length = load.size();
        if (isFlexibleDataRate) {
            if (format == RemoteRequestFrame)
                return false;

            return length <= 8 || length == 12 || length == 16 || length == 20
                    || length == 24 || length == 32 || length == 48 || length == 64;
        }

        return length <= 8;
    }

    constexpr FrameType frameType() const noexcept
    {
        switch (format) {
        case 0x1: return DataFrame;
        case 0x2: return ErrorFrame;
        case 0x3: return RemoteRequestFrame;
        case 0x4: return InvalidFrame;
        // no default to trigger warning
        }

        return UnknownFrame;
    }

    constexpr void setFrameType(FrameType newFormat) noexcept
    {
        switch (newFormat) {
        case DataFrame:
            format = 0x1; return;
        case ErrorFrame:
            format = 0x2; return;
        case RemoteRequestFrame:
            format = 0x3; return;
        case UnknownFrame:
            format = 0x0; return;
        case InvalidFrame:
            format = 0x4; return;
        }
    }

    constexpr bool hasExtendedFrameFormat() const noexcept { return (isExtendedFrame & 0x1); }
    constexpr void setExtendedFrameFormat(bool isExtended) noexcept
    {
        isExtendedFrame = (isExtended & 0x1);
    }

    constexpr QCanBusFrame::FrameId frameId() const noexcept
    {
        if (Q_UNLIKELY(format == ErrorFrame))
            return 0;
        return (canId & 0x1FFFFFFFU);
    }
    constexpr void setFrameId(QCanBusFrame::FrameId newFrameId)
    {
        if (Q_LIKELY(newFrameId < 0x20000000U)) {
            isValidFrameId = true;
            canId = newFrameId;
            setExtendedFrameFormat(isExtendedFrame || (newFrameId & 0x1FFFF800U));
        } else {
            isValidFrameId = false;
            canId = 0;
        }
    }

    void setPayload(const QByteArray &data)
    {
        load = data;
        if (data.size() > 8)
            isFlexibleDataRate = 0x1;
    }
    constexpr void setTimeStamp(TimeStamp ts) noexcept { stamp = ts; }

    QByteArray payload() const { return load; }
    constexpr TimeStamp timeStamp() const noexcept { return stamp; }

    constexpr FrameErrors error() const noexcept
    {
        if (format != ErrorFrame)
            return NoError;

        return FrameErrors(canId & 0x1FFFFFFFU);
    }
    constexpr void setError(FrameErrors e)
    {
        if (format != ErrorFrame)
            return;
        canId = (e & AnyError).toInt();
    }

    QString toString() const;

    constexpr bool hasFlexibleDataRateFormat() const noexcept { return (isFlexibleDataRate & 0x1); }
    constexpr void setFlexibleDataRateFormat(bool isFlexibleData) noexcept
    {
        isFlexibleDataRate = (isFlexibleData & 0x1);
        if (!isFlexibleData) {
            isBitrateSwitch = 0x0;
            isErrorStateIndicator = 0x0;
        }
    }

    constexpr bool hasBitrateSwitch() const noexcept { return (isBitrateSwitch & 0x1); }
    constexpr void setBitrateSwitch(bool bitrateSwitch) noexcept
    {
        isBitrateSwitch = (bitrateSwitch & 0x1);
        if (bitrateSwitch)
            isFlexibleDataRate = 0x1;
    }

    constexpr bool hasErrorStateIndicator() const noexcept { return (isErrorStateIndicator & 0x1); }
    constexpr void setErrorStateIndicator(bool errorStateIndicator) noexcept
    {
        isErrorStateIndicator = (errorStateIndicator & 0x1);
        if (errorStateIndicator)
            isFlexibleDataRate = 0x1;
    }
    constexpr bool hasLocalEcho() const noexcept { return (isLocalEcho & 0x1); }
    constexpr void setLocalEcho(bool localEcho) noexcept
    {
        isLocalEcho = (localEcho & 0x1);
    }

#ifndef QT_NO_DATASTREAM
    friend Q_SERIALBUS_EXPORT QDataStream &operator<<(QDataStream &, const QCanBusFrame &);
    friend Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &, QCanBusFrame &);
#endif

private:
    enum Version {
        Qt_5_8 = 0x0,
        Qt_5_9 = 0x1,
        Qt_5_10 = 0x2
    };

    quint32 canId:29; // acts as container for error codes too
    quint8 format:3; // max of 8 frame types

    quint8 isExtendedFrame:1;
    quint8 version:5;
    quint8 isValidFrameId:1;
    quint8 isFlexibleDataRate:1;

    quint8 isBitrateSwitch:1;
    quint8 isErrorStateIndicator:1;
    quint8 isLocalEcho:1;
    quint8 reserved0:5;

    // reserved for future use
    quint8 reserved[2];

    QByteArray load;
    TimeStamp stamp;
};

Q_DECLARE_TYPEINFO(QCanBusFrame, Q_RELOCATABLE_TYPE);
Q_DECLARE_TYPEINFO(QCanBusFrame::FrameError, Q_PRIMITIVE_TYPE);
Q_DECLARE_TYPEINFO(QCanBusFrame::FrameType, Q_PRIMITIVE_TYPE);
Q_DECLARE_TYPEINFO(QCanBusFrame::TimeStamp, Q_PRIMITIVE_TYPE);

Q_DECLARE_OPERATORS_FOR_FLAGS(QCanBusFrame::FrameErrors)

#ifndef QT_NO_DATASTREAM
Q_SERIALBUS_EXPORT QDataStream &operator<<(QDataStream &, const QCanBusFrame &);
Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &, QCanBusFrame &);
#endif

QT_END_NAMESPACE

Q_DECLARE_METATYPE(QCanBusFrame::FrameType)
Q_DECLARE_METATYPE(QCanBusFrame::FrameErrors)

#endif // QCANBUSFRAME_H
