/*
 * 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 <general_test/timer_cancel_test.h>

#include <cinttypes>
#include <cstddef>

#include <shared/send_message.h>

#include <chre.h>

using nanoapp_testing::sendFatalFailureToHost;
using nanoapp_testing::sendInternalFailureToHost;
using nanoapp_testing::sendSuccessToHost;

/*
 * This test has four stages where we cancel one-shot and recurring timers,
 * before and after they're triggered.
 *
 * See the TimerCancelTest constructor to see which stage tests which setup.
 *
 * When all of our stages have succeeded, then we send success to the host.
 */

// 10 milliseconds
static uint64_t kDuration = UINT64_C(10000000);

namespace general_test {

void TimerCancelTest::startStages() {
  for (uint32_t i = 0; i < kStageCount; i++) {
    Stage *stage = &mStages[i];
    stage->timerId = chreTimerSet(kDuration, stage, stage->oneShot);
    if (stage->timerId == CHRE_TIMER_INVALID) {
      sendFatalFailureToHost("Unable to set timer:", &i);
    }
    if (stage->expectCallback) {
      // Go on to the next stage.  Note this stage will markSuccess()
      // in handleStageEvent().
      continue;
    }
    if (!chreTimerCancel(stage->timerId)) {
      sendFatalFailureToHost("Unable to cancel timer:", &i);
    }
    if (chreTimerCancel(stage->timerId)) {
      sendFatalFailureToHost("Claimed success in second cancel:", &i);
    }
    markSuccess(i);
  }
}

TimerCancelTest::TimerCancelTest()
  : Test(CHRE_API_VERSION_1_0),
    mInMethod(false),
    mStages{
      // expectCallback:false ==> We're canceling before the timer fires.
      // expectCallback:true  ==> We'll cancel after the timer fires once.
      //
      //        stage, oneShot, expectCallback
        Stage(0,     false,   false),
        Stage(1,     true,    false),
        Stage(2,     false,   true  ),
        Stage(3,     true,    true  )},
    mFinishedBitmask(0) {
}

void TimerCancelTest::setUp(uint32_t messageSize, const void * /* message */) {
  mInMethod = true;

  if (messageSize != 0) {
    sendFatalFailureToHost(
        "TimerCancel message expects 0 additional bytes, got ",
        &messageSize);
  }

  constexpr uint32_t kUnownedTimer = 0;
  static_assert((kUnownedTimer != CHRE_TIMER_INVALID), "Bad test");
  if (chreTimerCancel(kUnownedTimer)) {
    sendFatalFailureToHost("Claimed success canceling timer we don't own");
  }

  startStages();

  // Now we wait for some events from the timers to fire.

  mInMethod = false;
}

void TimerCancelTest::handleStageEvent(Stage *stage) {
  if (!stage->expectCallback) {
    sendFatalFailureToHost("Timer didn't cancel:", &stage->stage);
  }
  // Now we're going to cancel the timer, so we don't expect an
  // additional call.
  stage->expectCallback = false;

  bool cancelSucceeded = chreTimerCancel(stage->timerId);
  if (stage->oneShot) {
    if (cancelSucceeded) {
      sendFatalFailureToHost("Claimed success canceling one-shot after "
                             "it fired:", &stage->stage);
    }
  } else {
    if (!cancelSucceeded) {
      sendFatalFailureToHost("Unable to cancel recurring timer:",
                             &stage->stage);
    }
  }
  if (chreTimerCancel(stage->timerId)) {
    sendFatalFailureToHost("Claimed success in second cancel:",
                           &stage->stage);
  }
  markSuccess(stage->stage);
}

void TimerCancelTest::handleEvent(uint32_t senderInstanceId,
                                  uint16_t eventType, const void* eventData) {
  if (mInMethod) {
    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
                           "method is running");
  }
  mInMethod = true;
  if (senderInstanceId != CHRE_INSTANCE_ID) {
    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
                           &senderInstanceId);
  }
  if (eventType != CHRE_EVENT_TIMER) {
    unexpectedEvent(eventType);
  }
  const Stage *stage = static_cast<const Stage*>(eventData);
  if (stage->stage >= kStageCount) {
    sendFatalFailureToHost("Invalid handleEvent data:", &stage->stage);
  }
  handleStageEvent(const_cast<Stage *>(stage));

  mInMethod = false;
}

void TimerCancelTest::markSuccess(uint32_t stage) {
  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
  uint32_t finishedBit = (1 << stage);
  if ((kAllFinished & finishedBit) == 0) {
    sendFatalFailureToHost("markSuccess bad stage:", &stage);
  }
  if ((mFinishedBitmask & finishedBit) != 0) {
    sendInternalFailureToHost("markSuccess multiple times:", &stage);
  }
  mFinishedBitmask |= finishedBit;
  if (mFinishedBitmask == kAllFinished) {
    sendSuccessToHost();
  }
}

}  // namespace general_test