/*
 *  Copyright (c) 2019, 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.
 */

/**
 * @file
 *   This file implements a simple CLI for the Commissioner role.
 */

#include "cli_commissioner.hpp"

#include "cli/cli.hpp"

#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD

namespace ot {
namespace Cli {

/**
 * @cli commissioner announce
 * @code
 * commissioner announce 0x00050000 2 32 fdde:ad00:beef:0:0:ff:fe00:c00
 * Done
 * @endcode
 * @cparam commissioner announce @ca{mask} @ca{count} @ca{period} @ca{destination}
 *   * `mask`: Bitmask that identifies channels for sending MLE `Announce` messages.
 *   * `count`: Number of MLE `Announce` transmissions per channel.
 *   * `period`: Number of milliseconds between successive MLE `Announce` transmissions.
 *   * `destination`: Destination IPv6 address for the message. The message may be multicast.
 * @par
 * Sends an Announce Begin message.
 * @note Use this command only after successfully starting the %Commissioner role
 * with the `commissioner start` command.
 * @csa{commissioner start}
 * @sa otCommissionerAnnounceBegin
 */
template <> otError Commissioner::Process<Cmd("announce")>(Arg aArgs[])
{
    otError      error;
    uint32_t     mask;
    uint8_t      count;
    uint16_t     period;
    otIp6Address address;

    SuccessOrExit(error = aArgs[0].ParseAsUint32(mask));
    SuccessOrExit(error = aArgs[1].ParseAsUint8(count));
    SuccessOrExit(error = aArgs[2].ParseAsUint16(period));
    SuccessOrExit(error = aArgs[3].ParseAsIp6Address(address));

    error = otCommissionerAnnounceBegin(GetInstancePtr(), mask, count, period, &address);

exit:
    return error;
}

/**
 * @cli commissioner energy
 * @code
 * commissioner energy 0x00050000 2 32 1000 fdde:ad00:beef:0:0:ff:fe00:c00
 * Done
 * Energy: 00050000 0 0 0 0
 * @endcode
 * @cparam commissioner energy @ca{mask} @ca{count} @ca{period} @ca{scanDuration} @ca{destination}
 *   * `mask`: Bitmask that identifies channels for performing IEEE 802.15.4 energy scans.
 *   * `count`: Number of IEEE 802.15.4 energy scans per channel.
 *   * `period`: Number of milliseconds between successive IEEE 802.15.4 energy scans.
 *   * `scanDuration`: Scan duration in milliseconds to use when
 *     performing an IEEE 802.15.4 energy scan.
 *   * `destination`: Destination IPv6 address for the message. The message may be multicast.
 * @par
 * Sends an Energy Scan Query message. Command output is printed as it is received.
 * @note Use this command only after successfully starting the %Commissioner role
 * with the `commissioner start` command.
 * @csa{commissioner start}
 * @sa otCommissionerEnergyScan
 */
template <> otError Commissioner::Process<Cmd("energy")>(Arg aArgs[])
{
    otError      error;
    uint32_t     mask;
    uint8_t      count;
    uint16_t     period;
    uint16_t     scanDuration;
    otIp6Address address;

    SuccessOrExit(error = aArgs[0].ParseAsUint32(mask));
    SuccessOrExit(error = aArgs[1].ParseAsUint8(count));
    SuccessOrExit(error = aArgs[2].ParseAsUint16(period));
    SuccessOrExit(error = aArgs[3].ParseAsUint16(scanDuration));
    SuccessOrExit(error = aArgs[4].ParseAsIp6Address(address));

    error = otCommissionerEnergyScan(GetInstancePtr(), mask, count, period, scanDuration, &address,
                                     &Commissioner::HandleEnergyReport, this);

exit:
    return error;
}

template <> otError Commissioner::Process<Cmd("joiner")>(Arg aArgs[])
{
    otError             error = OT_ERROR_NONE;
    otExtAddress        addr;
    const otExtAddress *addrPtr = nullptr;
    otJoinerDiscerner   discerner;

    /**
     * @cli commissioner joiner table
     * @code
     * commissioner joiner table
     * | ID                    | PSKd                             | Expiration |
     * +-----------------------+----------------------------------+------------+
     * |                     * |                           J01NME |      81015 |
     * |      d45e64fa83f81cf7 |                           J01NME |     101204 |
     * | 0x0000000000000abc/12 |                           J01NME |     114360 |
     * Done
     * @endcode
     * @par
     * Lists all %Joiner entries in table format.
     */
    if (aArgs[0] == "table")
    {
        uint16_t     iter = 0;
        otJoinerInfo joinerInfo;

        static const char *const kJoinerTableTitles[] = {"ID", "PSKd", "Expiration"};

        static const uint8_t kJoinerTableColumnWidths[] = {
            23,
            34,
            12,
        };

        OutputTableHeader(kJoinerTableTitles, kJoinerTableColumnWidths);

        while (otCommissionerGetNextJoinerInfo(GetInstancePtr(), &iter, &joinerInfo) == OT_ERROR_NONE)
        {
            switch (joinerInfo.mType)
            {
            case OT_JOINER_INFO_TYPE_ANY:
                OutputFormat("| %21s", "*");
                break;

            case OT_JOINER_INFO_TYPE_EUI64:
                OutputFormat("|      ");
                OutputExtAddress(joinerInfo.mSharedId.mEui64);
                break;

            case OT_JOINER_INFO_TYPE_DISCERNER:
                OutputFormat("| 0x%08lx%08lx/%2u",
                             static_cast<unsigned long>(joinerInfo.mSharedId.mDiscerner.mValue >> 32),
                             static_cast<unsigned long>(joinerInfo.mSharedId.mDiscerner.mValue & 0xffffffff),
                             joinerInfo.mSharedId.mDiscerner.mLength);
                break;
            }

            OutputFormat(" | %32s | %10lu |", joinerInfo.mPskd.m8, ToUlong(joinerInfo.mExpirationTime));
            OutputNewLine();
        }

        ExitNow(error = OT_ERROR_NONE);
    }

    VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);

    ClearAllBytes(discerner);

    if (aArgs[1] == "*")
    {
        // Intentionally empty
    }
    else
    {
        error = ParseJoinerDiscerner(aArgs[1], discerner);

        if (error == OT_ERROR_NOT_FOUND)
        {
            error   = aArgs[1].ParseAsHexString(addr.m8);
            addrPtr = &addr;
        }

        SuccessOrExit(error);
    }

    /**
     * @cli commissioner joiner add
     * @code
     * commissioner joiner add d45e64fa83f81cf7 J01NME
     * Done
     * @endcode
     * @code
     * commissioner joiner add 0xabc/12 J01NME
     * Done
     * @endcode
     * @cparam commissioner joiner add @ca{eui64}|@ca{discerner pksd} [@ca{timeout}]
     *   * `eui64`: IEEE EUI-64 of the %Joiner. To match any joiner, use `*`.
     *   * `discerner`: The %Joiner discerner in the format `number/length`.
     *   * `pksd`: Pre-Shared Key for the joiner.
     *   * `timeout`: The %Joiner timeout in seconds.
     * @par
     * Adds a joiner entry.
     * @note Use this command only after successfully starting the %Commissioner role
     * with the `commissioner start` command.
     * @csa{commissioner start}
     * @sa otCommissionerAddJoiner
     * @sa otCommissionerAddJoinerWithDiscerner
     */
    if (aArgs[0] == "add")
    {
        uint32_t timeout = kDefaultJoinerTimeout;

        VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);

        if (!aArgs[3].IsEmpty())
        {
            SuccessOrExit(error = aArgs[3].ParseAsUint32(timeout));
        }

        if (discerner.mLength)
        {
            error = otCommissionerAddJoinerWithDiscerner(GetInstancePtr(), &discerner, aArgs[2].GetCString(), timeout);
        }
        else
        {
            error = otCommissionerAddJoiner(GetInstancePtr(), addrPtr, aArgs[2].GetCString(), timeout);
        }
        /**
         * @cli commissioner joiner remove
         * @code
         * commissioner joiner remove d45e64fa83f81cf7
         * Done
         * @endcode
         * @code
         * commissioner joiner remove 0xabc/12
         * Done
         * @endcode
         * @cparam commissioner joiner remove @ca{eui64}|@ca{discerner}
         *   * `eui64`: IEEE EUI-64 of the joiner. To match any joiner, use `*`.
         *   * `discerner`: The joiner discerner in the format `number/length`.
         * @par
         * Removes a %Joiner entry.
         * @note Use this command only after successfully starting the %Commissioner role
         * with the `commissioner start` command.
         * @csa{commissioner start}
         * @sa otCommissionerRemoveJoiner
         * @sa otCommissionerRemoveJoinerWithDiscerner
         */
    }
    else if (aArgs[0] == "remove")
    {
        if (discerner.mLength)
        {
            error = otCommissionerRemoveJoinerWithDiscerner(GetInstancePtr(), &discerner);
        }
        else
        {
            error = otCommissionerRemoveJoiner(GetInstancePtr(), addrPtr);
        }
    }
    else
    {
        error = OT_ERROR_INVALID_ARGS;
    }

exit:
    return error;
}

/**
 * @cli commissioner mgmtget
 * @code
 * commissioner mgmtget locator sessionid
 * Done
 * @endcode
 * @cparam commissioner mgmtget [locator] [sessionid] <!--
 * -->                          [steeringdata] [joinerudpport] <!--
 * -->                          [-x @ca{TLVs}]
 *   * `locator`: Border Router RLOC16.
 *   * `sessionid`: Session ID of the %Commissioner.
 *   * `steeringdata`: Steering data.
 *   * `joinerudpport`: %Joiner UDP port.
 *   * `TLVs`: The set of TLVs to be retrieved.
 * @par
 * Sends a `MGMT_GET` (Management Get) message to the Leader.
 * Variable values that have been set using the `commissioner mgmtset` command are returned.
 * @sa otCommissionerSendMgmtGet
 */
template <> otError Commissioner::Process<Cmd("mgmtget")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;
    uint8_t tlvs[32];
    uint8_t length = 0;

    for (; !aArgs->IsEmpty(); aArgs++)
    {
        VerifyOrExit(static_cast<size_t>(length) < sizeof(tlvs), error = OT_ERROR_NO_BUFS);

        if (*aArgs == "locator")
        {
            tlvs[length++] = OT_MESHCOP_TLV_BORDER_AGENT_RLOC;
        }
        else if (*aArgs == "sessionid")
        {
            tlvs[length++] = OT_MESHCOP_TLV_COMM_SESSION_ID;
        }
        else if (*aArgs == "steeringdata")
        {
            tlvs[length++] = OT_MESHCOP_TLV_STEERING_DATA;
        }
        else if (*aArgs == "joinerudpport")
        {
            tlvs[length++] = OT_MESHCOP_TLV_JOINER_UDP_PORT;
        }
        else if (*aArgs == "-x")
        {
            uint16_t readLength;

            aArgs++;
            readLength = static_cast<uint16_t>(sizeof(tlvs) - length);
            SuccessOrExit(error = aArgs->ParseAsHexString(readLength, tlvs + length));
            length += static_cast<uint8_t>(readLength);
        }
        else
        {
            ExitNow(error = OT_ERROR_INVALID_ARGS);
        }
    }

    error = otCommissionerSendMgmtGet(GetInstancePtr(), tlvs, static_cast<uint8_t>(length));

exit:
    return error;
}

/**
 * @cli commissioner mgmtset
 * @code
 * commissioner mgmtset joinerudpport 9988
 * Done
 * @endcode
 * @cparam commissioner mgmtset [locator @ca{locator}] [sessionid @ca{sessionid}] <!--
 * -->                          [steeringdata @ca{steeringdata}] [joinerudpport @ca{joinerudpport}] <!--
 * -->                          [-x @ca{TLVs}]
 *   * `locator`: Border Router RLOC16.
 *   * `sessionid`: Session ID of the %Commissioner.
 *   * `steeringdata`: Steering data.
 *   * `joinerudpport`: %Joiner UDP port.
 *   * `TLVs`: The set of TLVs to be retrieved.
 * @par
 * Sends a `MGMT_SET` (Management Set) message to the Leader, and sets the
 * variables to the values specified.
 * @sa otCommissionerSendMgmtSet
 */
template <> otError Commissioner::Process<Cmd("mgmtset")>(Arg aArgs[])
{
    otError                error;
    otCommissioningDataset dataset;
    uint8_t                tlvs[32];
    uint8_t                tlvsLength = 0;

    VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);

    ClearAllBytes(dataset);

    for (; !aArgs->IsEmpty(); aArgs++)
    {
        if (*aArgs == "locator")
        {
            aArgs++;
            dataset.mIsLocatorSet = true;
            SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mLocator));
        }
        else if (*aArgs == "sessionid")
        {
            aArgs++;
            dataset.mIsSessionIdSet = true;
            SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mSessionId));
        }
        else if (*aArgs == "steeringdata")
        {
            uint16_t length;

            aArgs++;
            dataset.mIsSteeringDataSet = true;
            length                     = sizeof(dataset.mSteeringData.m8);
            SuccessOrExit(error = aArgs->ParseAsHexString(length, dataset.mSteeringData.m8));
            dataset.mSteeringData.mLength = static_cast<uint8_t>(length);
        }
        else if (*aArgs == "joinerudpport")
        {
            aArgs++;
            dataset.mIsJoinerUdpPortSet = true;
            SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mJoinerUdpPort));
        }
        else if (*aArgs == "-x")
        {
            uint16_t length;

            aArgs++;
            length = sizeof(tlvs);
            SuccessOrExit(error = aArgs->ParseAsHexString(length, tlvs));
            tlvsLength = static_cast<uint8_t>(length);
        }
        else
        {
            ExitNow(error = OT_ERROR_INVALID_ARGS);
        }
    }

    error = otCommissionerSendMgmtSet(GetInstancePtr(), &dataset, tlvs, tlvsLength);

exit:
    return error;
}

/**
 * @cli commissioner panid
 * @code
 * commissioner panid 0xdead 0x7fff800 fdde:ad00:beef:0:0:ff:fe00:c00
 * Done
 * Conflict: dead, 00000800
 * @endcode
 * @cparam commissioner panid @ca{panid} @ca{mask} @ca{destination}
 *   * `paind`: PAN ID to use to check for conflicts.
 *   * `mask`; Bitmask that identifies channels to perform IEEE 802.15.4
 *     Active Scans.
 *   * `destination`: IPv6 destination address for the message. The message may be multicast.
 * @par
 * Sends a PAN ID query. Command output is returned as it is received.
 * @note Use this command only after successfully starting the %Commissioner role
 * with the `commissioner start` command.
 * @csa{commissioner start}
 * @sa otCommissionerPanIdQuery
 */
template <> otError Commissioner::Process<Cmd("panid")>(Arg aArgs[])
{
    otError      error;
    uint16_t     panId;
    uint32_t     mask;
    otIp6Address address;

    SuccessOrExit(error = aArgs[0].ParseAsUint16(panId));
    SuccessOrExit(error = aArgs[1].ParseAsUint32(mask));
    SuccessOrExit(error = aArgs[2].ParseAsIp6Address(address));

    error = otCommissionerPanIdQuery(GetInstancePtr(), panId, mask, &address, &Commissioner::HandlePanIdConflict, this);

exit:
    return error;
}

/**
 * @cli commissioner provisioningurl
 * @code
 * commissioner provisioningurl http://github.com/openthread/openthread
 * Done
 * @endcode
 * @cparam commissioner provisioningurl @ca{provisioningurl}
 * @par
 * Sets the %Commissioner provisioning URL.
 * @sa otCommissionerSetProvisioningUrl
 */
template <> otError Commissioner::Process<Cmd("provisioningurl")>(Arg aArgs[])
{
    // If aArgs[0] is empty, `GetCString() will return `nullptr`
    /// which will correctly clear the provisioning URL.
    return otCommissionerSetProvisioningUrl(GetInstancePtr(), aArgs[0].GetCString());
}

/**
 * @cli commissioner sessionid
 * @code
 * commissioner sessionid
 * 0
 * Done
 * @endcode
 * @par
 * Gets the current %Commissioner session ID.
 * @sa otCommissionerGetSessionId
 */
template <> otError Commissioner::Process<Cmd("sessionid")>(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    OutputLine("%d", otCommissionerGetSessionId(GetInstancePtr()));

    return OT_ERROR_NONE;
}

/**
 * @cli commissioner id (get,set)
 * @code
 * commissioner id OpenThread Commissioner
 * Done
 * @endcode
 * @code
 * commissioner id
 * OpenThread Commissioner
 * Done
 * @endcode
 * @cparam commissioner id @ca{name}
 * @par
 * Gets or sets the OpenThread %Commissioner ID name.
 * @sa otCommissionerSetId
 */
template <> otError Commissioner::Process<Cmd("id")>(Arg aArgs[])
{
    otError error;

    if (aArgs[0].IsEmpty())
    {
        OutputLine("%s", otCommissionerGetId(GetInstancePtr()));
        error = OT_ERROR_NONE;
    }
    else
    {
        error = otCommissionerSetId(GetInstancePtr(), aArgs[0].GetCString());
    }

    return error;
}

/**
 * @cli commissioner start
 * @code
 * commissioner start
 * Commissioner: petitioning
 * Done
 * Commissioner: active
 * @endcode
 * @par
 * Starts the Thread %Commissioner role.
 * @note The `commissioner` commands are available only when
 * `OPENTHREAD_CONFIG_COMMISSIONER_ENABLE` and `OPENTHREAD_FTD` are set.
 * @sa otCommissionerStart
 */
template <> otError Commissioner::Process<Cmd("start")>(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    return otCommissionerStart(GetInstancePtr(), &Commissioner::HandleStateChanged, &Commissioner::HandleJoinerEvent,
                               this);
}

void Commissioner::HandleStateChanged(otCommissionerState aState, void *aContext)
{
    static_cast<Commissioner *>(aContext)->HandleStateChanged(aState);
}

void Commissioner::HandleStateChanged(otCommissionerState aState)
{
    OutputLine("Commissioner: %s", StateToString(aState));
}

const char *Commissioner::StateToString(otCommissionerState aState)
{
    static const char *const kStateString[] = {
        "disabled",    // (0) OT_COMMISSIONER_STATE_DISABLED
        "petitioning", // (1) OT_COMMISSIONER_STATE_PETITION
        "active",      // (2) OT_COMMISSIONER_STATE_ACTIVE
    };

    static_assert(0 == OT_COMMISSIONER_STATE_DISABLED, "OT_COMMISSIONER_STATE_DISABLED value is incorrect");
    static_assert(1 == OT_COMMISSIONER_STATE_PETITION, "OT_COMMISSIONER_STATE_PETITION value is incorrect");
    static_assert(2 == OT_COMMISSIONER_STATE_ACTIVE, "OT_COMMISSIONER_STATE_ACTIVE value is incorrect");

    return Stringify(aState, kStateString);
}

void Commissioner::HandleJoinerEvent(otCommissionerJoinerEvent aEvent,
                                     const otJoinerInfo       *aJoinerInfo,
                                     const otExtAddress       *aJoinerId,
                                     void                     *aContext)
{
    static_cast<Commissioner *>(aContext)->HandleJoinerEvent(aEvent, aJoinerInfo, aJoinerId);
}

void Commissioner::HandleJoinerEvent(otCommissionerJoinerEvent aEvent,
                                     const otJoinerInfo       *aJoinerInfo,
                                     const otExtAddress       *aJoinerId)
{
    static const char *const kEventStrings[] = {
        "start",    // (0) OT_COMMISSIONER_JOINER_START
        "connect",  // (1) OT_COMMISSIONER_JOINER_CONNECTED
        "finalize", // (2) OT_COMMISSIONER_JOINER_FINALIZE
        "end",      // (3) OT_COMMISSIONER_JOINER_END
        "remove",   // (4) OT_COMMISSIONER_JOINER_REMOVED
    };

    static_assert(0 == OT_COMMISSIONER_JOINER_START, "OT_COMMISSIONER_JOINER_START value is incorrect");
    static_assert(1 == OT_COMMISSIONER_JOINER_CONNECTED, "OT_COMMISSIONER_JOINER_CONNECTED value is incorrect");
    static_assert(2 == OT_COMMISSIONER_JOINER_FINALIZE, "OT_COMMISSIONER_JOINER_FINALIZE value is incorrect");
    static_assert(3 == OT_COMMISSIONER_JOINER_END, "OT_COMMISSIONER_JOINER_END value is incorrect");
    static_assert(4 == OT_COMMISSIONER_JOINER_REMOVED, "OT_COMMISSIONER_JOINER_REMOVED value is incorrect");

    OT_UNUSED_VARIABLE(aJoinerInfo);

    OutputFormat("Commissioner: Joiner %s ", Stringify(aEvent, kEventStrings));

    if (aJoinerId != nullptr)
    {
        OutputExtAddress(*aJoinerId);
    }

    OutputNewLine();
}

/**
 * @cli commissioner stop
 * @code
 * commissioner stop
 * Done
 * @endcode
 * @par
 * Stops the Thread %Commissioner role.
 * @sa otCommissionerStop
 */
template <> otError Commissioner::Process<Cmd("stop")>(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    return otCommissionerStop(GetInstancePtr());
}

/**
 * @cli commissioner state
 * @code
 * commissioner state
 * active
 * Done
 * @endcode
 * @par
 * Returns the current state of the %Commissioner. Possible values are
 * `active`, `disabled`, or `petition` (petitioning to become %Commissioner).
 * @sa otCommissionerState
 */
template <> otError Commissioner::Process<Cmd("state")>(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    OutputLine("%s", StateToString(otCommissionerGetState(GetInstancePtr())));

    return OT_ERROR_NONE;
}

otError Commissioner::Process(Arg aArgs[])
{
#define CmdEntry(aCommandString)                                    \
    {                                                               \
        aCommandString, &Commissioner::Process<Cmd(aCommandString)> \
    }

    static constexpr Command kCommands[] = {
        CmdEntry("announce"),  CmdEntry("energy"),  CmdEntry("id"),    CmdEntry("joiner"),
        CmdEntry("mgmtget"),   CmdEntry("mgmtset"), CmdEntry("panid"), CmdEntry("provisioningurl"),
        CmdEntry("sessionid"), CmdEntry("start"),   CmdEntry("state"), CmdEntry("stop"),
    };

#undef CmdEntry

    static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");

    otError        error = OT_ERROR_INVALID_COMMAND;
    const Command *command;

    if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
    {
        OutputCommandTable(kCommands);
        ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
    }

    command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
    VerifyOrExit(command != nullptr);

    error = (this->*command->mHandler)(aArgs + 1);

exit:
    return error;
}

void Commissioner::HandleEnergyReport(uint32_t       aChannelMask,
                                      const uint8_t *aEnergyList,
                                      uint8_t        aEnergyListLength,
                                      void          *aContext)
{
    static_cast<Commissioner *>(aContext)->HandleEnergyReport(aChannelMask, aEnergyList, aEnergyListLength);
}

void Commissioner::HandleEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyList, uint8_t aEnergyListLength)
{
    OutputFormat("Energy: %08lx ", ToUlong(aChannelMask));

    for (uint8_t i = 0; i < aEnergyListLength; i++)
    {
        OutputFormat("%d ", static_cast<int8_t>(aEnergyList[i]));
    }

    OutputNewLine();
}

void Commissioner::HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask, void *aContext)
{
    static_cast<Commissioner *>(aContext)->HandlePanIdConflict(aPanId, aChannelMask);
}

void Commissioner::HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask)
{
    OutputLine("Conflict: %04x, %08lx", aPanId, ToUlong(aChannelMask));
}

} // namespace Cli
} // namespace ot

#endif // OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
