/*
 * 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/event_loop.h"
#include "chre/core/event_loop_manager.h"
#include "chre/core/timer_pool.h"
#include "chre/platform/fatal_error.h"
#include "chre/platform/system_time.h"
#include "chre/util/lock_guard.h"

namespace chre {

TimerPool::TimerPool() {
  if (!mSystemTimer.init()) {
    FATAL_ERROR("Failed to initialize a system timer for the TimerPool");
  }
}

TimerHandle TimerPool::setSystemTimer(
    Nanoseconds duration, SystemCallbackFunction *callback,
    SystemCallbackType callbackType, const void *cookie) {
  TimerHandle timerHandle = setTimer(
      kSystemInstanceId, duration, callback,
      static_cast<uint16_t>(callbackType), cookie, true /* isOneShot */);

  if (timerHandle == CHRE_TIMER_INVALID) {
    FATAL_ERROR("Failed to set system timer");
  }

  return timerHandle;
}

TimerHandle TimerPool::setTimer(
    uint32_t instanceId, Nanoseconds duration,
    SystemCallbackFunction *callback, uint16_t eventType,
    const void *cookie, bool isOneShot) {
  LockGuard<Mutex> lock(mMutex);

  TimerRequest timerRequest;
  timerRequest.instanceId = instanceId;
  timerRequest.timerHandle = generateTimerHandleLocked();
  timerRequest.expirationTime = SystemTime::getMonotonicTime() + duration;
  timerRequest.duration = duration;
  timerRequest.isOneShot = isOneShot;
  timerRequest.callback = callback;
  timerRequest.eventType = eventType;
  timerRequest.cookie = cookie;

  bool newTimerExpiresEarliest =
      (!mTimerRequests.empty() && mTimerRequests.top() > timerRequest);
  bool success = insertTimerRequestLocked(timerRequest);

  if (success) {
    if (newTimerExpiresEarliest) {
      mSystemTimer.set(handleSystemTimerCallback, this, duration);
    } else if (mTimerRequests.size() == 1) {
      // If this timer request was the first, schedule it.
      handleExpiredTimersAndScheduleNextLocked();
    }
  }

  return success ? timerRequest.timerHandle : CHRE_TIMER_INVALID;
}

bool TimerPool::cancelTimer(
    uint32_t instanceId, TimerHandle timerHandle) {
  LockGuard<Mutex> lock(mMutex);
  size_t index;
  bool success = false;
  TimerRequest *timerRequest = getTimerRequestByTimerHandleLocked(timerHandle,
      &index);

  if (timerRequest == nullptr) {
    LOGW("Failed to cancel timer ID %" PRIu32 ": not found", timerHandle);
  } else if (timerRequest->instanceId != instanceId) {
    LOGW("Failed to cancel timer ID %" PRIu32 ": permission denied",
         timerHandle);
  } else {
    removeTimerRequestLocked(index);

    if (index == 0) {
      mSystemTimer.cancel();
      handleExpiredTimersAndScheduleNextLocked();
    }

    success = true;
  }

  return success;
}

TimerPool::TimerRequest *TimerPool::getTimerRequestByTimerHandleLocked(
    TimerHandle timerHandle, size_t *index) {
  for (size_t i = 0; i < mTimerRequests.size(); i++) {
    if (mTimerRequests[i].timerHandle == timerHandle) {
      if (index != nullptr) {
        *index = i;
      }
      return &mTimerRequests[i];
    }
  }

  return nullptr;
}

bool TimerPool::TimerRequest::operator>(const TimerRequest& request) const {
  return (expirationTime > request.expirationTime);
}

TimerHandle TimerPool::generateTimerHandleLocked() {
  TimerHandle timerHandle;
  if (mGenerateTimerHandleMustCheckUniqueness) {
    timerHandle = generateUniqueTimerHandleLocked();
  } else {
    timerHandle = mLastTimerHandle + 1;
    if (timerHandle == CHRE_TIMER_INVALID) {
      // TODO: Consider that uniqueness checking can be reset when the number of
      // timer requests reaches zero.
      mGenerateTimerHandleMustCheckUniqueness = true;
      timerHandle = generateUniqueTimerHandleLocked();
    }
  }

  mLastTimerHandle = timerHandle;
  return timerHandle;
}

TimerHandle TimerPool::generateUniqueTimerHandleLocked() {
  TimerHandle timerHandle = mLastTimerHandle;
  while (1) {
    timerHandle++;
    if (timerHandle != CHRE_TIMER_INVALID) {
      TimerRequest *timerRequest =
          getTimerRequestByTimerHandleLocked(timerHandle);
      if (timerRequest == nullptr) {
        return timerHandle;
      }
    }
  }
}

bool TimerPool::isNewTimerAllowedLocked(bool isNanoappTimer) const {
  static_assert(kMaxNanoappTimers <= kMaxTimerRequests,
                "Max number of nanoapp timers is too big");
  static_assert(kNumReservedNanoappTimers <= kMaxTimerRequests,
                "Number of reserved nanoapp timers is too big");

  bool allowed;
  if (isNanoappTimer) {
    allowed = (mNumNanoappTimers < kMaxNanoappTimers);
  } else { // System timer
    // We must not allow more system timers than the required amount of reserved
    // timers for nanoapps.
    constexpr size_t kMaxSystemTimers =
        kMaxTimerRequests - kNumReservedNanoappTimers;
    size_t numSystemTimers = mTimerRequests.size() - mNumNanoappTimers;
    allowed = (numSystemTimers < kMaxSystemTimers);
  }

  return allowed;
}

bool TimerPool::insertTimerRequestLocked(const TimerRequest& timerRequest) {
  bool isNanoappTimer = (timerRequest.instanceId != kSystemInstanceId);
  bool success = isNewTimerAllowedLocked(isNanoappTimer) &&
      mTimerRequests.push(timerRequest);

  if (!success) {
    LOG_OOM();
  } else if (isNanoappTimer) {
    mNumNanoappTimers++;
  }

  return success;
}

void TimerPool::popTimerRequestLocked() {
  CHRE_ASSERT(!mTimerRequests.empty());
  if (!mTimerRequests.empty()) {
    bool isNanoappTimer =
        (mTimerRequests.top().instanceId != kSystemInstanceId);
    mTimerRequests.pop();
    if (isNanoappTimer) {
      mNumNanoappTimers--;
    }
  }
}

void TimerPool::removeTimerRequestLocked(size_t index) {
  CHRE_ASSERT(index < mTimerRequests.size());
  if (index < mTimerRequests.size()) {
    bool isNanoappTimer =
        (mTimerRequests[index].instanceId != kSystemInstanceId);
    mTimerRequests.remove(index);
    if (isNanoappTimer) {
      mNumNanoappTimers--;
    }
  }
}

bool TimerPool::handleExpiredTimersAndScheduleNext() {
  LockGuard<Mutex> lock(mMutex);
  return handleExpiredTimersAndScheduleNextLocked();
}

bool TimerPool::handleExpiredTimersAndScheduleNextLocked() {
  bool success = false;
  while (!mTimerRequests.empty()) {
    Nanoseconds currentTime = SystemTime::getMonotonicTime();
    TimerRequest& currentTimerRequest = mTimerRequests.top();
    if (currentTime >= currentTimerRequest.expirationTime) {
      // Post an event for an expired timer.
      success = EventLoopManagerSingleton::get()->getEventLoop().postEvent(
          currentTimerRequest.eventType,
          const_cast<void *>(currentTimerRequest.cookie),
          currentTimerRequest.callback, kSystemInstanceId,
          currentTimerRequest.instanceId);

      // Reschedule the timer if needed, and release the current request.
      if (!currentTimerRequest.isOneShot) {
        // Important: we need to make a copy of currentTimerRequest here,
        // because it's a reference to memory that may get moved during the
        // insert operation (thereby invalidating it).
        TimerRequest cyclicTimerRequest = currentTimerRequest;
        cyclicTimerRequest.expirationTime = currentTime
            + currentTimerRequest.duration;
        popTimerRequestLocked();
        CHRE_ASSERT(insertTimerRequestLocked(cyclicTimerRequest));
      } else {
        popTimerRequestLocked();
      }
    } else {
      Nanoseconds duration = currentTimerRequest.expirationTime - currentTime;
      mSystemTimer.set(handleSystemTimerCallback, this, duration);

      // Assign success to true here to handle timers that tick before their
      // expiration time. This should be rarely required, but for systems where
      // a timer may tick earlier than requested the request is rescheduled with
      // the remaining time as computed above.
      success = true;
      break;
    }
  }

  return success;
}

void TimerPool::handleSystemTimerCallback(void *timerPoolPtr) {
  auto callback = [](uint16_t /* eventType */, void *eventData) {
    auto *timerPool = static_cast<TimerPool *>(eventData);
    if (!timerPool->handleExpiredTimersAndScheduleNext()) {
      LOGE("Timer callback invoked with no outstanding timers");
    }
  };

  EventLoopManagerSingleton::get()->deferCallback(
      SystemCallbackType::TimerPoolTick, timerPoolPtr, callback);
}

}  // namespace chre