/*
 * 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/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(EventLoop& eventLoop)
    : mEventLoop(eventLoop) {
  if (!mSystemTimer.init()) {
    FATAL_ERROR("Failed to initialize a system timer for the TimerPool");
  }
}

TimerHandle TimerPool::setTimer(const Nanoapp *nanoapp, Nanoseconds duration,
    const void *cookie, bool isOneShot) {
  CHRE_ASSERT(nanoapp);
  LockGuard<Mutex> lock(mMutex);

  TimerRequest timerRequest;
  timerRequest.requestingNanoapp = nanoapp;
  timerRequest.timerHandle = generateTimerHandle();
  timerRequest.expirationTime = SystemTime::getMonotonicTime() + duration;
  timerRequest.duration = duration;
  timerRequest.isOneShot = isOneShot;
  timerRequest.cookie = cookie;

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

  LOGD("App %" PRIx64 " requested timer with duration %" PRIu64 "ns",
      nanoapp->getAppId(), duration.toRawNanoseconds());

  if (newTimerExpiresEarliest) {
    if (mSystemTimer.isActive()) {
      mSystemTimer.cancel();
    }

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

  return timerRequest.timerHandle;
}

bool TimerPool::cancelTimer(const Nanoapp *nanoapp, TimerHandle timerHandle) {
  CHRE_ASSERT(nanoapp);
  LockGuard<Mutex> lock(mMutex);

  size_t index;
  bool success = false;
  TimerRequest *timerRequest = getTimerRequestByTimerHandle(timerHandle,
      &index);

  if (timerRequest == nullptr) {
    LOGW("Failed to cancel timer ID %" PRIu32 ": not found", timerHandle);
  } else if (timerRequest->requestingNanoapp != nanoapp) {
    LOGW("Failed to cancel timer ID %" PRIu32 ": permission denied",
         timerHandle);
  } else {
    TimerHandle cancelledTimerHandle = timerRequest->timerHandle;
    mTimerRequests.remove(index);
    if (index == 0) {
      if (mSystemTimer.isActive()) {
        mSystemTimer.cancel();
      }

      handleExpiredTimersAndScheduleNext();
    }

    LOGD("App %" PRIx64 " cancelled timer %" PRIu32, nanoapp->getAppId(),
         cancelledTimerHandle);
    success = true;
  }

  return success;
}

TimerPool::TimerRequest *TimerPool::getTimerRequestByTimerHandle(
    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::generateTimerHandle() {
  TimerHandle timerHandle;
  if (mGenerateTimerHandleMustCheckUniqueness) {
    timerHandle = generateUniqueTimerHandle();
  } 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 = generateUniqueTimerHandle();
    }
  }

  mLastTimerHandle = timerHandle;
  return timerHandle;
}

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

void TimerPool::insertTimerRequest(const TimerRequest& timerRequest) {
  // If the timer request was not inserted, simply append it to the list.
  if (!mTimerRequests.push(timerRequest)) {
    FATAL_ERROR("Failed to insert a timer request: out of memory");
  }
}

void TimerPool::onSystemTimerCallback() {
  // Gain exclusive access to the timer pool. This is needed because the context
  // of this callback is not defined.
  LockGuard<Mutex> lock(mMutex);
  if (!handleExpiredTimersAndScheduleNext()) {
    LOGE("Timer callback invoked with no outstanding timers");
  }
}

bool TimerPool::handleExpiredTimersAndScheduleNext() {
  bool eventWasPosted = false;
  while (!mTimerRequests.empty()) {
    Nanoseconds currentTime = SystemTime::getMonotonicTime();
    TimerRequest& currentTimerRequest = mTimerRequests.top();
    if (currentTime >= currentTimerRequest.expirationTime) {
      // Post an event for an expired timer.
      mEventLoop.postEvent(CHRE_EVENT_TIMER,
          const_cast<void *>(currentTimerRequest.cookie), nullptr,
          kSystemInstanceId,
          currentTimerRequest.requestingNanoapp->getInstanceId());
      eventWasPosted = true;

      // Reschedule the timer if needed.
      if (!currentTimerRequest.isOneShot) {
        currentTimerRequest.expirationTime = currentTime
            + currentTimerRequest.duration;
        insertTimerRequest(currentTimerRequest);
      }

      // Release the current request.
      mTimerRequests.pop();
    } else {
      Nanoseconds duration = currentTimerRequest.expirationTime - currentTime;
      mSystemTimer.set(handleSystemTimerCallback, this, duration);
      break;
    }
  }

  return eventWasPosted;
}

void TimerPool::handleSystemTimerCallback(void *timerPoolPtr) {
  // Cast the context pointer to a TimerPool context and call into the callback
  // handler.
  TimerPool *timerPool = static_cast<TimerPool *>(timerPoolPtr);
  timerPool->onSystemTimerCallback();
}

}  // namespace chre