/******************************************************************************
 *
 *  Copyright (C) 2017 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 <base/bind.h>
#include <base/logging.h>
#include <base/threading/thread.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <semaphore.h>
#include <time.h>
#include <unistd.h>

#include "btcore/include/module.h"
#include "osi/include/alarm.h"
#include "osi/include/fixed_queue.h"
#include "osi/include/thread.h"
#include "stack/include/btu.h"

class TimeoutHelper {
 public:
  TimeoutHelper() { sem_init(&sem, 0, 0); }

  ~TimeoutHelper() { sem_destroy(&sem); }

  void wait(int seconds, base::Closure callback) {
    struct timespec timeout;
    clock_gettime(CLOCK_REALTIME, &timeout);
    timeout.tv_sec += seconds;

    int semvalue;
    sem_getvalue(&sem, &semvalue);

    // Call the callback if timeout occured
    if (sem_timedwait(&sem, &timeout) == -1 && !callback.is_null()) {
      callback.Run();
    }
  }

  void notify() { sem_post(&sem); }

 private:
  sem_t sem;
};

TimeoutHelper helper;

// External function definitions
void btu_message_loop_run(void* context);
void btu_task_start_up(void* context);
void btu_task_shut_down(void* context);

/* Below are methods and variables that must be implemented if we don't want to
 * compile the whole stack. They will be removed, or changed into mocks one by
 * one in the future, as the refactoring progresses */
void btif_transfer_context(void (*)(unsigned short, char*), uint16_t, char*,
                           int, void (*)(unsigned short, char*, char*)) {
  helper.notify();
};

void btu_init_core(){};
void btif_init_ok(unsigned short, char*){};
void BTE_InitStack(){};
void bta_sys_init(){};
void bta_sys_free(){};
void btu_free_core(){};
const module_t* get_module(const char*) { return nullptr; };
bool module_init(module_t const*) { return true; };
void module_clean_up(module_t const*){};

thread_t* bt_workqueue_thread;

class BtuMessageLoopTest : public testing::Test {
 public:
  MOCK_METHOD0(TestCallback, void(void));
  base::MessageLoop* message_loop;

  virtual void SetUp() {
    // Initialize alarms to prevent btu_task_shut_down from crashing
    alarm_new("test alarm");
    bt_workqueue_thread = thread_new("test alarm thread");

    // btu_task_start_up calls btif_transfer_context to let the stack know
    // start up is finished
    btu_task_start_up(nullptr);
    helper.wait(5, base::Bind(&BtuMessageLoopTest::Fail, base::Unretained(this),
                              "BTU startup timed out"));
  }

  virtual void TearDown() {
    btu_task_shut_down(nullptr);
    alarm_cleanup();
  }

  void Fail(std::string message) { FAIL() << message; }
};

TEST_F(BtuMessageLoopTest, send_message) {
  message_loop = get_message_loop();
  EXPECT_FALSE(message_loop == nullptr);

  EXPECT_CALL(*this, TestCallback()).Times(1);
  message_loop->task_runner()->PostTask(
      FROM_HERE,
      base::Bind(&BtuMessageLoopTest::TestCallback, base::Unretained(this)));

  message_loop->task_runner()->PostTask(
      FROM_HERE, base::Bind(&TimeoutHelper::notify, base::Unretained(&helper)));

  // Prevent the test from ending before the message loop posts the function
  helper.wait(5, base::Bind(&BtuMessageLoopTest::Fail, base::Unretained(this),
                            "Timed out waiting for callback"));
}
