/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * 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.
 */

#ifndef ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_SOCKET_H
#define ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_SOCKET_H

#include <condition_variable>
#include <mutex>

#include "chre_host/fragmented_load_transaction.h"
#include "chre_host/host_protocol_host.h"
#include "chre_host/socket_client.h"

#ifdef CHRE_HAL_SOCKET_METRICS_ENABLED
#include <aidl/android/frameworks/stats/IStats.h>

#include "chre_host/metrics_reporter.h"
#endif  //  CHRE_HAL_SOCKET_METRICS_ENABLED

namespace android {
namespace hardware {
namespace contexthub {
namespace common {
namespace implementation {

/**
 * Callback interface to be used for
 * HalChreSocketConnection::registerCallback().
 */
class IChreSocketCallback {
 public:
  virtual ~IChreSocketCallback() {}

  /**
   * Invoked when a transaction completed
   *
   * @param transactionId The ID of the transaction.
   * @param success true if the transaction succeeded.
   */
  virtual void onTransactionResult(uint32_t transactionId, bool success) = 0;

  /**
   * Invoked when a nanoapp sends a message to this socket client.
   *
   * @param message The message.
   */
  virtual void onNanoappMessage(
      const ::chre::fbs::NanoappMessageT &message) = 0;

  /**
   * Invoked to provide a list of nanoapps previously requested by
   * HalChreSocketConnection::queryNanoapps().
   *
   * @param response The list response.
   */
  virtual void onNanoappListResponse(
      const ::chre::fbs::NanoappListResponseT &response) = 0;

  /**
   * Invoked when CHRE restarts.
   */
  virtual void onContextHubRestarted() = 0;

  /**
   * Invoked when a data is available as a result of a debug dump request
   * through HalChreSocketConnection::requestDebugDump().
   *
   * @param data The debug dump data.
   */
  virtual void onDebugDumpData(const ::chre::fbs::DebugDumpDataT &data) = 0;

  /**
   * Invoked when a debug dump is completed.
   *
   * @param response The debug dump response.
   */
  virtual void onDebugDumpComplete(
      const ::chre::fbs::DebugDumpResponseT &response) = 0;
};

/**
 * A helper class that can be used to connect to the CHRE socket.
 */
class HalChreSocketConnection {
 public:
  HalChreSocketConnection(IChreSocketCallback *callback);

  bool getContextHubs(::chre::fbs::HubInfoResponseT *response);

  bool sendMessageToHub(uint64_t nanoappId, uint32_t messageType,
                        uint16_t hostEndpointId, const unsigned char *payload,
                        size_t payloadLength);

  bool sendDebugConfiguration();

  bool loadNanoapp(chre::FragmentedLoadTransaction &transaction);

  bool unloadNanoapp(uint64_t appId, uint32_t transactionId);

  bool queryNanoapps();

  bool requestDebugDump();

  bool sendSettingChangedNotification(::chre::fbs::Setting fbsSetting,
                                      ::chre::fbs::SettingState fbsState);

  bool onHostEndpointConnected(uint16_t hostEndpointId, uint8_t type,
                               const std::string &package_name,
                               const std::string &attribution_tag);

  bool onHostEndpointDisconnected(uint16_t hostEndpointId);

  /**
   * Returns true if there exists a pending load transaction; false otherwise.
   *
   * @return true                     there exists a pending load transaction.
   * @return false                    there does not exist a pending load
   * transaction.
   */
  bool isLoadTransactionPending();

 private:
  class SocketCallbacks : public ::android::chre::SocketClient::ICallbacks,
                          public ::android::chre::IChreMessageHandlers {
   public:
    explicit SocketCallbacks(HalChreSocketConnection &parent,
                             IChreSocketCallback *callback);

    void onMessageReceived(const void *data, size_t length) override;
    void onConnected() override;
    void onDisconnected() override;
    void handleNanoappMessage(
        const ::chre::fbs::NanoappMessageT &message) override;
    void handleHubInfoResponse(
        const ::chre::fbs::HubInfoResponseT &response) override;
    void handleNanoappListResponse(
        const ::chre::fbs::NanoappListResponseT &response) override;
    void handleLoadNanoappResponse(
        const ::chre::fbs::LoadNanoappResponseT &response) override;
    void handleUnloadNanoappResponse(
        const ::chre::fbs::UnloadNanoappResponseT &response) override;
    void handleDebugDumpData(const ::chre::fbs::DebugDumpDataT &data) override;
    void handleDebugDumpResponse(
        const ::chre::fbs::DebugDumpResponseT &response) override;

   private:
    HalChreSocketConnection &mParent;
    IChreSocketCallback *mCallback = nullptr;
    bool mHaveConnected = false;

#ifdef CHRE_HAL_SOCKET_METRICS_ENABLED
    long mLastClearedTimestamp = 0;
    static constexpr uint32_t kOneDayinMillis = 24 * 60 * 60 * 1000;
    static constexpr uint16_t kMaxDailyReportedApWakeUp = 200;
    uint16_t mNanoappWokeUpCount = 0;
    std::mutex mNanoappWokeApCountMutex;
#endif  // CHRE_HAL_SOCKET_METRICS_ENABLED
  };

  sp<SocketCallbacks> mSocketCallbacks;

  ::android::chre::SocketClient mClient;

  ::chre::fbs::HubInfoResponseT mHubInfoResponse;
  bool mHubInfoValid = false;
  std::mutex mHubInfoMutex;
  std::condition_variable mHubInfoCond;

  // The pending fragmented load request
  uint32_t mCurrentFragmentId = 0;
  std::optional<chre::FragmentedLoadTransaction> mPendingLoadTransaction;
  std::mutex mPendingLoadTransactionMutex;

#ifdef CHRE_HAL_SOCKET_METRICS_ENABLED
  android::chre::MetricsReporter mMetricsReporter;
#endif  // CHRE_HAL_SOCKET_METRICS_ENABLED

  /**
   * Checks to see if a load response matches the currently pending
   * fragmented load transaction. mPendingLoadTransactionMutex must
   * be acquired prior to calling this function.
   *
   * @param response the received load response
   *
   * @return true if the response matches a pending load transaction
   *         (if any), false otherwise
   */
  bool isExpectedLoadResponseLocked(
      const ::chre::fbs::LoadNanoappResponseT &response);

  /**
   * Sends a fragmented load request to CHRE. The caller must ensure that
   * transaction.isComplete() returns false prior to invoking this method.
   *
   * @param transaction the FragmentedLoadTransaction object
   *
   * @return true if the load succeeded
   */
  bool sendFragmentedLoadNanoAppRequest(
      chre::FragmentedLoadTransaction &transaction);

#ifdef CHRE_HAL_SOCKET_METRICS_ENABLED
  // TODO(b/298459533): Remove this when the flag_log_nanoapp_load_metrics flag
  // is cleaned up
  /**
   * Create and report CHRE vendor atom and send it to stats_client
   *
   * @param atom the vendor atom to be reported
   */
  void reportMetric(const aidl::android::frameworks::stats::VendorAtom atom);
#endif  // CHRE_HAL_SOCKET_METRICS_ENABLED
  // TODO(b/298459533): Remove end
};

}  // namespace implementation
}  // namespace common
}  // namespace contexthub
}  // namespace hardware
}  // namespace android

#endif  // ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_SOCKET_H