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

#include "cli_udp.hpp"

#include <openthread/message.h>
#include <openthread/udp.h>

#include "cli/cli.hpp"
#include "common/encoding.hpp"

namespace ot {
namespace Cli {

constexpr UdpExample::Command UdpExample::sCommands[];

UdpExample::UdpExample(Output &aOutput)
    : OutputWrapper(aOutput)
    , mLinkSecurityEnabled(true)
{
    memset(&mSocket, 0, sizeof(mSocket));
}

otError UdpExample::ProcessHelp(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    for (const Command &command : sCommands)
    {
        OutputLine(command.mName);
    }

    return OT_ERROR_NONE;
}

otError UdpExample::ProcessBind(Arg aArgs[])
{
    otError           error;
    otSockAddr        sockaddr;
    otNetifIdentifier netif = OT_NETIF_THREAD;

    if (aArgs[0] == "-u")
    {
        netif = OT_NETIF_UNSPECIFIED;
        aArgs++;
    }
    else if (aArgs[0] == "-b")
    {
        netif = OT_NETIF_BACKBONE;
        aArgs++;
    }

    SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress));
    SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort));
    VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);

    error = otUdpBind(GetInstancePtr(), &mSocket, &sockaddr, netif);

exit:
    return error;
}

otError UdpExample::ProcessConnect(Arg aArgs[])
{
    otError    error;
    otSockAddr sockaddr;

    SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress));
    SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort));
    VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);

    error = otUdpConnect(GetInstancePtr(), &mSocket, &sockaddr);

exit:
    return error;
}

otError UdpExample::ProcessClose(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    return otUdpClose(GetInstancePtr(), &mSocket);
}

otError UdpExample::ProcessOpen(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    otError error;

    VerifyOrExit(!otUdpIsOpen(GetInstancePtr(), &mSocket), error = OT_ERROR_ALREADY);
    error = otUdpOpen(GetInstancePtr(), &mSocket, HandleUdpReceive, this);

exit:
    return error;
}

otError UdpExample::ProcessSend(Arg aArgs[])
{
    otError           error   = OT_ERROR_NONE;
    otMessage *       message = nullptr;
    otMessageInfo     messageInfo;
    otMessageSettings messageSettings = {mLinkSecurityEnabled, OT_MESSAGE_PRIORITY_NORMAL};

    memset(&messageInfo, 0, sizeof(messageInfo));

    // Possible argument formats:
    //
    // send             <text>
    // send             <type> <value>
    // send <ip> <port> <text>
    // send <ip> <port> <type> <value>

    if (!aArgs[2].IsEmpty())
    {
        SuccessOrExit(error = aArgs[0].ParseAsIp6Address(messageInfo.mPeerAddr));
        SuccessOrExit(error = aArgs[1].ParseAsUint16(messageInfo.mPeerPort));
        aArgs += 2;
    }

    message = otUdpNewMessage(GetInstancePtr(), &messageSettings);
    VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);

    if (aArgs[0] == "-s")
    {
        // Auto-generated payload with a given length

        uint16_t payloadLength;

        SuccessOrExit(error = aArgs[1].ParseAsUint16(payloadLength));
        SuccessOrExit(error = PrepareAutoGeneratedPayload(*message, payloadLength));
    }
    else if (aArgs[0] == "-x")
    {
        // Binary hex data payload

        VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
        SuccessOrExit(error = PrepareHexStringPaylod(*message, aArgs[1].GetCString()));
    }
    else
    {
        // Text payload (same as without specifying the type)

        if (aArgs[0] == "-t")
        {
            aArgs++;
        }

        VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
        SuccessOrExit(error = otMessageAppend(message, aArgs[0].GetCString(), aArgs[0].GetLength()));
    }

    SuccessOrExit(error = otUdpSend(GetInstancePtr(), &mSocket, message, &messageInfo));

    message = nullptr;

exit:
    if (message != nullptr)
    {
        otMessageFree(message);
    }

    return error;
}

otError UdpExample::ProcessLinkSecurity(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        OutputEnabledDisabledStatus(mLinkSecurityEnabled);
    }
    else
    {
        error = Interpreter::ParseEnableOrDisable(aArgs[0], mLinkSecurityEnabled);
    }

    return error;
}

otError UdpExample::PrepareAutoGeneratedPayload(otMessage &aMessage, uint16_t aPayloadLength)
{
    otError error     = OT_ERROR_NONE;
    uint8_t character = '0';

    for (; aPayloadLength != 0; aPayloadLength--)
    {
        SuccessOrExit(error = otMessageAppend(&aMessage, &character, sizeof(character)));

        switch (character)
        {
        case '9':
            character = 'A';
            break;
        case 'Z':
            character = 'a';
            break;
        case 'z':
            character = '0';
            break;
        default:
            character++;
            break;
        }
    }

exit:
    return error;
}

otError UdpExample::PrepareHexStringPaylod(otMessage &aMessage, const char *aHexString)
{
    enum : uint8_t
    {
        kBufferSize = 50,
    };

    otError  error;
    uint8_t  buf[kBufferSize];
    uint16_t length;
    bool     done = false;

    while (!done)
    {
        length = sizeof(buf);
        error  = Utils::CmdLineParser::ParseAsHexStringSegment(aHexString, length, buf);

        VerifyOrExit((error == OT_ERROR_NONE) || (error == OT_ERROR_PENDING));
        done = (error == OT_ERROR_NONE);

        SuccessOrExit(error = otMessageAppend(&aMessage, buf, length));
    }

exit:
    return error;
}

otError UdpExample::Process(Arg aArgs[])
{
    otError        error = OT_ERROR_INVALID_ARGS;
    const Command *command;

    if (aArgs[0].IsEmpty())
    {
        IgnoreError(ProcessHelp(aArgs));
        ExitNow();
    }

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

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

exit:
    return error;
}

void UdpExample::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
    static_cast<UdpExample *>(aContext)->HandleUdpReceive(aMessage, aMessageInfo);
}

void UdpExample::HandleUdpReceive(otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
    char buf[1500];
    int  length;

    OutputFormat("%d bytes from ", otMessageGetLength(aMessage) - otMessageGetOffset(aMessage));
    OutputIp6Address(aMessageInfo->mPeerAddr);
    OutputFormat(" %d ", aMessageInfo->mPeerPort);

    length      = otMessageRead(aMessage, otMessageGetOffset(aMessage), buf, sizeof(buf) - 1);
    buf[length] = '\0';

    OutputLine("%s", buf);
}

} // namespace Cli
} // namespace ot
