// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright © 2020 Florian Schmaus
#include <cstdlib>	 // for abort, exit, EXIT_SUCCESS
#include <iostream>	 // for operator<<, basic_ostream::o...

#include "BinaryPrivateSemaphore.hpp"		 // for BPS
#include "CountingPrivateSemaphore.hpp"	 // for CPS
#include "Debug.hpp"										 // for DBG
#include "Fiber.hpp"										 // for Fiber
#include "PrivateSemaphore.hpp"					 // for PS
#include "Runtime.hpp"									 // for Runtime
#include "emper-common.h"								 // for UNUSED_ARG
#include "emper.hpp"										 // for async

using fibParams = struct {
	int n;
	int* result;
	PS* sem;
};

static void fib(void* voidParams) {
	auto* params = static_cast<fibParams*>(voidParams);
	int n = params->n;
	int* result = params->result;
	if (!result) {
		std::cerr << "voidParams: " << voidParams << " n: " << params->n << " sem: " << params->sem
							<< std::endl;
		abort();
	}
	PS* sem = params->sem;

	if (n < 2) {
		*result = n;
	} else {
		CPS newSem(2);

		int a, b;

		fibParams newParams1;
		newParams1.n = n - 1;
		newParams1.result = &a;
		newParams1.sem = &newSem;
		fibParams newParams2;
		newParams2.n = n - 2;
		newParams2.result = &b;
		newParams2.sem = &newSem;

		Fiber* f1 = Fiber::from(&fib, &newParams1);
		Fiber* f2 = Fiber::from(&fib, &newParams2);

		Runtime* runtime = Runtime::getRuntime();
		runtime->schedule(*f1);
		runtime->schedule(*f2);

		DBG("fib: Calling wait for n=" << n);
		newSem.wait();

		*result = a + b;
	}

	DBG("fib: Calling signalAndExit for n=" << n);
	sem->signalAndExit();
}

static void fibKickoff() {
	const int fibNum = 2;
	int result;
	BPS sem;
	fibParams params = {fibNum, &result, &sem};

	Fiber* fibFiber = Fiber::from(fib, &params);
	async(fibFiber);

	sem.wait();

	std::cout << "fib(" << fibNum << ") = " << result << std::endl;
	exit(EXIT_SUCCESS);
}

auto main(UNUSED_ARG int argc, UNUSED_ARG char* argv[]) -> int {
	// const unsigned nthreads = std::thread::hardware_concurrency();
	const unsigned nthreads = 2;

	std::cout << "Number of threads: " << nthreads << std::endl;

	Runtime runtime(nthreads);

	Fiber* fibFiber = Fiber::from(&fibKickoff);

	std::cout << "Just alloacted alpha fiber at " << fibFiber << std::endl;

	runtime.scheduleFromAnywhere(*fibFiber);

	runtime.waitUntilFinished();

	return 0;
}