/*
 *
 *    Copyright (c) 2022 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
#pragma once

#include <lib/dnssd/Resolver.h>
#include <lib/dnssd/Types.h>
#include <lib/dnssd/minimal_mdns/Parser.h>
#include <lib/dnssd/minimal_mdns/RecordData.h>
#include <lib/dnssd/minimal_mdns/core/QName.h>
#include <lib/support/BitFlags.h>
#include <lib/support/Variant.h>

namespace chip {
namespace Dnssd {

/// Allows storing and retrieving of a SerializedQName for later recall.
///
/// This is a convenience storage as MDNS generally provides data as QNames
/// and comparisons between QNames is more convenient than replacing them with
/// null-terminated character strings.
class StoredServerName
{
public:
    StoredServerName() {}

    void Clear() { memset(mNameBuffer, 0, sizeof(mNameBuffer)); }

    /// Set the underlying value. Will return CHIP_ERROR_NO_MEMORY
    /// on insufficient storage space.
    ///
    /// If insufficient space, buffer will be cleared.
    CHIP_ERROR Set(mdns::Minimal::SerializedQNameIterator value);

    /// Return the underlying value in this object.
    ///
    /// Value valid as long as this object is valid and underlying Set() is
    /// not called.
    mdns::Minimal::SerializedQNameIterator Get() const;

private:
    // Try to have space for at least:
    //  L1234._sub._matterc._udp.local      => 30 chars
    //  <fabric>-<node>._matter._tcp.local  => 52 chars
    //  <hostname>.local (where hostname is kHostNameMaxLength == 16)
    //
    // This does not try to optimize out ".local" suffix which is always expected
    // since comparisons are easier when suffix is still present.
    static constexpr size_t kMaxStoredNameLength = 64;

    uint8_t mNameBuffer[kMaxStoredNameLength] = {};
};

/// Incrementally accumulates data from DNSSD packets. It is geared twoards
/// resource-constrained dns-sd querier implementations.
///
/// It all starts with processing SRV records which define the type of record
/// (could be operational, commissionable or commissioner), after which the
/// additional data is accumulated, specifically TXT information and A/AAAA
///
/// Class can also be used to determine what additional data is missing from a
/// record so that additional DNSSD queries can be made recursively (e.g. if
/// only a SRV/TXT records are available, ask for AAAA records).
class IncrementalResolver
{
public:
    // Elements that the incremental resolve still needs
    enum class RequiredInformationBitFlags : uint8_t
    {
        kSrvInitialization = (1 << 0), // server being initialized
        kIpAddress         = (1 << 1), // IP address missing
    };
    using RequiredInformationFlags = BitFlags<RequiredInformationBitFlags>;

    // Type of service name that is contained in this resolver
    enum class ServiceNameType
    {
        kInvalid, // not a matter service name
        kOperational,
        kCommissioner,
        kCommissionable,
    };

    IncrementalResolver() = default;

    /// Checks if object has been initialized using the `InitializeParsing`
    /// method.
    bool IsActive() const { return mSpecificResolutionData.Valid(); }

    bool IsActiveCommissionParse() const { return mSpecificResolutionData.Is<CommissionNodeData>(); }
    bool IsActiveOperationalParse() const { return mSpecificResolutionData.Is<OperationalNodeData>(); }

    ServiceNameType GetCurrentType() const { return mServiceNameType; }

    PeerId OperationalParsePeerId() const
    {
        VerifyOrReturnValue(IsActiveOperationalParse(), PeerId());
        return mSpecificResolutionData.Get<OperationalNodeData>().peerId;
    }

    /// Start parsing a new record. SRV records are the records we are mainly
    /// interested on, after which TXT and A/AAAA are looked for.
    ///
    /// If this function returns with error, the object will be in an inactive state.
    CHIP_ERROR InitializeParsing(mdns::Minimal::SerializedQNameIterator name, const uint64_t ttl,
                                 const mdns::Minimal::SrvRecord & srv);

    /// Notify that a new record is being processed.
    /// Will handle filtering and processing of data to determine if the entry is relevant for
    /// the current resolver.
    ///
    /// Providing a data that is not relevant to the current parser is not considered and error,
    /// however if the resource fails parsing completely an error will be returned.
    ///
    ///
    /// [data] represents the record received via [interface] and [packetRange] represents the range
    /// of valid bytes within the packet for the purpose of QName parsing
    CHIP_ERROR OnRecord(Inet::InterfaceId interface, const mdns::Minimal::ResourceData & data,
                        mdns::Minimal::BytesRange packetRange);

    /// Return what additional data is required until the object can be extracted
    ///
    /// If `!GetREquiredInformation().HasAny()` the parsed information is ready
    /// to be processed.
    RequiredInformationFlags GetMissingRequiredInformation() const;

    /// Fetch the target host name set by `InitializeParsing`
    ///
    /// VALIDITY: Data references internal storage of this object and is valid as long
    ///           as this object is valid and InitializeParsing is not called again.
    mdns::Minimal::SerializedQNameIterator GetTargetHostName() const { return mTargetHostName.Get(); }

    /// Fetch the record name set by `InitializeParsing`.
    ///
    /// VALIDITY: Data references internal storage of this object and is valid as long
    ///           as this object is valid and InitializeParsing is not called again.
    mdns::Minimal::SerializedQNameIterator GetRecordName() const { return mRecordName.Get(); }

    /// Take the current value of the object and clear it once returned.
    ///
    /// Object must be in `IsActive()` for this to succeed.
    /// Data will be returned (and cleared) even if not yet complete based
    /// on `GetMissingRequiredInformation()`. This method takes as much data as
    /// it was parsed so far.
    CHIP_ERROR Take(DiscoveredNodeData & outputData);

    /// Take the current value of the object and clear it once returned.
    ///
    /// Object must be in `IsActiveOperationalParse()` for this to succeed.
    /// Data will be returned (and cleared) even if not yet complete based
    /// on `GetMissingRequiredInformation()`. This method takes as much data as
    /// it was parsed so far.
    CHIP_ERROR Take(ResolvedNodeData & outputData);

    /// Clears current state, setting as inactive
    void ResetToInactive()
    {
        mCommonResolutionData.Reset();
        mSpecificResolutionData = ParsedRecordSpecificData();
    }

private:
    /// Notify that a PTR record can be parsed.
    ///
    /// Input data MUST have GetType() == QType::TXT
    CHIP_ERROR OnTxtRecord(const mdns::Minimal::ResourceData & data, mdns::Minimal::BytesRange packetRange);

    /// Notify that a new IP address has been found.
    ///
    /// This is to be called on both A (if IPv4 support is enabled) and AAAA
    /// addresses.
    ///
    /// Prerequisite: IP address belongs to the right nost name
    CHIP_ERROR OnIpAddress(Inet::InterfaceId interface, const Inet::IPAddress & addr);

    using ParsedRecordSpecificData = Variant<OperationalNodeData, CommissionNodeData>;

    StoredServerName mRecordName;     // Record name for what is parsed (SRV/PTR/TXT)
    StoredServerName mTargetHostName; // `Target` for the SRV record
    ServiceNameType mServiceNameType = ServiceNameType::kInvalid;
    CommonResolutionData mCommonResolutionData;
    ParsedRecordSpecificData mSpecificResolutionData;
};

} // namespace Dnssd
} // namespace chip
