/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Nanache 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.
 */

#include <android-base/logging.h>

#include <VtsCoreUtil.h>
#include <android/hardware/wifi/1.0/IWifi.h>
#include <android/hardware/wifi/1.0/IWifiNanIface.h>
#include <android/hardware/wifi/1.0/IWifiNanIfaceEventCallback.h>
#include <android/hardware/wifi/1.5/IWifiNanIface.h>
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
#include <chrono>
#include <condition_variable>
#include <mutex>

#include "wifi_hidl_call_util.h"
#include "wifi_hidl_test_utils.h"

using namespace ::android::hardware::wifi::V1_0;

using ::android::sp;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::hardware::wifi::V1_0::IWifi;

#define TIMEOUT_PERIOD 10

/**
 * Fixture to use for all NAN Iface HIDL interface tests.
 */
class WifiNanIfaceHidlTest : public ::testing::TestWithParam<std::string> {
   public:
    virtual void SetUp() override {
        if (!::testing::deviceSupportsFeature("android.hardware.wifi.aware"))
            GTEST_SKIP() << "Skipping this test since NAN is not supported.";

        // Make sure test starts with a clean state
        stopWifi(GetInstanceName());

        iwifiNanIface = getWifiNanIface(GetInstanceName());
        ASSERT_NE(nullptr, iwifiNanIface.get());
        ASSERT_EQ(WifiStatusCode::SUCCESS,
                  HIDL_INVOKE(iwifiNanIface, registerEventCallback,
                              new WifiNanIfaceEventCallback(*this))
                      .code);
    }

    virtual void TearDown() override { stopWifi(GetInstanceName()); }

    /* Used as a mechanism to inform the test about data/event callback */
    inline void notify() {
      std::unique_lock<std::mutex> lock(mtx_);
      count_++;
      cv_.notify_one();
    }

    enum CallbackType {
        INVALID = -2,
        ANY_CALLBACK = -1,

        NOTIFY_CAPABILITIES_RESPONSE = 0,
        NOTIFY_ENABLE_RESPONSE,
        NOTIFY_CONFIG_RESPONSE,
        NOTIFY_DISABLE_RESPONSE,
        NOTIFY_START_PUBLISH_RESPONSE,
        NOTIFY_STOP_PUBLISH_RESPONSE,
        NOTIFY_START_SUBSCRIBE_RESPONSE,
        NOTIFY_STOP_SUBSCRIBE_RESPONSE,
        NOTIFY_TRANSMIT_FOLLOWUP_RESPONSE,
        NOTIFY_CREATE_DATA_INTERFACE_RESPONSE,
        NOTIFY_DELETE_DATA_INTERFACE_RESPONSE,
        NOTIFY_INITIATE_DATA_PATH_RESPONSE,
        NOTIFY_RESPOND_TO_DATA_PATH_INDICATION_RESPONSE,
        NOTIFY_TERMINATE_DATA_PATH_RESPONSE,

        EVENT_CLUSTER_EVENT,
        EVENT_DISABLED,
        EVENT_PUBLISH_TERMINATED,
        EVENT_SUBSCRIBE_TERMINATED,
        EVENT_MATCH,
        EVENT_MATCH_EXPIRED,
        EVENT_FOLLOWUP_RECEIVED,
        EVENT_TRANSMIT_FOLLOWUP,
        EVENT_DATA_PATH_REQUEST,
        EVENT_DATA_PATH_CONFIRM,
        EVENT_DATA_PATH_TERMINATED
    };

    /* Test code calls this function to wait for data/event callback */
    /* Must set callbackType = INVALID before call this function */
    inline std::cv_status wait(CallbackType waitForCallbackType) {
      std::unique_lock<std::mutex> lock(mtx_);

      EXPECT_NE(INVALID, waitForCallbackType); // can't ASSERT in a non-void-returning method

      std::cv_status status = std::cv_status::no_timeout;
      auto now = std::chrono::system_clock::now();
      while (count_ == 0) {
        status = cv_.wait_until(lock, now + std::chrono::seconds(TIMEOUT_PERIOD));
        if (status == std::cv_status::timeout) return status;
        if (waitForCallbackType != ANY_CALLBACK && callbackType != INVALID
            && callbackType != waitForCallbackType) {
          count_--;
        }
      }
      count_--;
      return status;
    }

    class WifiNanIfaceEventCallback: public IWifiNanIfaceEventCallback {
      WifiNanIfaceHidlTest& parent_;

     public:
      WifiNanIfaceEventCallback(WifiNanIfaceHidlTest& parent) : parent_(parent) {};

      virtual ~WifiNanIfaceEventCallback() = default;

      Return<void> notifyCapabilitiesResponse(
            uint16_t id,
            const WifiNanStatus& status,
            const NanCapabilities& capabilities) override {
        parent_.callbackType = NOTIFY_CAPABILITIES_RESPONSE;

        parent_.id = id;
        parent_.status = status;
        parent_.capabilities = capabilities;

        parent_.notify();
        return Void();
      }

      Return<void> notifyEnableResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_ENABLE_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyConfigResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_CONFIG_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyDisableResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_DISABLE_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyStartPublishResponse(
            uint16_t id,
            const WifiNanStatus& status,
            uint8_t sessionId) override {
        parent_.callbackType = NOTIFY_START_PUBLISH_RESPONSE;

        parent_.id = id;
        parent_.status = status;
        parent_.sessionId = sessionId;

        parent_.notify();
        return Void();
      }

      Return<void> notifyStopPublishResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_STOP_PUBLISH_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyStartSubscribeResponse(
            uint16_t id,
            const WifiNanStatus& status,
            uint8_t sessionId) override {
        parent_.callbackType = NOTIFY_START_SUBSCRIBE_RESPONSE;

        parent_.id = id;
        parent_.status = status;
        parent_.sessionId = sessionId;

        parent_.notify();
        return Void();
      }

      Return<void> notifyStopSubscribeResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_STOP_SUBSCRIBE_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyTransmitFollowupResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_TRANSMIT_FOLLOWUP_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyCreateDataInterfaceResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_CREATE_DATA_INTERFACE_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyDeleteDataInterfaceResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_DELETE_DATA_INTERFACE_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyInitiateDataPathResponse(
            uint16_t id,
            const WifiNanStatus& status,
            uint32_t ndpInstanceId) override {
        parent_.callbackType = NOTIFY_INITIATE_DATA_PATH_RESPONSE;

        parent_.id = id;
        parent_.status = status;
        parent_.ndpInstanceId = ndpInstanceId;

        parent_.notify();
        return Void();
      }

      Return<void> notifyRespondToDataPathIndicationResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_RESPOND_TO_DATA_PATH_INDICATION_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> notifyTerminateDataPathResponse(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = NOTIFY_TERMINATE_DATA_PATH_RESPONSE;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> eventClusterEvent(
            const NanClusterEventInd& event) override {
        parent_.callbackType = EVENT_CLUSTER_EVENT;

        parent_.nanClusterEventInd = event;

        parent_.notify();
        return Void();
      }

      Return<void> eventDisabled(
            const WifiNanStatus& status) override {
        parent_.callbackType = EVENT_DISABLED;

        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> eventPublishTerminated(
            uint8_t sessionId,
            const WifiNanStatus& status) override {
        parent_.callbackType = EVENT_PUBLISH_TERMINATED;

        parent_.sessionId = sessionId;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> eventSubscribeTerminated(
            uint8_t sessionId,
            const WifiNanStatus& status) override {
        parent_.callbackType = EVENT_SUBSCRIBE_TERMINATED;

        parent_.sessionId = sessionId;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> eventMatch(
            const NanMatchInd& event) override {
        parent_.callbackType = EVENT_MATCH;

        parent_.nanMatchInd = event;

        parent_.notify();
        return Void();
      }

      Return<void> eventMatchExpired(
            uint8_t discoverySessionId,
            uint32_t peerId) override {
        parent_.callbackType = EVENT_MATCH_EXPIRED;

        parent_.sessionId = discoverySessionId;
        parent_.peerId = peerId;

        parent_.notify();
        return Void();
      }

      Return<void> eventFollowupReceived(
            const NanFollowupReceivedInd& event) override {
        parent_.callbackType = EVENT_FOLLOWUP_RECEIVED;

        parent_.nanFollowupReceivedInd = event;

        parent_.notify();
        return Void();
      }

      Return<void> eventTransmitFollowup(
            uint16_t id,
            const WifiNanStatus& status) override {
        parent_.callbackType = EVENT_TRANSMIT_FOLLOWUP;

        parent_.id = id;
        parent_.status = status;

        parent_.notify();
        return Void();
      }

      Return<void> eventDataPathRequest(
            const NanDataPathRequestInd& event) override {
        parent_.callbackType = EVENT_DATA_PATH_REQUEST;

        parent_.nanDataPathRequestInd = event;

        parent_.notify();
        return Void();
      }

      Return<void> eventDataPathConfirm(
            const NanDataPathConfirmInd& event) override {
        parent_.callbackType = EVENT_DATA_PATH_CONFIRM;

        parent_.nanDataPathConfirmInd = event;

        parent_.notify();
        return Void();
      }

      Return<void> eventDataPathTerminated(
            uint32_t ndpInstanceId) override {
        parent_.callbackType = EVENT_DATA_PATH_TERMINATED;

        parent_.ndpInstanceId = ndpInstanceId;

        parent_.notify();
        return Void();
      }
    };

    private:
      // synchronization objects
      std::mutex mtx_;
      std::condition_variable cv_;
      int count_;

    protected:
      android::sp<IWifiNanIface> iwifiNanIface;

      // Data from IWifiNanIfaceEventCallback callbacks: this is the collection of all
      // arguments to all callbacks. They are set by the callback (notifications or
      // events) and can be retrieved by tests.
      CallbackType callbackType;
      uint16_t id;
      WifiNanStatus status;
      NanCapabilities capabilities;
      uint8_t sessionId;
      uint32_t ndpInstanceId;
      NanClusterEventInd nanClusterEventInd;
      NanMatchInd nanMatchInd;
      uint32_t peerId;
      NanFollowupReceivedInd nanFollowupReceivedInd;
      NanDataPathRequestInd nanDataPathRequestInd;
      NanDataPathConfirmInd nanDataPathConfirmInd;

      std::string GetInstanceName() { return GetParam(); }
};

/*
 * Create:
 * Ensures that an instance of the IWifiNanIface proxy object is
 * successfully created.
 */
TEST_P(WifiNanIfaceHidlTest, Create) {
    // The creation of a proxy object is tested as part of SetUp method.
}

/*
 * Fail: use past destruction
 * Ensure that API calls fail with ERROR_WIFI_IFACE_INVALID when using an interface once wifi
 * is disabled.
 */
TEST_P(WifiNanIfaceHidlTest, FailOnIfaceInvalid) {
    stopWifi(GetInstanceName());
    android::sp<IWifiNanIface> iwifiNanIface =
        getWifiNanIface(GetInstanceName());
    ASSERT_NE(nullptr, iwifiNanIface.get());
    stopWifi(GetInstanceName());
    sleep(5);  // make sure that all chips/interfaces are invalidated
    ASSERT_EQ(WifiStatusCode::ERROR_WIFI_IFACE_INVALID,
              HIDL_INVOKE(iwifiNanIface, getCapabilitiesRequest, 0).code);
}

/*
 * getCapabilitiesRequest: validate that returns capabilities.
 */
TEST_P(WifiNanIfaceHidlTest, getCapabilitiesRequest) {
    uint16_t inputCmdId = 10;
    callbackType = INVALID;
    sp<::android::hardware::wifi::V1_5::IWifiNanIface> iface_converted =
        ::android::hardware::wifi::V1_5::IWifiNanIface::castFrom(iwifiNanIface);
    if (iface_converted != nullptr) {
        ASSERT_EQ(WifiStatusCode::ERROR_NOT_SUPPORTED,
                  HIDL_INVOKE(iwifiNanIface, getCapabilitiesRequest, inputCmdId)
                      .code);
        // Skip this test since this API is deprecated in this newer HAL version
        return;
    }

    ASSERT_EQ(
        WifiStatusCode::SUCCESS,
        HIDL_INVOKE(iwifiNanIface, getCapabilitiesRequest, inputCmdId).code);
    // wait for a callback
    ASSERT_EQ(std::cv_status::no_timeout, wait(NOTIFY_CAPABILITIES_RESPONSE));
    ASSERT_EQ(NOTIFY_CAPABILITIES_RESPONSE, callbackType);
    ASSERT_EQ(id, inputCmdId);

    // check for reasonable capability values
    EXPECT_GT(capabilities.maxConcurrentClusters, (unsigned int)0);
    EXPECT_GT(capabilities.maxPublishes, (unsigned int)0);
    EXPECT_GT(capabilities.maxSubscribes, (unsigned int)0);
    EXPECT_EQ(capabilities.maxServiceNameLen, (unsigned int)255);
    EXPECT_EQ(capabilities.maxMatchFilterLen, (unsigned int)255);
    EXPECT_GT(capabilities.maxTotalMatchFilterLen, (unsigned int)255);
    EXPECT_EQ(capabilities.maxServiceSpecificInfoLen, (unsigned int)255);
    EXPECT_GE(capabilities.maxExtendedServiceSpecificInfoLen,
              (unsigned int)255);
    EXPECT_GT(capabilities.maxNdiInterfaces, (unsigned int)0);
    EXPECT_GT(capabilities.maxNdpSessions, (unsigned int)0);
    EXPECT_GT(capabilities.maxAppInfoLen, (unsigned int)0);
    EXPECT_GT(capabilities.maxQueuedTransmitFollowupMsgs, (unsigned int)0);
    EXPECT_GT(capabilities.maxSubscribeInterfaceAddresses, (unsigned int)0);
    EXPECT_NE(capabilities.supportedCipherSuites, (unsigned int)0);
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiNanIfaceHidlTest);
INSTANTIATE_TEST_SUITE_P(
    PerInstance, WifiNanIfaceHidlTest,
    testing::ValuesIn(
        android::hardware::getAllHalInstanceNames(IWifi::descriptor)),
    android::hardware::PrintInstanceNameToString);
