/*
 * Copyright 2015-2018, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *
 *     * Neither the name of the copyright holder nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * benchmark_worker.cpp -- benchmark_worker module definitions
 */

#include <cassert>
#include <err.h>

#include "benchmark_worker.hpp"
#include "os_thread.h"

/*
 * worker_state_wait_for_transition -- wait for transition from and to
 * specified states
 */
static void
worker_state_wait_for_transition(struct benchmark_worker *worker,
				 enum benchmark_worker_state state,
				 enum benchmark_worker_state new_state)
{
	while (worker->state == state)
		os_cond_wait(&worker->cond, &worker->lock);
	assert(worker->state == new_state);
}

/*
 * worker_state_transition -- change worker state from and to specified states
 */
static void
worker_state_transition(struct benchmark_worker *worker,
			enum benchmark_worker_state old_state,
			enum benchmark_worker_state new_state)
{
	assert(worker->state == old_state);
	worker->state = new_state;
	os_cond_signal(&worker->cond);
}

/*
 * thread_func -- (internal) callback for os_thread
 */
static void *
thread_func(void *arg)
{
	assert(arg != nullptr);
	auto *worker = (struct benchmark_worker *)arg;

	os_mutex_lock(&worker->lock);

	worker_state_wait_for_transition(worker, WORKER_STATE_IDLE,
					 WORKER_STATE_INIT);

	if (worker->init)
		worker->ret_init = worker->init(worker->bench, worker->args,
						&worker->info);

	worker_state_transition(worker, WORKER_STATE_INIT,
				WORKER_STATE_INITIALIZED);

	if (worker->ret_init) {
		os_mutex_unlock(&worker->lock);
		return nullptr;
	}

	worker_state_wait_for_transition(worker, WORKER_STATE_INITIALIZED,
					 WORKER_STATE_RUN);

	worker->ret = worker->func(worker->bench, &worker->info);

	worker_state_transition(worker, WORKER_STATE_RUN, WORKER_STATE_END);

	worker_state_wait_for_transition(worker, WORKER_STATE_END,
					 WORKER_STATE_EXIT);

	if (worker->exit)
		worker->exit(worker->bench, worker->args, &worker->info);

	worker_state_transition(worker, WORKER_STATE_EXIT, WORKER_STATE_DONE);

	os_mutex_unlock(&worker->lock);
	return nullptr;
}

/*
 * benchmark_worker_alloc -- allocate benchmark worker
 */
struct benchmark_worker *
benchmark_worker_alloc(void)
{
	struct benchmark_worker *w =
		(struct benchmark_worker *)calloc(1, sizeof(*w));

	if (!w)
		return nullptr;

	if (os_mutex_init(&w->lock))
		goto err_free_worker;

	if (os_cond_init(&w->cond))
		goto err_destroy_mutex;

	if (os_thread_create(&w->thread, nullptr, thread_func, w))
		goto err_destroy_cond;

	return w;

err_destroy_cond:
	os_cond_destroy(&w->cond);
err_destroy_mutex:
	os_mutex_destroy(&w->lock);
err_free_worker:
	free(w);
	return nullptr;
}

/*
 * benchmark_worker_free -- release benchmark worker
 */
void
benchmark_worker_free(struct benchmark_worker *w)
{
	os_thread_join(&w->thread, nullptr);
	os_cond_destroy(&w->cond);
	os_mutex_destroy(&w->lock);
	free(w);
}

/*
 * benchmark_worker_init -- call init function for worker
 */
int
benchmark_worker_init(struct benchmark_worker *worker)
{
	os_mutex_lock(&worker->lock);

	worker_state_transition(worker, WORKER_STATE_IDLE, WORKER_STATE_INIT);

	worker_state_wait_for_transition(worker, WORKER_STATE_INIT,
					 WORKER_STATE_INITIALIZED);

	int ret = worker->ret_init;

	os_mutex_unlock(&worker->lock);

	return ret;
}

/*
 * benchmark_worker_exit -- call exit function for worker
 */
void
benchmark_worker_exit(struct benchmark_worker *worker)
{
	os_mutex_lock(&worker->lock);

	worker_state_transition(worker, WORKER_STATE_END, WORKER_STATE_EXIT);

	worker_state_wait_for_transition(worker, WORKER_STATE_EXIT,
					 WORKER_STATE_DONE);

	os_mutex_unlock(&worker->lock);
}

/*
 * benchmark_worker_run -- run benchmark worker
 */
int
benchmark_worker_run(struct benchmark_worker *worker)
{
	int ret = 0;

	os_mutex_lock(&worker->lock);

	worker_state_transition(worker, WORKER_STATE_INITIALIZED,
				WORKER_STATE_RUN);

	os_mutex_unlock(&worker->lock);

	return ret;
}

/*
 * benchmark_worker_join -- join benchmark worker
 */
int
benchmark_worker_join(struct benchmark_worker *worker)
{
	os_mutex_lock(&worker->lock);

	worker_state_wait_for_transition(worker, WORKER_STATE_RUN,
					 WORKER_STATE_END);

	os_mutex_unlock(&worker->lock);

	return 0;
}
