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

#ifndef OTBR_ANDROID_MDNS_PUBLISHER_HPP_
#define OTBR_ANDROID_MDNS_PUBLISHER_HPP_

#include "mdns/mdns.hpp"

#include <aidl/com/android/server/thread/openthread/BnNsdDiscoverServiceCallback.h>
#include <aidl/com/android/server/thread/openthread/BnNsdResolveHostCallback.h>
#include <aidl/com/android/server/thread/openthread/BnNsdResolveServiceCallback.h>
#include <aidl/com/android/server/thread/openthread/BnNsdStatusReceiver.h>
#include <aidl/com/android/server/thread/openthread/DnsTxtAttribute.h>
#include <aidl/com/android/server/thread/openthread/INsdPublisher.h>
#include <set>

namespace otbr {
namespace Android {
using Status = ::ndk::ScopedAStatus;
using aidl::com::android::server::thread::openthread::BnNsdDiscoverServiceCallback;
using aidl::com::android::server::thread::openthread::BnNsdResolveHostCallback;
using aidl::com::android::server::thread::openthread::BnNsdResolveServiceCallback;
using aidl::com::android::server::thread::openthread::BnNsdStatusReceiver;
using aidl::com::android::server::thread::openthread::DnsTxtAttribute;
using aidl::com::android::server::thread::openthread::INsdPublisher;

class MdnsPublisher : public Mdns::Publisher
{
public:
    explicit MdnsPublisher(Publisher::StateCallback aCallback)
        : mStateCallback(std::move(aCallback))
        , mNextListenerId(0)

    {
    }

    ~MdnsPublisher(void) { Stop(); }

    /** Sets the INsdPublisher which forwards the mDNS API requests to the NsdManager in system_server. */
    void SetINsdPublisher(std::shared_ptr<INsdPublisher> aINsdPublisher);

    otbrError Start(void) override { return OTBR_ERROR_NONE; }

    void Stop(void) override
    {
        mServiceRegistrations.clear();
        mHostRegistrations.clear();
        if (mNsdPublisher != nullptr)
        {
            mNsdPublisher->reset();
        }
    }

    bool IsStarted(void) const override { return mNsdPublisher != nullptr; }

    void UnpublishService(const std::string &aName, const std::string &aType, ResultCallback &&aCallback) override;

    void UnpublishHost(const std::string &aName, ResultCallback &&aCallback) override;

    void UnpublishKey(const std::string &aName, ResultCallback &&aCallback) override;

    void SubscribeService(const std::string &aType, const std::string &aInstanceName) override;

    void UnsubscribeService(const std::string &aType, const std::string &aInstanceName) override;

    void SubscribeHost(const std::string &aHostName) override;

    void UnsubscribeHost(const std::string &aHostName) override;

    class NsdStatusReceiver : public BnNsdStatusReceiver
    {
    public:
        explicit NsdStatusReceiver(Mdns::Publisher::ResultCallback aCallback)
            : mCallback(std::move(aCallback))
        {
        }

        Status onSuccess(void) override;

        Status onError(int aError) override;

    private:
        Mdns::Publisher::ResultCallback mCallback;
    };

    struct ServiceResolver : private ::NonCopyable
    {
        explicit ServiceResolver(int aListenerId, std::shared_ptr<INsdPublisher> aNsdPublisher)
            : mListenerId(aListenerId)
            , mNsdPublisher(std::move(aNsdPublisher))
        {
        }

        ~ServiceResolver(void)
        {
            if (mNsdPublisher)
            {
                mNsdPublisher->stopServiceResolution(mListenerId);
            }
        }

        int                            mListenerId;
        std::shared_ptr<INsdPublisher> mNsdPublisher;
    };

    struct ServiceSubscription : public std::enable_shared_from_this<ServiceSubscription>, private ::NonCopyable
    {
        explicit ServiceSubscription(std::string                    aType,
                                     std::string                    aName,
                                     MdnsPublisher                 &aPublisher,
                                     std::shared_ptr<INsdPublisher> aNsdPublisher)
            : mType(std::move(aType))
            , mName(std::move(aName))
            , mPublisher(aPublisher)
            , mNsdPublisher(std::move(aNsdPublisher))
            , mBrowseListenerId(-1)
        {
        }

        ~ServiceSubscription(void) { Release(); }

        void Release(void);
        void Browse(void);
        void Resolve(const std::string &aName, const std::string &aType);
        void AddServiceResolver(const std::string &aName, ServiceResolver *aResolver);
        void RemoveServiceResolver(const std::string &aInstanceName);

        std::string                    mType;
        std::string                    mName;
        MdnsPublisher                 &mPublisher;
        std::shared_ptr<INsdPublisher> mNsdPublisher;
        int32_t                        mBrowseListenerId;

        std::map<std::string, std::set<ServiceResolver *>> mResolvers;
    };

    struct HostSubscription : private ::NonCopyable
    {
        explicit HostSubscription(std::string                    aName,
                                  MdnsPublisher                 &aPublisher,
                                  std::shared_ptr<INsdPublisher> aNsdPublisher,
                                  int                            listenerId)
            : mName(std::move(aName))
            , mPublisher(aPublisher)
            , mNsdPublisher(std::move(aNsdPublisher))
            , mListenerId(listenerId)
        {
        }

        ~HostSubscription(void) { Release(); }

        void Release(void) { mNsdPublisher->stopHostResolution(mListenerId); }

        std::string                    mName;
        MdnsPublisher                 &mPublisher;
        std::shared_ptr<INsdPublisher> mNsdPublisher;
        const int32_t                  mListenerId;
    };

    class NsdDiscoverServiceCallback : public BnNsdDiscoverServiceCallback
    {
    public:
        explicit NsdDiscoverServiceCallback(std::weak_ptr<ServiceSubscription> aSubscription)
            : mSubscription(std::move(aSubscription))
        {
        }

        Status onServiceDiscovered(const std::string &aName, const std::string &aType, bool aIsFound);

    private:
        std::weak_ptr<ServiceSubscription> mSubscription;
    };

    class NsdResolveServiceCallback : public BnNsdResolveServiceCallback
    {
    public:
        explicit NsdResolveServiceCallback(std::weak_ptr<ServiceSubscription> aSubscription)
            : mSubscription(std::move(aSubscription))
        {
        }

        Status onServiceResolved(const std::string                  &aHostname,
                                 int                                 aNetifIndex,
                                 const std::string                  &aName,
                                 const std::string                  &aType,
                                 int                                 aPort,
                                 const std::vector<std::string>     &aAddresses,
                                 const std::vector<DnsTxtAttribute> &aTxt,
                                 int                                 aTtlSeconds);

    private:
        std::weak_ptr<ServiceSubscription> mSubscription;
    };

    class NsdResolveHostCallback : public BnNsdResolveHostCallback
    {
    public:
        explicit NsdResolveHostCallback(std::weak_ptr<HostSubscription> aSubscription)
            : mSubscription(std::move(aSubscription))
        {
        }

        Status onHostResolved(const std::string &aName, const std::vector<std::string> &aAddresses);

    private:
        std::weak_ptr<HostSubscription> mSubscription;
    };

protected:
    otbrError PublishServiceImpl(const std::string &aHostName,
                                 const std::string &aName,
                                 const std::string &aType,
                                 const SubTypeList &aSubTypeList,
                                 uint16_t           aPort,
                                 const TxtData     &aTxtData,
                                 ResultCallback   &&aCallback) override;

    otbrError PublishHostImpl(const std::string &aName, const AddressList &aAddresses, ResultCallback &&aCallback);

    otbrError PublishKeyImpl(const std::string &aName, const KeyData &aKeyData, ResultCallback &&aCallback) override;

    void OnServiceResolveFailedImpl(const std::string &aType, const std::string &aInstanceName, int32_t aErrorCode);

    void OnHostResolveFailedImpl(const std::string &aHostName, int32_t aErrorCode);

    otbrError DnsErrorToOtbrError(int32_t aError);

private:
    class NsdServiceRegistration : public ServiceRegistration
    {
    public:
        NsdServiceRegistration(const std::string           &aHostName,
                               const std::string           &aName,
                               const std::string           &aType,
                               const SubTypeList           &aSubTypeList,
                               uint16_t                     aPort,
                               const TxtData               &aTxtData,
                               ResultCallback             &&aCallback,
                               MdnsPublisher               *aPublisher,
                               int32_t                      aListenerId,
                               std::weak_ptr<INsdPublisher> aINsdPublisher)
            : ServiceRegistration(aHostName,
                                  aName,
                                  aType,
                                  aSubTypeList,
                                  aPort,
                                  aTxtData,
                                  std::move(aCallback),
                                  aPublisher)
            , mListenerId(aListenerId)
            , mNsdPublisher(std::move(aINsdPublisher))

        {
        }

        ~NsdServiceRegistration(void) override;

        const int32_t                      mListenerId;
        std::shared_ptr<NsdStatusReceiver> mUnregisterReceiver;

    private:
        std::weak_ptr<INsdPublisher> mNsdPublisher;
    };

    class NsdHostRegistration : public HostRegistration
    {
    public:
        NsdHostRegistration(const std::string           &aName,
                            const AddressList           &aAddresses,
                            ResultCallback             &&aCallback,
                            MdnsPublisher               *aPublisher,
                            int32_t                      aListenerId,
                            std::weak_ptr<INsdPublisher> aINsdPublisher)
            : HostRegistration(aName, aAddresses, std::move(aCallback), aPublisher)
            , mListenerId(aListenerId)
            , mNsdPublisher(aINsdPublisher)
        {
        }

        ~NsdHostRegistration(void) override;

        const int32_t                      mListenerId;
        std::shared_ptr<NsdStatusReceiver> mUnregisterReceiver;

    private:
        std::weak_ptr<INsdPublisher> mNsdPublisher;
    };

    typedef std::vector<std::shared_ptr<ServiceSubscription>> ServiceSubscriptionList;
    typedef std::vector<std::shared_ptr<HostSubscription>>    HostSubscriptionList;

    static constexpr int kDefaultResolvedTtl = 10;
    static constexpr int kMinResolvedTtl     = 1;
    static constexpr int kMaxResolvedTtl     = 10;

    int32_t AllocateListenerId(void);

    StateCallback                  mStateCallback;
    int32_t                        mNextListenerId;
    std::shared_ptr<INsdPublisher> mNsdPublisher = nullptr;
    ServiceSubscriptionList        mServiceSubscriptions;
    HostSubscriptionList           mHostSubscriptions;
};

} // namespace Android
} // namespace otbr

#endif // OTBR_ANDROID_MDNS_PUBLISHER_HPP_
