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

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include <netinet/in.h>
#include <signal.h>

#include <vector>

#include "common/code_utils.hpp"
#include "common/logging.hpp"
#include "common/mainloop.hpp"
#include "common/mainloop_manager.hpp"
#include "mdns/mdns.hpp"

using namespace otbr;

static struct Context
{
    Mdns::Publisher *mPublisher;
    bool             mUpdate;
} sContext;

int RunMainloop(void)
{
    int rval = 0;

    while (true)
    {
        MainloopContext mainloop;

        mainloop.mMaxFd   = -1;
        mainloop.mTimeout = {INT_MAX, INT_MAX};
        FD_ZERO(&mainloop.mReadFdSet);
        FD_ZERO(&mainloop.mWriteFdSet);
        FD_ZERO(&mainloop.mErrorFdSet);

        MainloopManager::GetInstance().Update(mainloop);
        rval = select(mainloop.mMaxFd + 1, &mainloop.mReadFdSet, &mainloop.mWriteFdSet, &mainloop.mErrorFdSet,
                      (mainloop.mTimeout.tv_sec == INT_MAX ? nullptr : &mainloop.mTimeout));

        if (rval < 0)
        {
            perror("select");
            break;
        }

        MainloopManager::GetInstance().Process(mainloop);
    }

    return rval;
}

void PublishSingleServiceWithCustomHost(void *aContext, Mdns::Publisher::State aState)
{
    uint8_t    xpanid[kSizeExtPanId]           = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
    uint8_t    extAddr[kSizeExtAddr]           = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
    uint8_t    hostAddr[OTBR_IP6_ADDRESS_SIZE] = {0};
    const char hostName[]                      = "custom-host";

    hostAddr[0]  = 0x20;
    hostAddr[1]  = 0x02;
    hostAddr[15] = 0x01;

    VerifyOrDie(aContext == &sContext, "unexpected context");
    if (aState == Mdns::Publisher::State::kReady)
    {
        Mdns::Publisher::TxtList txtList{
            {"nn", "cool"}, {"xp", xpanid, sizeof(xpanid)}, {"tv", "1.1.1"}, {"xa", extAddr, sizeof(extAddr)}};

        sContext.mPublisher->PublishHost(hostName, {Ip6Address(hostAddr)},
                                         [](otbrError aError) { SuccessOrDie(aError, "cannot publish the host"); });

        sContext.mPublisher->PublishService(
            hostName, "SingleService", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "cannot publish the service"); });
    }
}

void PublishMultipleServicesWithCustomHost(void *aContext, Mdns::Publisher::State aState)
{
    uint8_t    xpanid[kSizeExtPanId]           = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
    uint8_t    extAddr[kSizeExtAddr]           = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
    uint8_t    hostAddr[OTBR_IP6_ADDRESS_SIZE] = {0};
    const char hostName1[]                     = "custom-host-1";
    const char hostName2[]                     = "custom-host-2";

    hostAddr[0]  = 0x20;
    hostAddr[1]  = 0x02;
    hostAddr[15] = 0x01;

    VerifyOrDie(aContext == &sContext, "unexpected context");
    if (aState == Mdns::Publisher::State::kReady)
    {
        Mdns::Publisher::TxtList txtList{
            {"nn", "cool"}, {"xp", xpanid, sizeof(xpanid)}, {"tv", "1.1.1"}, {"xa", extAddr, sizeof(extAddr)}};

        sContext.mPublisher->PublishHost(hostName1, {Ip6Address(hostAddr)},
                                         [](otbrError aError) { SuccessOrDie(aError, "cannot publish the host"); });

        sContext.mPublisher->PublishService(
            hostName1, "MultipleService11", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "cannot publish the first service"); });

        sContext.mPublisher->PublishService(
            hostName1, "MultipleService12", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "cannot publish the second service"); });

        sContext.mPublisher->PublishHost(hostName2, {Ip6Address(hostAddr)}, [](otbrError aError) {
            SuccessOrDie(aError, "cannot publish the second host");
        });

        sContext.mPublisher->PublishService(
            hostName2, "MultipleService21", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "cannot publish the first service"); });

        sContext.mPublisher->PublishService(
            hostName2, "MultipleService22", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "cannot publish the second service"); });
    }
}

void PublishSingleService(void *aContext, Mdns::Publisher::State aState)
{
    OT_UNUSED_VARIABLE(aContext);

    uint8_t                  xpanid[kSizeExtPanId] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
    uint8_t                  extAddr[kSizeExtAddr] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
    Mdns::Publisher::TxtList txtList{
        {"nn", "cool"}, {"xp", xpanid, sizeof(xpanid)}, {"tv", "1.1.1"}, {"xa", extAddr, sizeof(extAddr)}};

    assert(aContext == &sContext);
    if (aState == Mdns::Publisher::State::kReady)
    {
        sContext.mPublisher->PublishService(
            "", "SingleService", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "SingleService._meshcop._udp."); });
    }
}

void PublishMultipleServices(void *aContext, Mdns::Publisher::State aState)
{
    OT_UNUSED_VARIABLE(aContext);

    uint8_t xpanid[kSizeExtPanId] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
    uint8_t extAddr[kSizeExtAddr] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};

    assert(aContext == &sContext);
    if (aState == Mdns::Publisher::State::kReady)
    {
        Mdns::Publisher::TxtList txtList{
            {"nn", "cool1"}, {"xp", xpanid, sizeof(xpanid)}, {"tv", "1.1.1"}, {"xa", extAddr, sizeof(extAddr)}};

        sContext.mPublisher->PublishService(
            "", "MultipleService1", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "MultipleService1._meshcop._udp."); });
    }

    if (aState == Mdns::Publisher::State::kReady)
    {
        Mdns::Publisher::TxtList txtList{
            {"nn", "cool2"}, {"xp", xpanid, sizeof(xpanid)}, {"tv", "1.1.1"}, {"xa", extAddr, sizeof(extAddr)}};

        sContext.mPublisher->PublishService(
            "", "MultipleService2", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "MultipleService2._meshcop._udp."); });
    }
}

void PublishUpdateServices(void *aContext)
{
    OT_UNUSED_VARIABLE(aContext);

    uint8_t xpanidOld[kSizeExtPanId] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
    uint8_t xpanidNew[kSizeExtPanId] = {0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41};
    uint8_t extAddr[kSizeExtAddr]    = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};

    assert(aContext == &sContext);
    if (!sContext.mUpdate)
    {
        Mdns::Publisher::TxtList txtList{
            {"nn", "cool"}, {"xp", xpanidOld, sizeof(xpanidOld)}, {"tv", "1.1.1"}, {"xa", extAddr, sizeof(extAddr)}};

        sContext.mPublisher->PublishService(
            "", "UpdateService", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { otbrLogResult(aError, "UpdateService._meshcop._udp"); });
    }
    else
    {
        Mdns::Publisher::TxtList txtList{{"nn", "coolcool"},
                                         {"xp", xpanidNew, sizeof(xpanidNew)},
                                         {"tv", "1.1.1"},
                                         {"xa", extAddr, sizeof(extAddr)}};

        sContext.mPublisher->PublishService(
            "", "UpdateService", "_meshcop._udp.", Mdns::Publisher::SubTypeList{}, 12345, txtList,
            [](otbrError aError) { SuccessOrDie(aError, "UpdateService._meshcop._udp"); });
    }
}

void PublishServiceSubTypes(void *aContext)
{
    OT_UNUSED_VARIABLE(aContext);

    assert(aContext == &sContext);

    Mdns::Publisher::SubTypeList subTypeList{"_subtype1", "_SUBTYPE2"};

    subTypeList.back() = "_SUBTYPE3";

    sContext.mPublisher->PublishService(
        "", "ServiceWithSubTypes", "_meshcop._udp.", subTypeList, 12345, Mdns::Publisher::TxtList{},
        [](otbrError aError) { SuccessOrDie(aError, "ServiceWithSubTypes._meshcop._udp."); });
}

otbrError TestSingleServiceWithCustomHost(void)
{
    otbrError error = OTBR_ERROR_NONE;

    Mdns::Publisher *pub = Mdns::Publisher::Create(
        [](Mdns::Publisher::State aState) { PublishSingleServiceWithCustomHost(&sContext, aState); });
    sContext.mPublisher = pub;
    SuccessOrExit(error = pub->Start());
    RunMainloop();

exit:
    Mdns::Publisher::Destroy(pub);
    return error;
}

otbrError TestMultipleServicesWithCustomHost(void)
{
    otbrError error = OTBR_ERROR_NONE;

    Mdns::Publisher *pub = Mdns::Publisher::Create(
        [](Mdns::Publisher::State aState) { PublishMultipleServicesWithCustomHost(&sContext, aState); });
    sContext.mPublisher = pub;
    SuccessOrExit(error = pub->Start());
    RunMainloop();

exit:
    Mdns::Publisher::Destroy(pub);
    return error;
}

otbrError TestSingleService(void)
{
    otbrError ret = OTBR_ERROR_NONE;

    Mdns::Publisher *pub =
        Mdns::Publisher::Create([](Mdns::Publisher::State aState) { PublishSingleService(&sContext, aState); });
    sContext.mPublisher = pub;
    SuccessOrExit(ret = pub->Start());
    RunMainloop();

exit:
    Mdns::Publisher::Destroy(pub);
    return ret;
}

otbrError TestMultipleServices(void)
{
    otbrError ret = OTBR_ERROR_NONE;

    Mdns::Publisher *pub =
        Mdns::Publisher::Create([](Mdns::Publisher::State aState) { PublishMultipleServices(&sContext, aState); });
    sContext.mPublisher = pub;
    SuccessOrExit(ret = pub->Start());
    RunMainloop();

exit:
    Mdns::Publisher::Destroy(pub);
    return ret;
}

otbrError TestUpdateService(void)
{
    otbrError ret = OTBR_ERROR_NONE;

    Mdns::Publisher *pub = Mdns::Publisher::Create([](Mdns::Publisher::State aState) {
        if (aState == Mdns::Publisher::State::kReady)
        {
            sContext.mUpdate = false;
            PublishUpdateServices(&sContext);
            sContext.mUpdate = true;
            PublishUpdateServices(&sContext);
        }
    });
    sContext.mPublisher  = pub;
    SuccessOrExit(ret = pub->Start());
    RunMainloop();

exit:
    Mdns::Publisher::Destroy(pub);
    return ret;
}

otbrError TestServiceSubTypes(void)
{
    otbrError ret = OTBR_ERROR_NONE;

    Mdns::Publisher *pub = Mdns::Publisher::Create([](Mdns::Publisher::State aState) {
        if (aState == Mdns::Publisher::State::kReady)
        {
            PublishServiceSubTypes(&sContext);
        }
    });
    sContext.mPublisher  = pub;
    SuccessOrExit(ret = pub->Start());
    RunMainloop();

exit:
    Mdns::Publisher::Destroy(pub);
    return ret;
}

void RecoverSignal(int aSignal)
{
    if (aSignal == SIGUSR1)
    {
        signal(SIGUSR1, SIG_DFL);
    }
    else if (aSignal == SIGUSR2)
    {
        signal(SIGUSR2, SIG_DFL);
    }
}

otbrError TestStopService(void)
{
    otbrError ret = OTBR_ERROR_NONE;

    Mdns::Publisher *pub =
        Mdns::Publisher::Create([](Mdns::Publisher::State aState) { PublishSingleService(&sContext, aState); });
    sContext.mPublisher = pub;
    SuccessOrExit(ret = pub->Start());
    signal(SIGUSR1, RecoverSignal);
    signal(SIGUSR2, RecoverSignal);
    RunMainloop();
    sContext.mPublisher->Stop();
    RunMainloop();
    SuccessOrExit(ret = sContext.mPublisher->Start());
    RunMainloop();

exit:
    Mdns::Publisher::Destroy(pub);
    return ret;
}

int main(int argc, char *argv[])
{
    int ret = 0;

    if (argc < 2)
    {
        return 1;
    }

    otbrLogInit("otbr-mdns", OTBR_LOG_DEBUG, true);
    // allow quitting elegantly
    signal(SIGTERM, RecoverSignal);
    switch (argv[1][0])
    {
    case 's':
        ret = argv[1][1] == 'c' ? TestSingleServiceWithCustomHost() : TestSingleService();
        break;

    case 'm':
        ret = argv[1][1] == 'c' ? TestMultipleServicesWithCustomHost() : TestMultipleServices();
        break;

    case 'u':
        ret = TestUpdateService();
        break;

    case 't':
        ret = TestServiceSubTypes();
        break;

    case 'k':
        ret = TestStopService();
        break;

    default:
        ret = 1;
        break;
    }

    return ret;
}
