/*
 * 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.
 */

#include "chre/platform/android/host_link.h"

#include "chre/core/event_loop_manager.h"
#include "chre/platform/shared/host_protocol_common.h"
#include "chre/util/flatbuffers/helpers.h"
#include "chre/util/macros.h"
#include "chre/util/nested_data_ptr.h"
#include "chre_api/chre/version.h"
#include "chre_host/generated/host_messages_generated.h"

namespace chre {

static_assert(sizeof(uint16_t) <= sizeof(void *),
              "Pointer must at least fit a u16 for passing the host client ID");

/**
 * Assigns a vector the contents of a C-style, null-terminated string.
 *
 * @param vector The vector to assign with the contents of a string.
 * @param str The string to assign.
 */
void setVectorToString(std::vector<int8_t> *vector, const char *str) {
  *vector = std::vector<int8_t>(str, str + strlen(str));
}

/**
 * Sends a message to the host given a hostClientId.
 *
 * @param message The message to send to the host.
 * @param hostClientId The host who made the original request for which this is
 *        a reply.
 */
template <typename T>
void sendFlatbufferToHost(T &message, uint16_t hostClientId) {
  static_assert(fbs::ChreMessageTraits<typename T::TableType>::enum_value !=
                    fbs::ChreMessage::NONE,
                "Only works for message types supported by ChreMessageUnion");

  fbs::MessageContainerT container;
  container.message.Set(std::move(message));
  container.host_addr.reset(new fbs::HostAddress(hostClientId));

  ChreFlatBufferBuilder builder;
  auto containerOffset = CreateMessageContainer(builder, &container, nullptr);
  builder.Finish(containerOffset);

  SocketServerSingleton::get()->sendToClientById(
      builder.GetBufferPointer(), builder.GetSize(), hostClientId);
}

/**
 * Handles a message directed to a nanoapp from the system.
 *
 * @param message The message to deliver to a nanoapp.
 */
void handleNanoappMessage(const fbs::NanoappMessageT &message) {
  LOGD("handleNanoappMessage");
  HostCommsManager &manager =
      EventLoopManagerSingleton::get()->getHostCommsManager();
  manager.sendMessageToNanoappFromHost(
      message.app_id, message.message_type, message.host_endpoint,
      message.message.data(), message.message.size());
}

/**
 * Handles a request for information about this context hub instance.
 *
 * @param hostClientId The client ID on the host making the request.
 */
void handleHubInfoRequest(uint16_t hostClientId) {
  LOGD("handleHubInfoRequest");
  fbs::HubInfoResponseT response;
  setVectorToString(&response.name, "CHRE on Android");
  setVectorToString(&response.vendor, "Google");
  setVectorToString(
      &response.toolchain,
      "Android NDK API 26 (clang " STRINGIFY(__clang_major__) "." STRINGIFY(
          __clang_minor__) "." STRINGIFY(__clang_patchlevel__) ")");
  response.platform_version = 0;
  response.toolchain_version = ((__clang_major__ & 0xFF) << 24) |
                               ((__clang_minor__ & 0xFF) << 16) |
                               (__clang_patchlevel__ & 0xFFFF);
  response.peak_mips = 1000;
  response.stopped_power = 1000;
  response.sleep_power = 1000;
  response.peak_power = 10000;
  response.max_msg_len = CHRE_MESSAGE_TO_HOST_MAX_SIZE;
  response.platform_id = chreGetPlatformId();
  response.chre_platform_version = chreGetVersion();

  sendFlatbufferToHost(response, hostClientId);
}

/**
 * Handles a request from the host for a list of nanoapps.
 *
 * @param hostClientId The client ID on the host making the request.
 */
void handleNanoappListRequest(uint16_t hostClientId) {
  auto callback = [](uint16_t /*type*/, void *data, void * /*extraData*/) {
    uint16_t cbHostClientId = NestedDataPtr<uint16_t>(data);

    auto nanoappAddCallback = [](const Nanoapp *nanoapp, void *data) {
      auto response = static_cast<fbs::NanoappListResponseT *>(data);
      auto nanoappListEntry =
          std::unique_ptr<fbs::NanoappListEntryT>(new fbs::NanoappListEntryT());
      nanoappListEntry->app_id = nanoapp->getAppId();
      nanoappListEntry->version = nanoapp->getAppVersion();
      nanoappListEntry->enabled = true;
      nanoappListEntry->is_system = nanoapp->isSystemNanoapp();
      nanoappListEntry->permissions = nanoapp->getAppPermissions();
      response->nanoapps.push_back(std::move(nanoappListEntry));
    };

    fbs::NanoappListResponseT response;
    EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
    eventLoop.forEachNanoapp(nanoappAddCallback, &response);

    sendFlatbufferToHost(response, cbHostClientId);
  };

  LOGD("handleNanoappListRequest");
  EventLoopManagerSingleton::get()->deferCallback(
      SystemCallbackType::NanoappListResponse,
      NestedDataPtr<uint16_t>(hostClientId), callback);
}

/**
 * Handles a request to load a nanoapp.
 *
 * @param hostClientId The client ID on the host making the request.
 * @param loadRequest The details of the nanoapp load request.
 */
void handleLoadNanoappRequest(uint16_t hostClientId,
                              const fbs::LoadNanoappRequestT &loadRequest) {
  LOGD("handleLoadNanoappRequest");
}

/**
 * Handles a request to unload a nanoapp.
 *
 * @param hostClientId The client ID on the host making the request.
 * @param unloadRequest The details of the nanoapp unload request.
 */
void handleUnloadNanoappRequest(
    uint16_t hostClientId, const fbs::UnloadNanoappRequestT &unloadRequest) {
  LOGD("handleUnloadNanoappRequest");
}

/**
 * Handles a request for a debug dump.
 *
 * @param hostClientId The client OD on the host making the request.
 */
void handleDebugDumpRequest(uint16_t hostClientId) {
  LOGD("handleDebugDumpRequest");
}

bool handleMessageFromHost(void *message, size_t length) {
  bool success = HostProtocolCommon::verifyMessage(message, length);
  if (success) {
    fbs::MessageContainerT container;
    fbs::GetMessageContainer(message)->UnPackTo(&container);
    uint16_t hostClientId = container.host_addr->client_id();
    switch (container.message.type) {
      case fbs::ChreMessage::NanoappMessage:
        handleNanoappMessage(*container.message.AsNanoappMessage());
        break;

      case fbs::ChreMessage::HubInfoRequest:
        handleHubInfoRequest(hostClientId);
        break;

      case fbs::ChreMessage::NanoappListRequest:
        handleNanoappListRequest(hostClientId);
        break;

      case fbs::ChreMessage::LoadNanoappRequest:
        handleLoadNanoappRequest(hostClientId,
                                 *container.message.AsLoadNanoappRequest());
        break;

      case fbs::ChreMessage::UnloadNanoappRequest:
        handleUnloadNanoappRequest(hostClientId,
                                   *container.message.AsUnloadNanoappRequest());
        break;

      case fbs::ChreMessage::DebugDumpRequest:
        handleDebugDumpRequest(hostClientId);
        break;

      default:
        LOGW("Got invalid/unexpected message type %" PRIu8,
             static_cast<uint8_t>(container.message.type));
        success = false;
    }
  }

  return success;
}

}  // namespace chre
