/*
 *  Copyright (c) 2023, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include <openthread/config.h>

#include "common/array.hpp"
#include "common/code_utils.hpp"
#include "instance/instance.hpp"

#include "ncp/ncp_base.hpp"
#include "openthread/link_raw.h"

#include "test_platform.h"
#include "test_util.hpp"

using namespace ot;
using namespace ot::Ncp;

enum
{
    kTestBufferSize = 800
};

enum
{
    kTestMacScanChannelMask = 0x01
};

OT_TOOL_PACKED_BEGIN
struct RadioMessage
{
    uint8_t mChannel;
    uint8_t mPsdu[OT_RADIO_FRAME_MAX_SIZE];
} OT_TOOL_PACKED_END;

static struct RadioMessage sDefaultMessages[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM];
static otRadioFrame        sTxFrame[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM];
static ot::Instance       *sInstances[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM];
static ot::Instance       *sLastInstance;

otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance)
{
    otRadioFrame *frame = nullptr;

    for (size_t i = 0; i < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; i++)
    {
        if (sInstances[i] == aInstance || sInstances[i] == nullptr)
        {
            sTxFrame[i].mPsdu = sDefaultMessages->mPsdu;
            frame             = &sTxFrame[i];

            break;
        }
    }

    return frame;
}

otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *)
{
    sLastInstance = static_cast<ot::Instance *>(aInstance);
    return OT_ERROR_NONE;
}

otError otPlatMultipanGetActiveInstance(otInstance **aInstance)
{
    otError error = OT_ERROR_NOT_IMPLEMENTED;
    OT_UNUSED_VARIABLE(aInstance);

#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
    *aInstance = sLastInstance;
    error      = OT_ERROR_NONE;
#endif

    return error;
}

otError otPlatMultipanSetActiveInstance(otInstance *aInstance, bool aCompletePending)
{
    otError error = OT_ERROR_NOT_IMPLEMENTED;
    OT_UNUSED_VARIABLE(aInstance);
    OT_UNUSED_VARIABLE(aCompletePending);

#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
    VerifyOrExit(sLastInstance != static_cast<ot::Instance *>(aInstance), error = OT_ERROR_ALREADY);
    sLastInstance = static_cast<ot::Instance *>(aInstance);
    error         = OT_ERROR_NONE;
exit:
#endif

    return error;
}

class TestNcp : public NcpBase
{
public:
    explicit TestNcp(ot::Instance *aInstance)
        : mLastHeader(0)
        , mLastStatus(0)
        , mLastProp(0)
        , NcpBase(aInstance)
    {
        memset(mMsgBuffer, 0, kTestBufferSize);
        mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToNcpBuffer, this);
        mTxFrameBuffer.SetFrameRemovedCallback(nullptr, this);
    };

    explicit TestNcp(ot::Instance **aInstances, uint8_t aCount)
        : mLastHeader(0)
        , mLastStatus(0)
        , mLastProp(0)
        , NcpBase(aInstances, aCount)
    {
        memset(mMsgBuffer, 0, kTestBufferSize);
        mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToNcpBuffer, this);
        mTxFrameBuffer.SetFrameRemovedCallback(nullptr, this);
    };

    static void HandleFrameAddedToNcpBuffer(void                    *aContext,
                                            Spinel::Buffer::FrameTag aTag,
                                            Spinel::Buffer::Priority aPriority,
                                            Spinel::Buffer          *aBuffer)
    {
        OT_UNUSED_VARIABLE(aTag);
        OT_UNUSED_VARIABLE(aPriority);

        static_cast<TestNcp *>(aContext)->HandleFrameAddedToNcpBuffer(aBuffer);
    }

    void HandleFrameAddedToNcpBuffer(Spinel::Buffer *aBuffer)
    {
        static const size_t display_size = 64;

        memset(mMsgBuffer, 0, kTestBufferSize);
        SuccessOrQuit(aBuffer->OutFrameBegin());
        aBuffer->OutFrameRead(kTestBufferSize, mMsgBuffer);
        SuccessOrQuit(aBuffer->OutFrameRemove());

        // DumpBuffer("Received Buffer", mMsgBuffer, display_size);

        updateSpinelStatus();
    }

    void Receive(uint8_t *aBuffer, size_t bufferSize) { HandleReceive(aBuffer, static_cast<uint16_t>(bufferSize)); }

    void processTransmit()
    {
        uint8_t iid = SPINEL_HEADER_GET_IID(mLastHeader);

        LinkRawTransmitDone(iid, &sTxFrame[iid], nullptr, OT_ERROR_NONE);
    };

    void updateSpinelStatus()
    {
        Spinel::Decoder decoder;

        uint8_t      header;
        unsigned int command;
        unsigned int propKey;
        unsigned int status;

        decoder.Init(mMsgBuffer, kTestBufferSize);

        SuccessOrQuit(decoder.ReadUint8(mLastHeader));
        SuccessOrQuit(decoder.ReadUintPacked(command));
        SuccessOrQuit(decoder.ReadUintPacked(propKey));
        SuccessOrQuit(decoder.ReadUintPacked(status));

        mLastStatus = static_cast<uint32_t>(status);
        mLastProp   = static_cast<uint32_t>(propKey);
    }

    uint32_t getSpinelStatus() const { return mLastStatus; }
    uint32_t getSpinelProp() const { return mLastProp; }

    uint8_t getLastIid() const
    {
        /* Return as SPINEL_HEADER_IID_N format without shift */
        return SPINEL_HEADER_IID_MASK & mLastHeader;
    }

    uint8_t getLastTid() { return SPINEL_HEADER_GET_TID(mLastHeader); }

    bool gotResponse(uint8_t aIid, uint8_t aTid) { return ((aIid == getLastIid()) && (aTid == getLastTid())); }

private:
    uint8_t  mLastHeader;
    uint32_t mLastStatus;
    uint32_t mLastProp;
    uint8_t  mMsgBuffer[kTestBufferSize];
};

class TestHost
{
public:
    TestHost(TestNcp *aNcp, uint8_t aIid)
        : mNcp(aNcp)
        , mIid(aIid)
        , mTid(0)
        , mLastTxTid(0)
        , mBuffer(mBuf, kTestBufferSize)
        , mEncoder(mBuffer)
        , mOffset(0)
    {
        memset(mBuf, 0, kTestBufferSize);
    };

    void createLinkEnableFrame(bool isEnabled)
    {
        startFrame(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_PHY_ENABLED);
        SuccessOrQuit(mEncoder.WriteBool(isEnabled));
        endFrame("Enable Frame");
    }

    void createTransmitFrame()
    {
        startFrame(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_STREAM_RAW);

        SuccessOrQuit(mEncoder.WriteDataWithLen(sTxFrame[mIid].mPsdu, sTxFrame[mIid].mLength));
        SuccessOrQuit(mEncoder.WriteUint8(sTxFrame[mIid].mChannel));
        SuccessOrQuit(mEncoder.WriteUint8(sTxFrame[mIid].mInfo.mTxInfo.mMaxCsmaBackoffs));
        SuccessOrQuit(mEncoder.WriteUint8(sTxFrame[mIid].mInfo.mTxInfo.mMaxFrameRetries));
        SuccessOrQuit(mEncoder.WriteBool(sTxFrame[mIid].mInfo.mTxInfo.mCsmaCaEnabled));
        SuccessOrQuit(mEncoder.WriteBool(sTxFrame[mIid].mInfo.mTxInfo.mIsHeaderUpdated));
        SuccessOrQuit(mEncoder.WriteBool(sTxFrame[mIid].mInfo.mTxInfo.mIsARetx));
        SuccessOrQuit(mEncoder.WriteBool(sTxFrame[mIid].mInfo.mTxInfo.mIsSecurityProcessed));
        SuccessOrQuit(mEncoder.WriteUint32(sTxFrame[mIid].mInfo.mTxInfo.mTxDelay));
        SuccessOrQuit(mEncoder.WriteUint32(sTxFrame[mIid].mInfo.mTxInfo.mTxDelayBaseTime));

        endFrame("Transmit Frame");
    }

    void createSwitchoverRequest(uint8_t aIid, bool aForce)
    {
        startFrame(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE);
        SuccessOrQuit(mEncoder.WriteUint8(aIid | (aForce ? 0 : (1 << SPINEL_MULTIPAN_INTERFACE_SOFT_SWITCH_SHIFT))));
        endFrame("Interface Switch Request Frame");
    }

    void createReadStatusFrame()
    {
        startFrame(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_LAST_STATUS);
        endFrame("Read Status Frame");
    }

    void enableRawLink()
    {
        static const bool isLinkEnabled = true;
        createLinkEnableFrame(isLinkEnabled);
        sendToRcp();
    }

    void disableRawLink()
    {
        static const bool isLinkEnabled = false;
        createLinkEnableFrame(isLinkEnabled);
        sendToRcp();
    }

    spinel_status_t startTransmit()
    {
        mLastTxTid = mTid;
        createTransmitFrame();
        sendToRcp();
        prepareResponse(mLastTxTid);
        return static_cast<spinel_status_t>(mNcp->getSpinelStatus());
    };

    spinel_status_t requestSwitchover(uint8_t aIid, bool aForce)
    {
        mLastTxTid = mTid;
        createSwitchoverRequest(aIid, aForce);
        sendToRcp();
        prepareResponse(mLastTxTid);
        return static_cast<spinel_status_t>(mNcp->getSpinelStatus());
    };

    void getCommandStatus()
    {
        createReadStatusFrame();
        sendToRcp();
    }

    void finishTransmit()
    {
        /* Reset instance submac state to sleep by resetting link
           This is needed for a second transmit command to succeed
           as the HandleTimer method will not be called to reset the submac */
        disableRawLink();
        enableRawLink();

        /* Proceed with transmit done callback from ncp */
        mNcp->processTransmit();
    };

    uint8_t getLastTransmitTid(void) { return mLastTxTid; }

private:
    void startFrame(unsigned int aCommand, spinel_prop_key_t aKey)
    {
        uint8_t spinelHeader = SPINEL_HEADER_FLAG | mIid | mTid;

        SuccessOrQuit(mEncoder.BeginFrame(Spinel::Buffer::kPriorityLow));
        SuccessOrQuit(mEncoder.WriteUint8(spinelHeader));
        SuccessOrQuit(mEncoder.WriteUintPacked(aCommand));
        SuccessOrQuit(mEncoder.WriteUintPacked(aKey));
    }

    void endFrame(const char *aTextMessage)
    {
        static const uint16_t display_length = 64;
        SuccessOrQuit(mEncoder.EndFrame());
        // DumpBuffer(aTextMessage, mBuf, display_length);
    }

    void sendToRcp()
    {
        static const uint8_t data_offset = 2;
        size_t               frame_len   = mBuffer.OutFrameGetLength();

        mOffset += data_offset;

        mNcp->Receive(mBuf + mOffset, frame_len);

        mTid = SPINEL_GET_NEXT_TID(mTid);
        SuccessOrQuit(mBuffer.OutFrameRemove());

        mOffset += frame_len;
        mOffset %= kTestBufferSize;
    }

    void prepareResponse(uint8_t aTid)
    {
        /* Some spinel commands immediately send queued responses when command is complete
        while others require a separate command to the ncp in order to receive the response.
        If a response is needed and not immediately received. Issue a command to update the status. */

        if (!mNcp->gotResponse(mIid, aTid))
        {
            getCommandStatus();
        }
    }

    TestNcp        *mNcp;
    uint8_t         mIid;
    uint8_t         mTid;
    uint8_t         mLastTxTid;
    uint8_t         mBuf[kTestBufferSize];
    Spinel::Buffer  mBuffer;
    Spinel::Encoder mEncoder;
    size_t          mOffset;
};

void InitInstances(void)
{
#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE
    for (size_t i = 0; i < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; i++)
    {
        sInstances[i] = testInitAdditionalInstance(i);
        VerifyOrQuit(sInstances[i] != nullptr);
    }
#endif
}

void FreeInstances(void)
{
    for (size_t i = 0; i < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; i++)
    {
        if (sInstances[i] != nullptr)
        {
            testFreeInstance(sInstances[i]);
            sInstances[i] = nullptr;
        }
    }
}

void TestNcpBaseTransmitWithLinkRawDisabled(void)
{
    printf("\tTransmit With Link Raw Disabled");
    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);
    TestHost host3(&ncp, SPINEL_HEADER_IID_2);

    host1.disableRawLink();
    host2.disableRawLink();
    host3.disableRawLink();

    /* Test that the response status is Invalid State when transmit is skipped due to disabled link */
    VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_INVALID_STATE);
    VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_INVALID_STATE);
    VerifyOrQuit(host3.startTransmit() == SPINEL_STATUS_INVALID_STATE);

    FreeInstances();
    printf(" - PASS\n");
}

void TestNcpBaseTransmitWithLinkRawEnabled(void)
{
    printf("\tTransmit With Link Raw Enabled");
    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host(&ncp, SPINEL_HEADER_IID_0);

    host.enableRawLink();

    /* Test that the response status is OK when transmit is started successfully */
    VerifyOrQuit(host.startTransmit() == SPINEL_STATUS_OK);

    host.finishTransmit();

    FreeInstances();
    printf(" - PASS\n");
}

void TestNcpBaseTransmitWithIncorrectLinkRawEnabled(void)
{
    printf("\tTransmit With Incorrect Link Raw Enabled");
    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);

    host1.disableRawLink();
    host2.enableRawLink();

    /* Test that Invalid State is reported when different endpoint was enabled */
    VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_INVALID_STATE);

    /* Test that status is OK when transmitting on the proper interface */
    VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK);

    host1.finishTransmit();

    FreeInstances();
    printf(" - PASS\n");
}

void TestNcpBaseTransmitOnBoth(void)
{
    printf("\tTransmit on both interfaces");
    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);

    host1.enableRawLink();
    host2.enableRawLink();

    VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK);
    VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK);

    host1.finishTransmit();
    host2.finishTransmit();

    FreeInstances();
    printf(" - PASS\n");
}

void TestNcpBaseDifferentInstanceCall(void)
{
    printf("\tTransmit on both interfaces - verify instances used");

    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);

    sLastInstance = nullptr;

    host1.enableRawLink();
    host2.enableRawLink();

    VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK);
    VerifyOrQuit(sLastInstance != nullptr);
    VerifyOrQuit(sLastInstance == sInstances[0]);

    VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK);
    VerifyOrQuit(sLastInstance != nullptr);
    VerifyOrQuit(sLastInstance == sInstances[1]);

    host1.finishTransmit();
    host2.finishTransmit();

    /* Test reverse order of calls to make sure it is not just a fixed order */
    sLastInstance = nullptr;
    VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK);
    VerifyOrQuit(sLastInstance != nullptr);
    VerifyOrQuit(sLastInstance == sInstances[1]);

    VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK);
    VerifyOrQuit(sLastInstance != nullptr);
    VerifyOrQuit(sLastInstance == sInstances[0]);

    host1.finishTransmit();
    host2.finishTransmit();

    printf(" - PASS\n");
}

void TestNcpBaseTransmitDoneInterface(void)
{
    printf("\tTransmit on both interfaces - verify transmit done IID");

    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);

    sLastInstance = nullptr;

    host1.enableRawLink();
    host2.enableRawLink();

    VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK);
    VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK);

    otPlatRadioTxDone(sInstances[0], &sTxFrame[0], nullptr, OT_ERROR_NONE);
    VerifyOrQuit(ncp.gotResponse(SPINEL_HEADER_IID_0, host1.getLastTransmitTid()));

    otPlatRadioTxDone(sInstances[1], &sTxFrame[1], nullptr, OT_ERROR_NONE);
    VerifyOrQuit(ncp.gotResponse(SPINEL_HEADER_IID_1, host2.getLastTransmitTid()));

    /* Test reverse order of tx processing */
    VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK);
    VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK);

    otPlatRadioTxDone(sInstances[1], &sTxFrame[1], nullptr, OT_ERROR_NONE);
    VerifyOrQuit(ncp.gotResponse(SPINEL_HEADER_IID_1, host2.getLastTransmitTid()));

    otPlatRadioTxDone(sInstances[0], &sTxFrame[0], nullptr, OT_ERROR_NONE);
    VerifyOrQuit(ncp.gotResponse(SPINEL_HEADER_IID_0, host1.getLastTransmitTid()));

    printf(" - PASS\n");
}

void TestNcpBaseReceive(void)
{
    printf("\tReceive on a single interface");

    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);

    host1.enableRawLink();

    otPlatRadioReceiveDone(sInstances[0], &sTxFrame[0], OT_ERROR_NONE);

    VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW);
    VerifyOrQuit(ncp.getLastTid() == 0);
    VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_0);

    printf(" - PASS\n");
}

void TestNcpBaseReceiveOnTwoInterfaces(void)
{
    printf("\tReceive on both interfaces");

    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);

    sLastInstance = nullptr;

    host1.enableRawLink();
    host2.enableRawLink();

    otPlatRadioReceiveDone(sInstances[1], &sTxFrame[1], OT_ERROR_NONE);

    VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW);
    VerifyOrQuit(ncp.getLastTid() == 0);
    VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_1);

    otPlatRadioReceiveDone(sInstances[0], &sTxFrame[0], OT_ERROR_NONE);

    VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW);
    VerifyOrQuit(ncp.getLastTid() == 0);
    VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_0);

    /* reverse order */
    otPlatRadioReceiveDone(sInstances[0], &sTxFrame[0], OT_ERROR_NONE);

    VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW);
    VerifyOrQuit(ncp.getLastTid() == 0);
    VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_0);

    otPlatRadioReceiveDone(sInstances[1], &sTxFrame[1], OT_ERROR_NONE);

    VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW);
    VerifyOrQuit(ncp.getLastTid() == 0);
    VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_1);

    printf(" - PASS\n");
}

void TestNcpBaseSwitchoverRequest(void)
{
    printf("\tSwitchover requests from different interfaces");

    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);
    TestHost host3(&ncp, SPINEL_HEADER_IID_2);

    sLastInstance = nullptr;

    host1.enableRawLink();
    host2.enableRawLink();
    host3.enableRawLink();

    VerifyOrQuit(host1.requestSwitchover(0, true) == 0);
    VerifyOrQuit(sLastInstance == sInstances[0]);
    VerifyOrQuit(host1.requestSwitchover(1, true) == 1);
    VerifyOrQuit(sLastInstance == sInstances[1]);
    VerifyOrQuit(host1.requestSwitchover(2, true) == 2);
    VerifyOrQuit(sLastInstance == sInstances[2]);
    VerifyOrQuit(host2.requestSwitchover(0, true) == 0);
    VerifyOrQuit(sLastInstance == sInstances[0]);
    VerifyOrQuit(host2.requestSwitchover(1, true) == 1);
    VerifyOrQuit(sLastInstance == sInstances[1]);
    VerifyOrQuit(host2.requestSwitchover(2, true) == 2);
    VerifyOrQuit(sLastInstance == sInstances[2]);
    VerifyOrQuit(host3.requestSwitchover(0, true) == 0);
    VerifyOrQuit(sLastInstance == sInstances[0]);
    VerifyOrQuit(host3.requestSwitchover(1, true) == 1);
    VerifyOrQuit(sLastInstance == sInstances[1]);
    VerifyOrQuit(host3.requestSwitchover(2, true) == 2);
    VerifyOrQuit(sLastInstance == sInstances[2]);

    printf(" - PASS\n");
}

void TestNcpBaseSwitchoverRequestFail(void)
{
    printf("\tSwitchover requests Fail - same interface");

    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);

    sLastInstance = nullptr;

    host1.enableRawLink();
    host2.enableRawLink();

    VerifyOrQuit(host1.requestSwitchover(0, true) == 0);
    VerifyOrQuit(sLastInstance == sInstances[0]);

    VerifyOrQuit(host1.requestSwitchover(0, true) == SPINEL_STATUS_ALREADY);
    VerifyOrQuit(sLastInstance == sInstances[0]);

    VerifyOrQuit(host2.requestSwitchover(0, true) == SPINEL_STATUS_ALREADY);
    VerifyOrQuit(sLastInstance == sInstances[0]);

    printf(" - PASS\n");
}

void TestNcpBaseSwitchoverResponse(void)
{
    printf("\tSwitchover responses");

    InitInstances();

    TestNcp  ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    TestHost host1(&ncp, SPINEL_HEADER_IID_0);
    TestHost host2(&ncp, SPINEL_HEADER_IID_1);

    sLastInstance = nullptr;

    host1.enableRawLink();
    host2.enableRawLink();

    VerifyOrQuit(host1.requestSwitchover(0, true) == 0);
    VerifyOrQuit(sLastInstance == sInstances[0]);

    otPlatMultipanSwitchoverDone(sLastInstance, true);

    VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_LAST_STATUS);
    VerifyOrQuit(ncp.getLastTid() == 0);
    VerifyOrQuit(ncp.getLastIid() == OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID);
    VerifyOrQuit(ncp.getSpinelStatus() == SPINEL_STATUS_SWITCHOVER_DONE);

    VerifyOrQuit(host1.requestSwitchover(1, true) == 1);
    VerifyOrQuit(sLastInstance == sInstances[1]);

    otPlatMultipanSwitchoverDone(sLastInstance, false);

    VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_LAST_STATUS);
    VerifyOrQuit(ncp.getLastTid() == 0);
    VerifyOrQuit(ncp.getLastIid() == OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID);
    VerifyOrQuit(ncp.getSpinelStatus() == SPINEL_STATUS_SWITCHOVER_FAILED);

    printf(" - PASS\n");
}

///
int main(void)
{
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && (OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE)
    printf("Executing Transmit Tests\n");
    TestNcpBaseTransmitWithLinkRawDisabled();
    TestNcpBaseTransmitWithLinkRawEnabled();
    TestNcpBaseTransmitWithIncorrectLinkRawEnabled();
    TestNcpBaseTransmitOnBoth();
    TestNcpBaseDifferentInstanceCall();
    TestNcpBaseTransmitDoneInterface();
    printf("Transmit Tests - PASS\n");

    printf("Executing Receive Tests\n");
    TestNcpBaseReceive();
    TestNcpBaseReceiveOnTwoInterfaces();
    printf("Receive Tests - PASS\n");

    printf("Executing Interface Switching Tests\n");
    TestNcpBaseSwitchoverRequest();
    TestNcpBaseSwitchoverRequestFail();
    TestNcpBaseSwitchoverResponse();
    printf("Executing Interface Switching Tests - PASS\n");

    printf("\nAll tests passed\n");

#else
    printf("MULTIPAN_RCP feature and RADIO/LINK_RAW option are not enabled\n");
#endif
    return 0;
}
