/*
 * Copyright (C) 2019 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 "request_manager.h"

#include "chre/util/macros.h"
#include "chre/util/nanoapp/audio.h"
#include "chre/util/nested_data_ptr.h"
#include "generated/chre_power_test_generated.h"

namespace chre {
namespace {

//! List of all sensor types that can be interacted with from the nanoapp.
constexpr uint8_t kAllSensorTypes[] = {
    CHRE_SENSOR_TYPE_ACCELEROMETER,
    CHRE_SENSOR_TYPE_GYROSCOPE,
    CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE,
    CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD,
    CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD,
    CHRE_SENSOR_TYPE_PRESSURE,
    CHRE_SENSOR_TYPE_LIGHT,
    CHRE_SENSOR_TYPE_PROXIMITY,
    CHRE_SENSOR_TYPE_STEP_DETECT,
    CHRE_SENSOR_TYPE_STEP_COUNTER,
    CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER,
    CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE,
    CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE,
    CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE,
    CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT,
    CHRE_SENSOR_TYPE_STATIONARY_DETECT,
};

/**
 * Retrieve the configure mode for the given sensor type.
 *
 * @param sensorType The type of the sensor
 * @return The sensor configure mode for the given sensor type
 */
chreSensorConfigureMode getModeForSensorType(uint8_t sensorType) {
  chreSensorConfigureMode mode;
  switch (sensorType) {
    case CHRE_SENSOR_TYPE_ACCELEROMETER:
    case CHRE_SENSOR_TYPE_GYROSCOPE:
    case CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE:
    case CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD:
    case CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD:
    case CHRE_SENSOR_TYPE_PRESSURE:
    case CHRE_SENSOR_TYPE_LIGHT:
    case CHRE_SENSOR_TYPE_PROXIMITY:
    case CHRE_SENSOR_TYPE_STEP_DETECT:
    case CHRE_SENSOR_TYPE_STEP_COUNTER:
    case CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER:
    case CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE:
    case CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE:
    case CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE:
      mode = CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS;
      break;
    case CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT:
    case CHRE_SENSOR_TYPE_STATIONARY_DETECT:
      mode = CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT;
      break;
    default:
      LOGE("Mode requested for unhandled sensor type %" PRIu8
           " defaulting to continuous",
           sensorType);
      mode = CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS;
  }
  return mode;
}

/**
 * Verifies a given message from the host is a valid message to the nanoapp.
 *
 * @param hostMessage message being delivered from the host
 * @param verifiedMessage if verification is successful, contains the decoded
 *     message from the host. Otherwise, is uninitialized.
 * @return true if the message was verified to be a valid.
 */
template <class MessageClass>
bool verifyMessage(const chreMessageFromHostData &hostMessage,
                   const MessageClass **verifiedMessage) {
  flatbuffers::Verifier verifier(
      static_cast<const uint8_t *>(hostMessage.message),
      hostMessage.messageSize);
  bool verified = verifier.VerifyBuffer<MessageClass>(nullptr);
  if (verified) {
    *verifiedMessage = flatbuffers::GetRoot<MessageClass>(hostMessage.message);
  } else {
    LOGE("Failed to verify %s message from host",
         power_test::EnumNameMessageType(
             static_cast<power_test::MessageType>(hostMessage.messageType)));
  }
  return verified;
}

}  // namespace

using power_test::AudioRequestMessage;
using power_test::BreakItMessage;
using power_test::CellQueryMessage;
using power_test::GnssLocationMessage;
using power_test::GnssMeasurementMessage;
using power_test::MessageType;
using power_test::NanoappResponseMessage;
using power_test::SensorRequestMessage;
using power_test::TimerMessage;
using power_test::WifiScanMessage;

bool RequestManager::requestTimer(bool enable, TimerType type,
                                  Nanoseconds delay) {
  bool success = false;
  if (enable) {
    // Stop previous request if active.
    chreTimerCancel(mTimerIds[type]);
    mTimerIds[type] = CHRE_TIMER_INVALID;

    // Set a timer for the new request.
    NestedDataPtr<TimerType> timerType(type);
    uint32_t timerId =
        chreTimerSet(delay.toRawNanoseconds(), timerType, false /* oneShot */);
    if (timerId != CHRE_TIMER_INVALID) {
      success = true;
      mTimerIds[type] = timerId;
    }
  } else {
    success = chreTimerCancel(mTimerIds[type]);
    mTimerIds[type] = CHRE_TIMER_INVALID;
  }
  LOGI("RequestTimer success %d, enable %d, type %d, delay %" PRIu64, success,
       enable, type, delay.toRawNanoseconds());
  return success;
}

void RequestManager::wifiTimerCallback() const {
  struct chreWifiScanParams params = {};
  params.scanType = mWifiScanType;
  params.radioChainPref = mWifiRadioChain;
  params.channelSet = mWifiChannelSet;
  bool success = chreWifiRequestScanAsync(&params, nullptr /*cookie*/);
  LOGI("Requested WiFi - success %d, scanType %" PRIu8 " radioChain %" PRIu8
       " channelSet %" PRIu8,
       success, params.scanType, params.radioChainPref, params.channelSet);
}

bool RequestManager::requestGnssLocation(
    bool enable, uint32_t scanIntervalMillis,
    uint32_t minTimeToNextFixMillis) const {
  bool success;
  if (enable) {
    success = chreGnssLocationSessionStartAsync(
        scanIntervalMillis, minTimeToNextFixMillis, nullptr /* cookie */);
  } else {
    success = chreGnssLocationSessionStopAsync(nullptr /* cookie */);
  }
  LOGI("RequestGnss success %d, enable %d, scanIntervalMillis %" PRIu32
       " minTimeToNextFixMillis %" PRIu32,
       success, enable, scanIntervalMillis, minTimeToNextFixMillis);
  return success;
}

bool RequestManager::requestGnssMeasurement(bool enable,
                                            uint32_t intervalMillis) const {
  bool success;
  if (enable) {
    success = chreGnssMeasurementSessionStartAsync(intervalMillis,
                                                   nullptr /* cookie */);
  } else {
    success = chreGnssMeasurementSessionStopAsync(nullptr /* cookie */);
  }
  LOGI("RequestGnssMeasurement success %d, enable %d, intervalMillis %" PRIu32,
       success, enable, intervalMillis);
  return success;
}

void RequestManager::cellTimerCallback() const {
  bool success = chreWwanGetCellInfoAsync(nullptr /* cookie */);
  LOGI("Requested Cell - success %d", success);
}

bool RequestManager::requestAudio(bool enable,
                                  uint64_t bufferDurationNs) const {
  bool success;

  if (enable) {
    // Only request audio data from the first source
    // TODO: Request audio data from all available sources (or allow configuring
    // which source to sample from)
    success = chreAudioConfigureSource(0 /* handle */, true /* enable */,
                                       bufferDurationNs, bufferDurationNs);
  } else {
    success = chreAudioConfigureSource(0 /* handle */, false /* enable */,
                                       0 /* bufferDuration */,
                                       0 /* deliveryInterval */);
  }
  LOGI("RequestAudio success %d, enable %d, bufferDurationNs %" PRIu64, success,
       enable, bufferDurationNs);
  return success;
}

bool RequestManager::requestSensor(bool enable, uint8_t sensorType,
                                   uint64_t samplingIntervalNs,
                                   uint64_t latencyNs) const {
  uint32_t sensorHandle;
  bool success = chreSensorFindDefault(sensorType, &sensorHandle);

  if (success) {
    if (enable) {
      success =
          chreSensorConfigure(sensorHandle, getModeForSensorType(sensorType),
                              samplingIntervalNs, latencyNs);
    } else {
      success = chreSensorConfigureModeOnly(sensorHandle,
                                            CHRE_SENSOR_CONFIGURE_MODE_DONE);
    }
  }

  LOGI("RequestSensor success %d, enable %d, sensorType %" PRIu8
       " samplingIntervalNs %" PRIu64 " latencyNs %" PRIu64,
       success, enable, sensorType, samplingIntervalNs, latencyNs);
  return success;
}

bool RequestManager::requestAllSensors(bool enable) const {
  bool success = true;
  uint32_t sensorHandle;
  struct chreSensorInfo sensorInfo;
  for (uint8_t i = 0; i < ARRAY_SIZE(kAllSensorTypes); i++) {
    success &= chreSensorFindDefault(kAllSensorTypes[i], &sensorHandle) &&
               chreGetSensorInfo(sensorHandle, &sensorInfo) &&
               requestSensor(enable, kAllSensorTypes[i], sensorInfo.minInterval,
                             CHRE_SENSOR_LATENCY_ASAP);
  }

  LOGI("requestAllSensors success %d enable %d", success, enable);
  return success;
}

bool RequestManager::requestAudioAtFastestRate(bool enable) const {
  struct chreAudioSource audioSource;
  bool success = chreAudioGetSource(0 /* handle */, &audioSource);
  if (success) {
    LOGI("Found audio source '%s' with %" PRIu32 "Hz %s data", audioSource.name,
         audioSource.sampleRate,
         chre::getChreAudioFormatString(audioSource.format));
    LOGI("  buffer duration: [%" PRIu64 "ns, %" PRIu64 "ns]",
         audioSource.minBufferDuration, audioSource.maxBufferDuration);
    success &= requestAudio(enable, audioSource.minBufferDuration);
  }

  LOGI("requestAudioAtFastestRate success %d enable %d", success, enable);
  return success;
}

bool RequestManager::requestBreakIt(bool enable) {
  bool success = requestTimer(enable, TimerType::WIFI, Seconds(1));
  success &= requestGnssLocation(enable, chre::kOneSecondInNanoseconds,
                                 0 /* minTimeToNextFixMillis */);
  success &= requestTimer(enable, TimerType::CELL, Seconds(1));
  success &= requestAudioAtFastestRate(enable);
  success &= requestAllSensors(enable);
  LOGI("RequestBreakIt success %d enable %d", success, enable);
  return success;
}

void RequestManager::handleTimerEvent(const void *cookie) const {
  if (cookie != nullptr) {
    NestedDataPtr<TimerType> timerType(const_cast<void *>(cookie));
    switch (timerType.data) {
      case TimerType::WAKEUP:
        LOGI("Received a wakeup timer event");
        break;
      case TimerType::WIFI:
        wifiTimerCallback();
        break;
      case TimerType::CELL:
        cellTimerCallback();
        break;
      default:
        LOGE("Invalid timer type received %d", timerType.data);
    }
  }
}

bool RequestManager::handleMessageFromHost(
    const chreMessageFromHostData &hostMessage) {
  bool success = false;
  if (hostMessage.message == nullptr) {
    LOGE("Host message from %" PRIu16 " has empty message",
         hostMessage.hostEndpoint);
  } else {
    switch (static_cast<MessageType>(hostMessage.messageType)) {
      case MessageType::TIMER_TEST: {
        const TimerMessage *msg;
        if (verifyMessage<TimerMessage>(hostMessage, &msg)) {
          success = requestTimer(msg->enable(), TimerType::WAKEUP,
                                 Nanoseconds(msg->wakeup_interval_ns()));
        }
        break;
      }
      case MessageType::WIFI_SCAN_TEST: {
        const WifiScanMessage *msg;
        if (verifyMessage<WifiScanMessage>(hostMessage, &msg)) {
          mWifiScanType = static_cast<uint8_t>(msg->scan_type());
          mWifiRadioChain = static_cast<uint8_t>(msg->radio_chain());
          mWifiChannelSet = static_cast<uint8_t>(msg->channel_set());
          success = requestTimer(msg->enable(), TimerType::WIFI,
                                 Nanoseconds(msg->scan_interval_ns()));
        }
        break;
      }
      case MessageType::GNSS_LOCATION_TEST: {
        const GnssLocationMessage *msg;
        if (verifyMessage<GnssLocationMessage>(hostMessage, &msg)) {
          success =
              requestGnssLocation(msg->enable(), msg->scan_interval_millis(),
                                  msg->min_time_to_next_fix_millis());
        }
        break;
      }
      case MessageType::CELL_QUERY_TEST: {
        const CellQueryMessage *msg;
        if (verifyMessage<CellQueryMessage>(hostMessage, &msg)) {
          success = requestTimer(msg->enable(), TimerType::CELL,
                                 Nanoseconds(msg->query_interval_ns()));
        }
        break;
      }
      case MessageType::AUDIO_REQUEST_TEST: {
        const AudioRequestMessage *msg;
        if (verifyMessage<AudioRequestMessage>(hostMessage, &msg)) {
          success = requestAudio(msg->enable(), msg->buffer_duration_ns());
        }
        break;
      }
      case MessageType::SENSOR_REQUEST_TEST: {
        const SensorRequestMessage *msg;
        if (verifyMessage<SensorRequestMessage>(hostMessage, &msg)) {
          success =
              requestSensor(msg->enable(), static_cast<uint8_t>(msg->sensor()),
                            msg->sampling_interval_ns(), msg->latency_ns());
        }
        break;
      }
      case MessageType::BREAK_IT_TEST: {
        const BreakItMessage *msg;
        if (verifyMessage<BreakItMessage>(hostMessage, &msg)) {
          success = requestBreakIt(msg->enable());
        }
        break;
      }
      case MessageType::GNSS_MEASUREMENT_TEST: {
        const GnssMeasurementMessage *msg;
        if (verifyMessage<GnssMeasurementMessage>(hostMessage, &msg)) {
          success =
              requestGnssMeasurement(msg->enable(), msg->min_interval_millis());
        }
        break;
      }
      default:
        LOGE("Received unknown host message %" PRIu32, hostMessage.messageType);
    }
  }
  return success;
}

}  // namespace chre