/*
 * Copyright (C) 2017 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.
 */

#define LOG_TAG "ContextHubHal"
#define LOG_NDEBUG 0

#include "generic_context_hub.h"

#include <chrono>
#include <cinttypes>
#include <vector>

#include <utils/Log.h>

namespace android {
namespace hardware {
namespace contexthub {
namespace V1_0 {
namespace implementation {

using ::android::hardware::Return;
using ::android::hardware::contexthub::V1_0::AsyncEventType;
using ::android::hardware::contexthub::V1_0::Result;
using ::android::hardware::contexthub::V1_0::TransactionResult;
using ::android::chre::HostProtocolHost;
using ::flatbuffers::FlatBufferBuilder;

// Aliased for consistency with the way these symbols are referenced in
// CHRE-side code
namespace fbs = ::chre::fbs;

namespace {

constexpr uint32_t kDefaultHubId = 0;

// TODO: remove this macro once all methods are implemented
#define UNUSED(param) (void) (param)

constexpr uint8_t extractChreApiMajorVersion(uint32_t chreVersion) {
  return static_cast<uint8_t>(chreVersion >> 24);
}

constexpr uint8_t extractChreApiMinorVersion(uint32_t chreVersion) {
  return static_cast<uint8_t>(chreVersion >> 16);
}

constexpr uint16_t extractChrePatchVersion(uint32_t chreVersion) {
  return static_cast<uint16_t>(chreVersion);
}

}  // anonymous namespace

GenericContextHub::GenericContextHub() {
  constexpr char kChreSocketName[] = "chre";

  mSocketCallbacks = new SocketCallbacks(*this);
  if (!mClient.connectInBackground(kChreSocketName, mSocketCallbacks)) {
    ALOGE("Couldn't start socket client");
  }
}

Return<void> GenericContextHub::getHubs(getHubs_cb _hidl_cb) {
  constexpr auto kHubInfoQueryTimeout = std::chrono::seconds(5);
  std::vector<ContextHub> hubs;
  ALOGV("%s", __func__);

  // If we're not connected yet, give it some time
  int maxSleepIterations = 50;
  while (!mHubInfoValid && !mClient.isConnected() && --maxSleepIterations > 0) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  if (!mClient.isConnected()) {
    ALOGE("Couldn't connect to hub daemon");
  } else if (!mHubInfoValid) {
    // We haven't cached the hub details yet, so send a request and block
    // waiting on a response
    std::unique_lock<std::mutex> lock(mHubInfoMutex);
    FlatBufferBuilder builder;
    HostProtocolHost::encodeHubInfoRequest(builder);

    ALOGD("Sending hub info request");
    if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
      ALOGE("Couldn't send hub info request");
    } else {
      mHubInfoCond.wait_for(lock, kHubInfoQueryTimeout,
                            [this]() { return mHubInfoValid; });
    }
  }

  if (mHubInfoValid) {
    hubs.push_back(mHubInfo);
  } else {
    ALOGE("Unable to get hub info from CHRE");
  }

  _hidl_cb(hubs);
  return Void();
}

Return<Result> GenericContextHub::registerCallback(
    uint32_t hubId, const sp<IContexthubCallback>& cb) {
  Result result;
  ALOGV("%s", __func__);

  // TODO: currently we only support 1 hub behind this HAL implementation
  if (hubId == kDefaultHubId) {
    mCallbacks = cb; // TODO: special handling for null?
    result = Result::OK;
  } else {
    result = Result::BAD_PARAMS;
  }

  return result;
}

Return<Result> GenericContextHub::sendMessageToHub(uint32_t hubId,
                                                   const ContextHubMsg& msg) {
  Result result;
  ALOGV("%s", __func__);

  if (hubId != kDefaultHubId) {
    result = Result::BAD_PARAMS;
  } else {
    FlatBufferBuilder builder(1024);
    HostProtocolHost::encodeNanoappMessage(
        builder, msg.appName, msg.msgType, msg.hostEndPoint, msg.msg.data(),
        msg.msg.size());

    if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
      result = Result::UNKNOWN_FAILURE;
    } else {
      result = Result::OK;
    }
  }

  return result;
}

Return<Result> GenericContextHub::loadNanoApp(
    uint32_t hubId, const NanoAppBinary& appBinary, uint32_t transactionId) {
  Result result;
  ALOGV("%s", __func__);

  if (hubId != kDefaultHubId) {
    result = Result::BAD_PARAMS;
  } else {
    FlatBufferBuilder builder(128 + appBinary.customBinary.size());
    uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) |
                                (appBinary.targetChreApiMinorVersion << 16);
    HostProtocolHost::encodeLoadNanoappRequest(
        builder, transactionId, appBinary.appId, appBinary.appVersion,
        targetApiVersion, appBinary.customBinary);
    if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
      result = Result::UNKNOWN_FAILURE;
    } else {
      result = Result::OK;
    }
  }

  ALOGD("Attempted to send load nanoapp request for app of size %zu with ID "
        "0x%016" PRIx64 " as transaction ID %" PRIu32 ": result %" PRIu32,
        appBinary.customBinary.size(), appBinary.appId, transactionId, result);

  return result;
}

Return<Result> GenericContextHub::unloadNanoApp(
    uint32_t hubId, uint64_t appId, uint32_t transactionId) {
  // TODO
  UNUSED(hubId);
  UNUSED(appId);
  UNUSED(transactionId);
  ALOGV("%s", __func__);
  return Result::UNKNOWN_FAILURE;
}

Return<Result> GenericContextHub::enableNanoApp(
    uint32_t hubId, uint64_t appId, uint32_t transactionId) {
  // TODO
  UNUSED(hubId);
  UNUSED(appId);
  UNUSED(transactionId);
  ALOGV("%s", __func__);
  return Result::UNKNOWN_FAILURE;
}

Return<Result> GenericContextHub::disableNanoApp(
    uint32_t hubId, uint64_t appId, uint32_t transactionId) {
  // TODO
  UNUSED(hubId);
  UNUSED(appId);
  UNUSED(transactionId);
  ALOGV("%s", __func__);
  return Result::UNKNOWN_FAILURE;
}

Return<Result> GenericContextHub::queryApps(uint32_t hubId) {
  Result result;
  ALOGV("%s", __func__);

  if (hubId != kDefaultHubId) {
    result = Result::BAD_PARAMS;
  } else {
    FlatBufferBuilder builder(64);
    HostProtocolHost::encodeNanoappListRequest(builder);
    if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
      result = Result::UNKNOWN_FAILURE;
    } else {
      result = Result::OK;
    }
  }

  return Result::UNKNOWN_FAILURE;
}

GenericContextHub::SocketCallbacks::SocketCallbacks(GenericContextHub& parent)
    : mParent(parent) {}

void GenericContextHub::SocketCallbacks::onMessageReceived(const void *data,
                                                           size_t length) {
  if (!HostProtocolHost::decodeMessageFromChre(data, length, *this)) {
    ALOGE("Failed to decode message");
  }
}

void GenericContextHub::SocketCallbacks::onConnected() {
  if (mHaveConnected) {
    ALOGI("Reconnected to CHRE daemon");
    if (mParent.mCallbacks != nullptr) {
      mParent.mCallbacks->handleHubEvent(AsyncEventType::RESTARTED);
    }
  }
  mHaveConnected = true;
}

void GenericContextHub::SocketCallbacks::onDisconnected() {
  ALOGW("Lost connection to CHRE daemon");
}

void GenericContextHub::SocketCallbacks::handleNanoappMessage(
    uint64_t appId, uint32_t messageType, uint16_t hostEndpoint,
    const void *messageData, size_t messageDataLen) {
  // TODO: this is not thread-safe w/registerCallback... we need something else
  // to confirm that it's safe for us to invoke the callback, and likely a lock
  // on stuff
  if (mParent.mCallbacks != nullptr) {
    ContextHubMsg msg;
    msg.appName = appId;
    msg.hostEndPoint = hostEndpoint;
    msg.msgType = messageType;

    // Dropping const from messageData when we wrap it in hidl_vec here. This is
    // safe because we won't modify it here, and the ContextHubMsg we pass to
    // the callback is const.
    msg.msg.setToExternal(
        const_cast<uint8_t *>(static_cast<const uint8_t *>(messageData)),
        messageDataLen, false /* shouldOwn */);

    mParent.mCallbacks->handleClientMsg(msg);
  }
}

void GenericContextHub::SocketCallbacks::handleHubInfoResponse(
    const char *name, const char *vendor,
    const char *toolchain, uint32_t legacyPlatformVersion,
    uint32_t legacyToolchainVersion, float peakMips, float stoppedPower,
    float sleepPower, float peakPower, uint32_t maxMessageLen,
    uint64_t platformId, uint32_t version) {
  ALOGD("Got hub info response");

  std::lock_guard<std::mutex> lock(mParent.mHubInfoMutex);
  if (mParent.mHubInfoValid) {
    ALOGI("Ignoring duplicate/unsolicited hub info response");
  } else {
    mParent.mHubInfo.name = name;
    mParent.mHubInfo.vendor = vendor;
    mParent.mHubInfo.toolchain = toolchain;
    mParent.mHubInfo.platformVersion = legacyPlatformVersion;
    mParent.mHubInfo.toolchainVersion = legacyToolchainVersion;
    mParent.mHubInfo.hubId = kDefaultHubId;

    mParent.mHubInfo.peakMips = peakMips;
    mParent.mHubInfo.stoppedPowerDrawMw = stoppedPower;
    mParent.mHubInfo.sleepPowerDrawMw = sleepPower;
    mParent.mHubInfo.peakPowerDrawMw = peakPower;

    mParent.mHubInfo.maxSupportedMsgLen = maxMessageLen;
    mParent.mHubInfo.chrePlatformId = platformId;

    mParent.mHubInfo.chreApiMajorVersion = extractChreApiMajorVersion(version);
    mParent.mHubInfo.chreApiMinorVersion = extractChreApiMinorVersion(version);
    mParent.mHubInfo.chrePatchVersion = extractChrePatchVersion(version);

    mParent.mHubInfoValid = true;
    mParent.mHubInfoCond.notify_all();
  }
}

void GenericContextHub::SocketCallbacks::handleNanoappListResponse(
    const fbs::NanoappListResponseT& response) {
  std::vector<HubAppInfo> appInfoList;

  ALOGV("Got nanoapp list response with %zu apps", response.nanoapps.size());
  for (const std::unique_ptr<fbs::NanoappListEntryT>& nanoapp
         : response.nanoapps) {
    // TODO: determine if this is really required, and if so, have
    // HostProtocolHost strip out null entries as part of decode
    if (nanoapp == nullptr) {
      continue;
    }

    ALOGV("App 0x%016" PRIx64 " ver 0x%" PRIx32 " enabled %d system %d",
          nanoapp->app_id, nanoapp->version, nanoapp->enabled,
          nanoapp->is_system);
    if (!nanoapp->is_system) {
      HubAppInfo appInfo;

      appInfo.appId = nanoapp->app_id;
      appInfo.version = nanoapp->version;
      appInfo.enabled = nanoapp->enabled;

      appInfoList.push_back(appInfo);
    }
  }

  // TODO: make this thread-safe w/setCallback
  mParent.mCallbacks->handleAppsInfo(appInfoList);
}

void GenericContextHub::SocketCallbacks::handleLoadNanoappResponse(
    const ::chre::fbs::LoadNanoappResponseT& response) {
  ALOGV("Got load nanoapp response for transaction %" PRIu32 " with result %d",
        response.transaction_id, response.success);

  TransactionResult result = (response.success) ?
      TransactionResult::SUCCESS : TransactionResult::FAILURE;
  mParent.mCallbacks->handleTxnResult(response.transaction_id, result);
}

IContexthub* HIDL_FETCH_IContexthub(const char* /* name */) {
  return new GenericContextHub();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace contexthub
}  // namespace hardware
}  // namespace android