/*
 *  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.
 */

/**
 * @file
 *   This file implements CLI for Backbone Router.
 */

#include "cli_bbr.hpp"

#include "cli/cli.hpp"

#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)

namespace ot {
namespace Cli {

void Bbr::OutputConfig(const otBackboneRouterConfig &aConfig)
{
    OutputLine("seqno:    %u", aConfig.mSequenceNumber);
    OutputLine("delay:    %u secs", aConfig.mReregistrationDelay);
    OutputLine("timeout:  %lu secs", ToUlong(aConfig.mMlrTimeout));
}

#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE

#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE

template <> otError Bbr::Process<Cmd("mlr")>(Arg aArgs[])
{
    otError error = OT_ERROR_INVALID_COMMAND;

    /**
     * @cli bbr mgmt mlr listener
     * @code
     * bbr mgmt mlr listener
     * ff04:0:0:0:0:0:0:abcd 3534000
     * ff04:0:0:0:0:0:0:eeee 3537610
     * Done
     * @endcode
     * @par
     * Returns the Multicast Listeners with the #otBackboneRouterMulticastListenerInfo
     * `mTimeout` in seconds.
     * @par
     * Available when `OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE` and
     * `OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE` are enabled.
     * @sa otBackboneRouterMulticastListenerGetNext
     */
    if (aArgs[0] == "listener")
    {
        if (aArgs[1].IsEmpty())
        {
            otBackboneRouterMulticastListenerIterator iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT;
            otBackboneRouterMulticastListenerInfo     listenerInfo;

            while (otBackboneRouterMulticastListenerGetNext(GetInstancePtr(), &iter, &listenerInfo) == OT_ERROR_NONE)
            {
                OutputIp6Address(listenerInfo.mAddress);
                OutputLine(" %lu", ToUlong(listenerInfo.mTimeout));
            }

            ExitNow(error = OT_ERROR_NONE);
        }

#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
        /**
         * @cli bbr mgmt mlr listener clear
         * @code
         * bbr mgmt mlr listener clear
         * Done
         * @endcode
         * @par api_copy
         * #otBackboneRouterMulticastListenerClear
         */
        if (aArgs[1] == "clear")
        {
            otBackboneRouterMulticastListenerClear(GetInstancePtr());
            error = OT_ERROR_NONE;
        }
        /**
         * @cli bbr mgmt mlr listener add
         * @code
         * bbr mgmt mlr listener add ff04::1
         * Done
         * @endcode
         * @code
         * bbr mgmt mlr listener add ff04::2 300
         * Done
         * @endcode
         * @cparam bbr mgmt mlr listener add @ca{ipaddress} [@ca{timeout-seconds}]
         * @par api_copy
         * #otBackboneRouterMulticastListenerAdd
         */
        else if (aArgs[1] == "add")
        {
            otIp6Address address;
            uint32_t     timeout = 0;

            SuccessOrExit(error = aArgs[2].ParseAsIp6Address(address));

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

            error = otBackboneRouterMulticastListenerAdd(GetInstancePtr(), &address, timeout);
        }
    }
    /**
     * @cli bbr mgmt mlr response
     * @code
     * bbr mgmt mlr response 2
     * Done
     * @endcode
     * @cparam bbr mgmt mlr response @ca{status-code}
     * For `status-code`, use:
     * *    0: ST_MLR_SUCCESS
     * *    2: ST_MLR_INVALID
     * *    3: ST_MLR_NO_PERSISTENT
     * *    4: ST_MLR_NO_RESOURCES
     * *    5: ST_MLR_BBR_NOT_PRIMARY
     * *    6: ST_MLR_GENERAL_FAILURE
     * @par api_copy
     * #otBackboneRouterConfigNextMulticastListenerRegistrationResponse
     */
    else if (aArgs[0] == "response")
    {
        uint8_t status;

        SuccessOrExit(error = aArgs[1].ParseAsUint8(status));
        otBackboneRouterConfigNextMulticastListenerRegistrationResponse(GetInstancePtr(), status);
        error = OT_ERROR_NONE;

#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
    }

exit:
    return error;
}

#endif // #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE

template <> otError Bbr::Process<Cmd("mgmt")>(Arg aArgs[])
{
    otError error = OT_ERROR_INVALID_COMMAND;

    if (aArgs[0].IsEmpty())
    {
        ExitNow(error = OT_ERROR_INVALID_COMMAND);
    }

#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
    if (aArgs[0] == "mlr")
    {
        error = Process<Cmd("mlr")>(aArgs + 1);
        ExitNow();
    }
#endif

exit:
    return error;
}

/**
 * @cli bbr enable
 * @code
 * bbr enable
 * Done
 * @endcode
 * @par api_copy
 * #otBackboneRouterSetEnabled
 */
template <> otError Bbr::Process<Cmd("enable")>(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);
    otBackboneRouterSetEnabled(GetInstancePtr(), true);

    return OT_ERROR_NONE;
}

/**
 * @cli bbr disable
 * @code
 * bbr disable
 * Done
 * @endcode
 * @par api_copy
 * #otBackboneRouterSetEnabled
 */
template <> otError Bbr::Process<Cmd("disable")>(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);
    otBackboneRouterSetEnabled(GetInstancePtr(), false);

    return OT_ERROR_NONE;
}

/**
 * @cli bbr jitter (get,set)
 * @code
 * bbr jitter
 * 20
 * Done
 * @endcode
 * @code
 * bbr jitter 10
 * Done
 * @endcode
 * @cparam bbr jitter [@ca{jitter}]
 * @par
 * Gets or sets jitter (in seconds) for Backbone Router registration.
 * @par
 * Available when `OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE` is enabled.
 * @sa otBackboneRouterGetRegistrationJitter
 * @sa otBackboneRouterSetRegistrationJitter
 */
template <> otError Bbr::Process<Cmd("jitter")>(Arg aArgs[])
{
    return ProcessGetSet(aArgs, otBackboneRouterGetRegistrationJitter, otBackboneRouterSetRegistrationJitter);
}

/**
 * @cli bbr register
 * @code
 * bbr register
 * Done
 * @endcode
 * @par api_copy
 * #otBackboneRouterRegister
 */
template <> otError Bbr::Process<Cmd("register")>(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    return otBackboneRouterRegister(GetInstancePtr());
}

/**
 * @cli bbr state
 * @code
 * bbr state
 * Disabled
 * Done
 * @endcode
 * @code
 * bbr state
 * Primary
 * Done
 * @endcode
 * @code
 * bbr state
 * Secondary
 * Done
 * @endcode
 * @par
 * Available when `OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE` is enabled.
 * @par api_copy
 * #otBackboneRouterGetState
 */
template <> otError Bbr::Process<Cmd("state")>(Arg aArgs[])

{
    static const char *const kStateStrings[] = {
        "Disabled",  // (0) OT_BACKBONE_ROUTER_STATE_DISABLED
        "Secondary", // (1) OT_BACKBONE_ROUTER_STATE_SECONDARY
        "Primary",   // (2) OT_BACKBONE_ROUTER_STATE_PRIMARY
    };

    static_assert(0 == OT_BACKBONE_ROUTER_STATE_DISABLED, "OT_BACKBONE_ROUTER_STATE_DISABLED value is incorrect");
    static_assert(1 == OT_BACKBONE_ROUTER_STATE_SECONDARY, "OT_BACKBONE_ROUTER_STATE_SECONDARY value is incorrect");
    static_assert(2 == OT_BACKBONE_ROUTER_STATE_PRIMARY, "OT_BACKBONE_ROUTER_STATE_PRIMARY value is incorrect");

    OT_UNUSED_VARIABLE(aArgs);

    OutputLine("%s", Stringify(otBackboneRouterGetState(GetInstancePtr()), kStateStrings));

    return OT_ERROR_NONE;
}

/**
 * @cli bbr config
 * @code
 * bbr config
 * seqno:    10
 * delay:    120 secs
 * timeout:  300 secs
 * Done
 * @endcode
 * @par api_copy
 * #otBackboneRouterGetConfig
 */
template <> otError Bbr::Process<Cmd("config")>(Arg aArgs[])
{
    otError                error = OT_ERROR_NONE;
    otBackboneRouterConfig config;

    otBackboneRouterGetConfig(GetInstancePtr(), &config);

    if (aArgs[0].IsEmpty())
    {
        OutputConfig(config);
    }
    else
    {
        // Set local Backbone Router configuration.
        /**
         * @cli bbr config (set)
         * @code
         * bbr config seqno 20 delay 30
         * Done
         * @endcode
         * @cparam bbr config [seqno @ca{seqno}] [delay @ca{delay}] [timeout @ca{timeout}]
         * @par
         * `bbr register` should be issued explicitly to register Backbone Router service to Leader
         * for Secondary Backbone Router.
         * @par api_copy
         * #otBackboneRouterSetConfig
         */
        for (Arg *arg = &aArgs[0]; !arg->IsEmpty(); arg++)
        {
            if (*arg == "seqno")
            {
                arg++;
                SuccessOrExit(error = arg->ParseAsUint8(config.mSequenceNumber));
            }
            else if (*arg == "delay")
            {
                arg++;
                SuccessOrExit(error = arg->ParseAsUint16(config.mReregistrationDelay));
            }
            else if (*arg == "timeout")
            {
                arg++;
                SuccessOrExit(error = arg->ParseAsUint32(config.mMlrTimeout));
            }
            else
            {
                ExitNow(error = OT_ERROR_INVALID_ARGS);
            }
        }

        error = otBackboneRouterSetConfig(GetInstancePtr(), &config);
    }

exit:
    return error;
}

#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE

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

    otError error = OT_ERROR_INVALID_COMMAND;

    /**
     * @cli bbr
     * @code
     * bbr
     * BBR Primary:
     * server16: 0xE400
     * seqno:    10
     * delay:    120 secs
     * timeout:  300 secs
     * Done
     * @endcode
     * @code
     * bbr
     * BBR Primary: None
     * Done
     * @endcode
     * @par
     * Returns the current Primary Backbone Router information for the Thread device.
     */
    if (aArgs[0].IsEmpty())
    {
        otBackboneRouterConfig config;

        OutputFormat("BBR Primary:");

        if (otBackboneRouterGetPrimary(GetInstancePtr(), &config) == OT_ERROR_NONE)
        {
            OutputNewLine();
            OutputLine("server16: 0x%04X", config.mServer16);
            OutputConfig(config);
        }
        else
        {
            OutputLine(" None");
        }

        error = OT_ERROR_NONE;
        ExitNow();
    }

#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
    {
        static constexpr Command kCommands[] = {
            CmdEntry("config"), CmdEntry("disable"),  CmdEntry("enable"), CmdEntry("jitter"),
            CmdEntry("mgmt"),   CmdEntry("register"), CmdEntry("state"),
        };

#undef CmdEntry

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

        const Command *command;

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

        error = (this->*command->mHandler)(aArgs + 1);
    }
#endif // #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE

exit:
    return error;
}

} // namespace Cli
} // namespace ot

#endif // #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
