/*
 * Copyright (C) 2016 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/core/sensor_request_manager.h"

#include "chre_api/chre/version.h"
#include "chre/core/event_loop_manager.h"
#include "chre/platform/fatal_error.h"
#include "chre/util/system/debug_dump.h"

namespace chre {
namespace {

bool isSensorRequestValid(const Sensor& sensor,
                          const SensorRequest& sensorRequest) {
  bool isRequestContinuous = sensorModeIsContinuous(
      sensorRequest.getMode());
  bool isRequestOneShot = sensorModeIsOneShot(sensorRequest.getMode());
  uint64_t requestedInterval = sensorRequest.getInterval().toRawNanoseconds();
  SensorType sensorType = sensor.getSensorType();

  bool success = true;
  if (requestedInterval < sensor.getMinInterval()) {
    success = false;
    LOGE("Requested interval %" PRIu64 " < sensor's minInterval %" PRIu64,
         requestedInterval, sensor.getMinInterval());
  } else if (isRequestContinuous) {
    if (sensorTypeIsOneShot(sensorType)) {
      success = false;
      LOGE("Invalid continuous request for a one-shot sensor.");
    }
  } else if (isRequestOneShot) {
    if (!sensorTypeIsOneShot(sensorType)) {
      success = false;
      LOGE("Invalid one-shot request for a continuous sensor.");
    }
  }
  return success;
}

void flushTimerCallback(uint16_t /* eventType */, void * /* data */) {
  // TODO: Fatal error here since some platforms may not be able to handle
  //       timeouts gracefully. Modify this implementation to drop flush
  //       requests and handle stale responses in the future appropriately.
  FATAL_ERROR("Flush request timed out");
}

}  // namespace

SensorRequestManager::SensorRequestManager() {
  mSensorRequests.resize(mSensorRequests.capacity());

  DynamicVector<Sensor> sensors;
  sensors.reserve(8);  // Avoid some initial reallocation churn
  if (!PlatformSensor::getSensors(&sensors)) {
    LOGE("Failed to query the platform for sensors");
  } else if (sensors.empty()) {
    LOGW("Platform returned zero sensors");
  } else {
    for (size_t i = 0; i < sensors.size(); i++) {
      SensorType sensorType = sensors[i].getSensorType();
      size_t sensorIndex = getSensorTypeArrayIndex(sensorType);

      if (sensorType == SensorType::Unknown) {
        LOGE("Invalid sensor type");
      } else if (sensors[i].getMinInterval() == 0) {
        LOGE("Invalid sensor minInterval: %s", getSensorTypeName(sensorType));
      } else {
        mSensorRequests[sensorIndex].setSensor(std::move(sensors[i]));
        LOGD("Found sensor: %s", getSensorTypeName(sensorType));
      }
    }
  }
}

SensorRequestManager::~SensorRequestManager() {
  for (size_t i = 0; i < mSensorRequests.size(); i++) {
    // Disable sensors that have been enabled previously.
    if (mSensorRequests[i].isSensorSupported()) {
      mSensorRequests[i].removeAll();
    }
  }
}

bool SensorRequestManager::getSensorHandle(SensorType sensorType,
                                           uint32_t *sensorHandle) const {
  CHRE_ASSERT(sensorHandle);

  bool sensorHandleIsValid = false;
  if (sensorType == SensorType::Unknown) {
    LOGW("Querying for unknown sensor type");
  } else {
    size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
    sensorHandleIsValid = mSensorRequests[sensorIndex].isSensorSupported();
    if (sensorHandleIsValid) {
      *sensorHandle = getSensorHandleFromSensorType(sensorType);
    }
  }

  return sensorHandleIsValid;
}

bool SensorRequestManager::setSensorRequest(Nanoapp *nanoapp,
    uint32_t sensorHandle, const SensorRequest& sensorRequest) {
  CHRE_ASSERT(nanoapp);

  // Validate the input to ensure that a valid handle has been provided.
  SensorType sensorType = getSensorTypeFromSensorHandle(sensorHandle);
  if (sensorType == SensorType::Unknown) {
    LOGW("Attempting to configure an invalid sensor handle");
    return false;
  }

  // Ensure that the runtime is aware of this sensor type.
  size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
  SensorRequests& requests = mSensorRequests[sensorIndex];
  if (!requests.isSensorSupported()) {
    LOGW("Attempting to configure non-existent sensor");
    return false;
  }

  const Sensor& sensor = requests.getSensor();
  if (!isSensorRequestValid(sensor, sensorRequest)) {
    return false;
  }

  size_t requestIndex;
  uint16_t eventType = getSampleEventTypeForSensorType(sensorType);
  bool nanoappHasRequest = (requests.find(nanoapp->getInstanceId(),
                                          &requestIndex) != nullptr);

  bool success;
  bool requestChanged;
  if (sensorRequest.getMode() == SensorMode::Off) {
    if (nanoappHasRequest) {
      // The request changes the mode to off and there was an existing request.
      // The existing request is removed from the multiplexer. The nanoapp is
      // unregistered from events of this type if this request was successful.
      success = requests.remove(requestIndex, &requestChanged);
      if (success) {
        nanoapp->unregisterForBroadcastEvent(eventType);

        uint16_t biasEventType;
        if (getSensorBiasEventType(sensorType, &biasEventType)) {
          // Per API requirements, turn off bias reporting when unsubscribing
          // from the sensor.
          nanoapp->unregisterForBroadcastEvent(biasEventType);
        }
      }
    } else {
      // The sensor is being configured to Off, but is already Off (there is no
      // existing request). We assign to success to be true and no other
      // operation is required.
      requestChanged = false;
      success = true;
    }
  } else if (!nanoappHasRequest) {
    // The request changes the mode to the enabled state and there was no
    // existing request. The request is newly created and added to the
    // multiplexer. The nanoapp is registered for events if this request was
    // successful.
    success = requests.add(sensorRequest, &requestChanged);
    if (success) {
      nanoapp->registerForBroadcastEvent(eventType);

      // Per API requirements, turn on bias reporting for calibrated sensors
      // by default when subscribed.
      uint16_t biasEventType;
      if (getSensorBiasEventType(sensorType, &biasEventType)
          && sensorTypeIsCalibrated(sensorType)) {
        nanoapp->registerForBroadcastEvent(biasEventType);
      }

      // Deliver last valid event to new clients of on-change sensors
      if (sensorTypeIsOnChange(sensor.getSensorType())
          && sensor.getLastEvent() != nullptr) {
        EventLoopManagerSingleton::get()->getEventLoop()
            .postEvent(getSampleEventTypeForSensorType(sensorType),
                       sensor.getLastEvent(), nullptr, kSystemInstanceId,
                       nanoapp->getInstanceId());
      }
    }
  } else {
    // The request changes the mode to the enabled state and there was an
    // existing request. The existing request is updated.
    success = requests.update(requestIndex, sensorRequest, &requestChanged);
  }

  if (requestChanged) {
    // TODO: Send an event to nanoapps to indicate the rate change.
  }

  return success;
}

bool SensorRequestManager::getSensorInfo(uint32_t sensorHandle,
                                         const Nanoapp& nanoapp,
                                         struct chreSensorInfo *info) const {
  CHRE_ASSERT(info);

  bool success = false;

  // Validate the input to ensure that a valid handle has been provided.
  SensorType sensorType = getSensorTypeFromSensorHandle(sensorHandle);
  if (sensorType == SensorType::Unknown) {
    LOGW("Attempting to access sensor with an invalid handle %" PRIu32,
         sensorHandle);
  } else {
    size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
    if (!mSensorRequests[sensorIndex].isSensorSupported()) {
      LOGW("Attempting to get sensor info for unsupported sensor handle %"
           PRIu32, sensorHandle);
    } else {
      // Platform-independent properties.
      info->sensorType = getUnsignedIntFromSensorType(sensorType);
      info->isOnChange = sensorTypeIsOnChange(sensorType);
      info->isOneShot  = sensorTypeIsOneShot(sensorType);
      info->reportsBiasEvents = sensorTypeReportsBias(sensorType);
      info->unusedFlags = 0;

      // Platform-specific properties.
      const Sensor& sensor = mSensorRequests[sensorIndex].getSensor();
      info->sensorName = sensor.getSensorName();

      // minInterval was added in CHRE API v1.1 - do not attempt to populate for
      // nanoapps targeting v1.0 as their struct will not be large enough
      if (nanoapp.getTargetApiVersion() >= CHRE_API_VERSION_1_1) {
        info->minInterval = sensor.getMinInterval();
      }

      success = true;
    }
  }

  return success;
}

bool SensorRequestManager::removeAllRequests(SensorType sensorType) {
  bool success = false;
  if (sensorType == SensorType::Unknown) {
    LOGW("Attempting to remove all requests of an invalid sensor type");
  } else {
    size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
    SensorRequests& requests = mSensorRequests[sensorIndex];
    uint16_t eventType = getSampleEventTypeForSensorType(sensorType);

    for (const SensorRequest& request : requests.getRequests()) {
      Nanoapp *nanoapp = EventLoopManagerSingleton::get()->getEventLoop()
          .findNanoappByInstanceId(request.getInstanceId());
      if (nanoapp != nullptr) {
        nanoapp->unregisterForBroadcastEvent(eventType);
      }
    }

    success = requests.removeAll();
  }
  return success;
}

Sensor *SensorRequestManager::getSensor(SensorType sensorType) {
  Sensor *sensorPtr = nullptr;
  if (sensorType == SensorType::Unknown
      || sensorType >= SensorType::SENSOR_TYPE_COUNT) {
    LOGW("Attempting to get Sensor of an invalid SensorType %d",
         static_cast<int>(sensorType));
  } else {
    size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
    if (mSensorRequests[sensorIndex].isSensorSupported()) {
      sensorPtr = &mSensorRequests[sensorIndex].getSensor();
    }
  }
  return sensorPtr;
}

bool SensorRequestManager::getSensorSamplingStatus(
    uint32_t sensorHandle, struct chreSensorSamplingStatus *status) const {
  CHRE_ASSERT(status);

  bool success = false;
  SensorType sensorType = getSensorTypeFromSensorHandle(sensorHandle);
  if (sensorType == SensorType::Unknown) {
    LOGW("Attempting to access sensor with an invalid handle %" PRIu32,
         sensorHandle);
  } else {
    size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
    if (mSensorRequests[sensorIndex].isSensorSupported()) {
      success = mSensorRequests[sensorIndex].getSamplingStatus(status);
    }
  }
  return success;
}

const DynamicVector<SensorRequest>& SensorRequestManager::getRequests(
    SensorType sensorType) const {
  size_t sensorIndex = 0;
  if (sensorType == SensorType::Unknown
      || sensorType >= SensorType::SENSOR_TYPE_COUNT) {
    LOGW("Attempting to get requests of an invalid SensorType");
  } else {
    sensorIndex = getSensorTypeArrayIndex(sensorType);
  }
  return mSensorRequests[sensorIndex].getRequests();
}

bool SensorRequestManager::configureBiasEvents(
      Nanoapp *nanoapp, uint32_t sensorHandle, bool enable) {
  bool success = false;
  uint16_t eventType;
  SensorType sensorType = getSensorTypeFromSensorHandle(sensorHandle);
  if (getSensorBiasEventType(sensorType, &eventType)) {
    if (enable) {
      nanoapp->registerForBroadcastEvent(eventType);
    } else {
      nanoapp->unregisterForBroadcastEvent(eventType);
    }

    success = true;
  }

  return success;
}

bool SensorRequestManager::getThreeAxisBias(
    uint32_t sensorHandle, struct chreSensorThreeAxisData *bias) const {
  CHRE_ASSERT(bias != nullptr);

  bool success = false;
  if (bias != nullptr) {
    SensorType sensorType = getSensorTypeFromSensorHandle(sensorHandle);
    if (sensorType == SensorType::Unknown) {
      LOGW("Attempting to access sensor with an invalid handle %" PRIu32,
           sensorHandle);
    } else {
      size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
      if (mSensorRequests[sensorIndex].isSensorSupported()) {
        success = mSensorRequests[sensorIndex].getThreeAxisBias(bias);
      }
    }
  }

  return success;
}

bool SensorRequestManager::flushAsync(
    Nanoapp *nanoapp, uint32_t sensorHandle, const void *cookie) {
  bool success = false;

  uint32_t nanoappInstanceId = nanoapp->getInstanceId();
  SensorType sensorType = getSensorTypeFromSensorHandle(sensorHandle);
  // NOTE: One-shot sensors do not support flush per API
  if (sensorType == SensorType::Unknown || sensorTypeIsOneShot(sensorType)) {
    LOGE("Cannot flush for sensor type %" PRIu32,
         static_cast<uint32_t>(sensorType));
  } else if (mFlushRequestQueue.full()) {
    LOG_OOM();
  } else {
    mFlushRequestQueue.emplace_back(sensorType, nanoappInstanceId, cookie);
    size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
    success = (mSensorRequests[sensorIndex].makeFlushRequest(
        mFlushRequestQueue.back()) == CHRE_ERROR_NONE);
    if (!success) {
      mFlushRequestQueue.pop_back();
    }
  }

  return success;
}

void SensorRequestManager::handleFlushCompleteEvent(
    uint8_t errorCode, SensorType sensorType) {
  struct CallbackState {
    uint8_t errorCode;
    SensorType sensorType;
  };

  // Enables passing data through void pointer to avoid allocation.
  union NestedCallbackState {
    void *eventData;
    CallbackState callbackState;
  };
  static_assert(sizeof(NestedCallbackState) == sizeof(void *),
                "Size of NestedCallbackState must equal that of void *");

  NestedCallbackState state = {};
  state.callbackState.errorCode = errorCode;
  state.callbackState.sensorType = sensorType;

  auto callback = [](uint16_t /* eventType */, void *eventData) {
    NestedCallbackState nestedState;
    nestedState.eventData = eventData;
    EventLoopManagerSingleton::get()->getSensorRequestManager()
        .handleFlushCompleteEventSync(nestedState.callbackState.errorCode,
                                      nestedState.callbackState.sensorType);
  };

  EventLoopManagerSingleton::get()->deferCallback(
      SystemCallbackType::SensorFlushComplete, state.eventData, callback);
}

void SensorRequestManager::logStateToBuffer(char *buffer, size_t *bufferPos,
                                            size_t bufferSize) const {
  debugDumpPrint(buffer, bufferPos, bufferSize, "\nSensors:\n");
  for (uint8_t i = 0; i < static_cast<uint8_t>(SensorType::SENSOR_TYPE_COUNT);
       i++) {
    SensorType sensor = static_cast<SensorType>(i);
    if (sensor != SensorType::Unknown) {
      for (const auto& request : getRequests(sensor)) {
        debugDumpPrint(buffer, bufferPos, bufferSize, " %s: mode=%d"
                       " interval(ns)=%" PRIu64 " latency(ns)=%"
                       PRIu64 " nanoappId=%" PRIu32 "\n",
                       getSensorTypeName(sensor), request.getMode(),
                       request.getInterval().toRawNanoseconds(),
                       request.getLatency().toRawNanoseconds(),
                       request.getInstanceId());
      }
    }
  }
}

void SensorRequestManager::postFlushCompleteEvent(
    uint32_t sensorHandle, uint8_t errorCode, const FlushRequest& request) {
  auto *event = memoryAlloc<chreSensorFlushCompleteEvent>();
  if (event == nullptr) {
    LOG_OOM();
  } else {
    event->sensorHandle = sensorHandle;
    event->errorCode = errorCode;
    event->cookie = request.cookie;
    memset(event->reserved, 0, sizeof(event->reserved));

    EventLoopManagerSingleton::get()->getEventLoop().postEventOrFree(
        CHRE_EVENT_SENSOR_FLUSH_COMPLETE, event, freeEventDataCallback,
        kSystemInstanceId, request.nanoappInstanceId);
  }
}

void SensorRequestManager::dispatchNextFlushRequest(
    uint32_t sensorHandle, SensorType sensorType) {
  SensorRequests& requests = getSensorRequests(sensorType);

  for (size_t i = 0; i < mFlushRequestQueue.size(); i++) {
    const FlushRequest& request = mFlushRequestQueue[i];
    if (request.sensorType == sensorType) {
      uint8_t newRequestErrorCode = requests.makeFlushRequest(request);
      if (newRequestErrorCode == CHRE_ERROR_NONE) {
        break;
      } else {
        postFlushCompleteEvent(sensorHandle, newRequestErrorCode, request);
        mFlushRequestQueue.erase(i);
        i--;
      }
    }
  }
}

void SensorRequestManager::handleFlushCompleteEventSync(
    uint8_t errorCode, SensorType sensorType) {
  for (size_t i = 0; i < mFlushRequestQueue.size(); i++) {
    const FlushRequest& request = mFlushRequestQueue[i];
    if (request.sensorType == sensorType) {
      uint32_t sensorHandle;
      if (getSensorHandle(sensorType, &sensorHandle)) {
        SensorRequests& requests = getSensorRequests(sensorType);
        requests.cancelFlushTimer();

        postFlushCompleteEvent(sensorHandle, errorCode, request);
        mFlushRequestQueue.erase(i);
        dispatchNextFlushRequest(sensorHandle, sensorType);
      }
      break;
    }
  }
}

const SensorRequest *SensorRequestManager::SensorRequests::find(
    uint32_t instanceId, size_t *index) const {
  CHRE_ASSERT(index);

  const auto& requests = mMultiplexer.getRequests();
  for (size_t i = 0; i < requests.size(); i++) {
    const SensorRequest& sensorRequest = requests[i];
    if (sensorRequest.getInstanceId() == instanceId) {
      *index = i;
      return &sensorRequest;
    }
  }

  return nullptr;
}

bool SensorRequestManager::SensorRequests::add(const SensorRequest& request,
                                               bool *requestChanged) {
  CHRE_ASSERT(requestChanged != nullptr);
  CHRE_ASSERT(isSensorSupported());

  size_t addIndex;
  bool success = true;
  if (!mMultiplexer.addRequest(request, &addIndex, requestChanged)) {
    *requestChanged = false;
    success = false;
    LOG_OOM();
  } else if (*requestChanged) {
    success = mSensor->setRequest(mMultiplexer.getCurrentMaximalRequest());
    if (!success) {
      // Remove the newly added request since the platform failed to handle it.
      // The sensor is expected to maintain the existing request so there is no
      // need to reset the platform to the last maximal request.
      mMultiplexer.removeRequest(addIndex, requestChanged);

      // This is a roll-back operation so the maximal change in the multiplexer
      // must not have changed. The request changed state is forced to false.
      *requestChanged = false;
    }
  }

  return success;
}

bool SensorRequestManager::SensorRequests::remove(size_t removeIndex,
                                                  bool *requestChanged) {
  CHRE_ASSERT(requestChanged != nullptr);
  CHRE_ASSERT(isSensorSupported());

  bool success = true;
  mMultiplexer.removeRequest(removeIndex, requestChanged);
  if (*requestChanged) {
    success = mSensor->setRequest(mMultiplexer.getCurrentMaximalRequest());
    if (!success) {
      LOGE("SensorRequestManager failed to remove a request");

      // If the platform fails to handle this request in a debug build there is
      // likely an error in the platform. This is not strictly a programming
      // error but it does make sense to use assert semantics when a platform
      // fails to handle a request that it had been sent previously.
      CHRE_ASSERT(false);

      // The request to the platform to set a request when removing has failed
      // so the request has not changed.
      *requestChanged = false;
    }
  }

  return success;
}

bool SensorRequestManager::SensorRequests::update(size_t updateIndex,
                                                  const SensorRequest& request,
                                                  bool *requestChanged) {
  CHRE_ASSERT(requestChanged != nullptr);
  CHRE_ASSERT(isSensorSupported());

  bool success = true;
  SensorRequest previousRequest = mMultiplexer.getRequests()[updateIndex];
  mMultiplexer.updateRequest(updateIndex, request, requestChanged);
  if (*requestChanged) {
    success = mSensor->setRequest(mMultiplexer.getCurrentMaximalRequest());
    if (!success) {
      // Roll back the request since sending it to the sensor failed. The
      // request will roll back to the previous maximal. The sensor is
      // expected to maintain the existing request if a request fails so there
      // is no need to reset the platform to the last maximal request.
      mMultiplexer.updateRequest(updateIndex, previousRequest, requestChanged);

      // This is a roll-back operation so the maximal change in the multiplexer
      // must not have changed. The request changed state is forced to false.
      *requestChanged = false;
    }
  }

  return success;
}

bool SensorRequestManager::SensorRequests::removeAll() {
  CHRE_ASSERT(isSensorSupported());

  bool requestChanged;
  mMultiplexer.removeAllRequests(&requestChanged);

  bool success = true;
  if (requestChanged) {
    SensorRequest maximalRequest = mMultiplexer.getCurrentMaximalRequest();
    success = mSensor->setRequest(maximalRequest);
    if (!success) {
      LOGE("SensorRequestManager failed to remove all request");

      // If the platform fails to handle this request in a debug build there is
      // likely an error in the platform. This is not strictly a programming
      // error but it does make sense to use assert semantics when a platform
      // fails to handle a request that it had been sent previously.
      CHRE_ASSERT(false);
    }
  }
  return success;
}

uint8_t SensorRequestManager::SensorRequests::makeFlushRequest(
    const FlushRequest& request) {
  uint8_t errorCode = CHRE_ERROR;
  if (!isSensorSupported()) {
    LOGE("Cannot flush on unsupported sensor");
  } else if (mMultiplexer.getRequests().size() == 0) {
    LOGE("Cannot flush on disabled sensor");
  } else if (!isFlushRequestPending()) {
    Nanoseconds now = SystemTime::getMonotonicTime();
    Nanoseconds deadline = request.deadlineTimestamp;
    if (now >= deadline) {
      LOGE("Flush sensor %" PRIu32 " failed for nanoapp ID %" PRIu32
           ": deadline exceeded", static_cast<uint32_t>(request.sensorType),
           request.nanoappInstanceId);
      errorCode = CHRE_ERROR_TIMEOUT;
    } else if (mSensor->flushAsync()) {
      errorCode = CHRE_ERROR_NONE;
      Nanoseconds delay = deadline - now;
      mFlushRequestTimerHandle =
          EventLoopManagerSingleton::get()->setDelayedCallback(
              SystemCallbackType::SensorFlushTimeout, nullptr /* data */,
              flushTimerCallback, delay);
    }
  } else {
    // Flush request will be made once the pending request is completed.
    // Return true so that the nanoapp can wait for a result through the
    // CHRE_EVENT_SENSOR_FLUSH_COMPLETE event.
    errorCode = CHRE_ERROR_NONE;
  }

  return errorCode;
}

void SensorRequestManager::SensorRequests::cancelFlushTimer() {
  EventLoopManagerSingleton::get()->cancelDelayedCallback(
      mFlushRequestTimerHandle);
  mFlushRequestTimerHandle = CHRE_TIMER_INVALID;
}

}  // namespace chre