/*
 *
 *    Copyright (c) 2021 Project CHIP Authors
 *    All rights reserved.
 *
 *    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.
 */

/**
 *    @file
 *      Declaration of SetUp Code Pairer, a class that parses a given
 *      setup code and uses the extracted informations to discover and
 *      filter commissionables nodes, before initiating the pairing process.
 *
 */

#pragma once

#include <controller/DevicePairingDelegate.h>
#include <lib/core/CHIPError.h>
#include <lib/core/NodeId.h>
#include <lib/support/DLLUtil.h>
#include <platform/CHIPDeviceConfig.h>
#include <protocols/secure_channel/RendezvousParameters.h>
#include <setup_payload/ManualSetupPayloadParser.h>
#include <setup_payload/QRCodeSetupPayloadParser.h>

#if CONFIG_NETWORK_LAYER_BLE
#include <ble/Ble.h>
#endif // CONFIG_NETWORK_BLE

#if CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING
#include <nfc/NFC.h>
#endif // CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING

#include <controller/DeviceDiscoveryDelegate.h>

#include <deque>
#include <optional>
#include <vector>

namespace chip {
namespace Controller {

class DeviceCommissioner;

/**
 * A class that represents a discovered device.  This includes both the inputs to discovery (via the
 * RendezvousParameters super-class), and the outputs from discovery (the PeerAddress in
 * RendezvousParameters but also some of our members like mHostName, mInterfaceId,
 * mLongDiscriminator).
 */
class SetUpCodePairerParameters : public RendezvousParameters
{
public:
    SetUpCodePairerParameters() = default;
    SetUpCodePairerParameters(const Dnssd::CommonResolutionData & data, std::optional<uint16_t> longDiscriminator, size_t index);
#if CONFIG_NETWORK_LAYER_BLE
    SetUpCodePairerParameters(BLE_CONNECTION_OBJECT connObj, std::optional<uint16_t> longDiscriminator, bool connected = true);
#endif // CONFIG_NETWORK_LAYER_BLE
    char mHostName[Dnssd::kHostNameMaxLength + 1] = {};
    Inet::InterfaceId mInterfaceId;

    // The long discriminator of the device that was actually discovered, if this is known.  This
    // differs from the mSetupDiscriminator member of RendezvousParameters in that the latter may be
    // a short discriminator from a numeric setup code (which may match multiple devices), while
    // this member, if set, is always a long discriminator that was actually advertised by the
    // device represented by our PeerAddress.
    std::optional<uint16_t> mLongDiscriminator = std::nullopt;
};

enum class SetupCodePairerBehaviour : uint8_t
{
    kCommission,
    kPaseOnly,
};

enum class DiscoveryType : uint8_t
{
    kDiscoveryNetworkOnly,
    kDiscoveryNetworkOnlyWithoutPASEAutoRetry,
    kAll,
};

class DLL_EXPORT SetUpCodePairer : public DevicePairingDelegate
#if CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING
    ,
                                   public Nfc::NFCReaderTransportDelegate
#endif
{
public:
    SetUpCodePairer(DeviceCommissioner * commissioner) : mCommissioner(commissioner) {}
    virtual ~SetUpCodePairer() {}

    CHIP_ERROR PairDevice(chip::NodeId remoteId, const char * setUpCode,
                          SetupCodePairerBehaviour connectionType              = SetupCodePairerBehaviour::kCommission,
                          DiscoveryType discoveryType                          = DiscoveryType::kAll,
                          Optional<Dnssd::CommonResolutionData> resolutionData = NullOptional);

    // Called by the DeviceCommissioner to notify that we have discovered a new device.
    void NotifyCommissionableDeviceDiscovered(const chip::Dnssd::DiscoveredNodeData & nodeData);

    void SetSystemLayer(System::Layer * systemLayer) { mSystemLayer = systemLayer; };

#if CONFIG_NETWORK_LAYER_BLE
    void SetBleLayer(Ble::BleLayer * bleLayer) { mBleLayer = bleLayer; };
#endif // CONFIG_NETWORK_LAYER_BLE

    // Stop ongoing discovery / pairing of the specified node, or of
    // whichever node we're pairing if kUndefinedNodeId is passed.
    bool StopPairing(NodeId remoteId = kUndefinedNodeId);

private:
    // DevicePairingDelegate implementation.
    void OnStatusUpdate(DevicePairingDelegate::Status status) override;
    void OnPairingComplete(CHIP_ERROR error, const std::optional<RendezvousParameters> & rendezvousParameters,
                           const std::optional<SetupPayload> & setupPayload) override;
    void OnPairingDeleted(CHIP_ERROR error) override;
    void OnCommissioningComplete(NodeId deviceId, CHIP_ERROR error) override;

#if CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING
    // Nfc::NFCReaderTransportDelegate implementation
    void OnTagDiscovered(const chip::Nfc::NFCTag::Identifier & identifer) override;
    void OnTagDiscoveryFailed(CHIP_ERROR error) override;
#endif

    CHIP_ERROR Connect();
    CHIP_ERROR StartDiscoveryOverBLE();
    CHIP_ERROR StopDiscoveryOverBLE();
    CHIP_ERROR StartDiscoveryOverDNSSD();
    CHIP_ERROR StopDiscoveryOverDNSSD();
    CHIP_ERROR StartDiscoveryOverWiFiPAF();
    CHIP_ERROR StopDiscoveryOverWiFiPAF();
    CHIP_ERROR StartDiscoveryOverNFC();
    CHIP_ERROR StopDiscoveryOverNFC();

    // Returns whether we have kicked off a new connection attempt.
    bool ConnectToDiscoveredDevice();

    // Stop attempts to discover more things to connect to, but keep trying to
    // connect to the ones we have already discovered.
    void StopAllDiscoveryAttempts();

    // Reset our mWaitingForDiscovery/mDiscoveredParameters state to indicate no
    // pending work.
    void ResetDiscoveryState();

    // Get ready to start PASE establishment via mCommissioner.  Sets up
    // whatever state is needed for that.
    void ExpectPASEEstablishment();

    // PASE establishment by mCommissioner has completed: we either have a PASE
    // session now or we failed to set one up, but we are done waiting on
    // mCommissioner.
    void PASEEstablishmentComplete();

    // Called when PASE establishment fails.
    //
    // May start a new PASE establishment.
    //
    // Will return whether we might in fact have more rendezvous parameters to
    // try (e.g. because we started a new PASE establishment or are waiting on
    // more device discovery).
    //
    // The commissioner can use the return value to decide whether pairing has
    // actually failed or not.
    bool TryNextRendezvousParameters();

    // True if we are still waiting on discovery to possibly produce new
    // RendezvousParameters in the future.
    bool DiscoveryInProgress() const;

    // Not an enum class because we use this for indexing into arrays.
    enum TransportTypes
    {
        kBLETransport = 0,
        kIPTransport,
        kWiFiPAFTransport,
#if CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING
        kNFCTransport,
#endif
        kTransportTypeCount,
    };

    void NotifyCommissionableDeviceDiscovered(const chip::Dnssd::CommonResolutionData & resolutionData,
                                              std::optional<uint16_t> matchedLongDiscriminator);

    static void OnDeviceDiscoveredTimeoutCallback(System::Layer * layer, void * context);

#if CONFIG_NETWORK_LAYER_BLE
    Ble::BleLayer * mBleLayer = nullptr;
    void OnDiscoveredDeviceOverBle(BLE_CONNECTION_OBJECT connObj, std::optional<uint16_t> matchedLongDiscriminator);
    void OnBLEDiscoveryError(CHIP_ERROR err);
    /////////// BLEConnectionDelegate Callbacks /////////
    static void OnDiscoveredDeviceOverBleSuccess(void * appState, BLE_CONNECTION_OBJECT connObj);
    static void OnDiscoveredDeviceWithDiscriminatorOverBleSuccess(void * appState, uint16_t matchedLongDiscriminator,
                                                                  BLE_CONNECTION_OBJECT connObj);
    static void OnDiscoveredDeviceOverBleError(void * appState, CHIP_ERROR err);
#endif // CONFIG_NETWORK_LAYER_BLE
#if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
    void OnDiscoveredDeviceOverWifiPAF();
    void OnWifiPAFDiscoveryError(CHIP_ERROR err);
    static void OnWiFiPAFSubscribeComplete(void * appState);
    static void OnWiFiPAFSubscribeError(void * appState, CHIP_ERROR err);
#endif

    bool NodeMatchesCurrentFilter(const Dnssd::DiscoveredNodeData & nodeData) const;
    static bool IdIsPresent(uint16_t vendorOrProductID);

    bool ShouldDiscoverUsing(RendezvousInformationFlag commissioningChannel) const;

    // kNotAvailable represents unavailable vendor/product ID values in setup payloads.
    static constexpr uint16_t kNotAvailable = 0;

    DeviceCommissioner * mCommissioner       = nullptr;
    System::Layer * mSystemLayer             = nullptr;
    chip::NodeId mRemoteId                   = kUndefinedNodeId;
    SetupCodePairerBehaviour mConnectionType = SetupCodePairerBehaviour::kCommission;
    DiscoveryType mDiscoveryType             = DiscoveryType::kAll;
    std::vector<SetupPayload> mSetupPayloads;

    // The payload we are using for our current PASE connection attempt.  Only
    // set while we are attempting PASE.
    std::optional<SetupPayload> mCurrentPASEPayload;

    // While we are trying to pair, we intercept the DevicePairingDelegate
    // notifications from mCommissioner.  We want to make sure we send them on
    // to the original pairing delegate, if any.
    DevicePairingDelegate * mPairingDelegate = nullptr;

    // Boolean will be set to true if we currently have an async discovery
    // process happening via the relevant transport.
    bool mWaitingForDiscovery[kTransportTypeCount] = { false };

    // Double ended-queue of things we have discovered but not tried connecting to yet.  The
    // general discovery/pairing process will terminate once this queue is empty
    // and all the booleans in mWaitingForDiscovery are false.
    std::deque<SetUpCodePairerParameters> mDiscoveredParameters;

    // Current thing we are trying to connect to over UDP. If a PASE connection fails with
    // a CHIP_ERROR_TIMEOUT, the discovered parameters will be used to ask the
    // mdns daemon to invalidate its caches.
    Optional<SetUpCodePairerParameters> mCurrentPASEParameters;

    // mWaitingForPASE is true if we have called either
    // EstablishPASEConnection or PairDevice on mCommissioner and are now just
    // waiting to see whether that works.
    bool mWaitingForPASE = false;

    // mLastPASEError is the error from the last OnPairingComplete call we got.
    CHIP_ERROR mLastPASEError = CHIP_NO_ERROR;
};

} // namespace Controller
} // namespace chip
