#include <stdlib.h>
#include "sema.h"
#include "api/caller.h"

#define MXC_ASSERT_ENABLE
#include "mxc_assert.h"

void *_api_call_start(api_id_t id, uintptr_t size)
{
	while (SEMA_GetSema(_API_SEMAPHORE) == E_BUSY) {
	}

	if (API_CALL_MEM->call_flag != _API_FLAG_IDLE) {
		/*
		 * The only way this can happen is if a call was issued from an
		 * interrupt hander while another call is still happening.  This
		 * has to be prevented at all cost!
		 */
		mxc_assert(
			"API recalled during ongoing call!",
			__FILE__,
			__LINE__
		);
	}

	API_CALL_MEM->id = id;
	return API_CALL_MEM->buffer;
}

void *_api_call_transact(void *buffer)
{
	API_CALL_MEM->call_flag = _API_FLAG_CALLING;
	SEMA_FreeSema(_API_SEMAPHORE);

	/* Notify the dispather of the new call */
	__SEV();
	__WFE();

	while (1) {
		/* Wait for the dispather to return */
		__WFE();

		while (SEMA_GetSema(_API_SEMAPHORE) == E_BUSY) {
		}
		if (API_CALL_MEM->call_flag == _API_FLAG_RETURNED) {
			break;
		}
		SEMA_FreeSema(_API_SEMAPHORE);
	}

	API_CALL_MEM->call_flag = _API_FLAG_IDLE;
	SEMA_FreeSema(_API_SEMAPHORE);

	return API_CALL_MEM->buffer;
}

__attribute__((noreturn)) void epic_exit(int ret)
{
	/*
	 * Call __epic_exit() and then jump to the reset routine/
	 */
	void *buffer;

	buffer         = _api_call_start(API_SYSTEM_EXIT, sizeof(int));
	*(int *)buffer = ret;
	_api_call_transact(buffer);

	API_CALL_MEM->reset_stub();

	/* unreachable */
	while (1)
		;
}

int epic_exec(char *name)
{
	/*
	 * Call __epic_exec().  If it succeeds, jump to the reset routine.
	 * Otherwise, return the error code.
	 */
	void *buffer;

	buffer           = _api_call_start(API_SYSTEM_EXEC, sizeof(char *));
	*(char **)buffer = name;
	int ret          = *(int *)_api_call_transact(buffer);

	if (ret < 0) {
		return ret;
	}

	API_CALL_MEM->reset_stub();

	/* unreachable */
	while (1)
		;
}

int api_fetch_args(char *buf, size_t cnt)
{
	if (API_CALL_MEM->id != 0) {
		/*
		 * When any call happened before the args are fetched, they are
		 * overwritten and no longer accessible.
		 */
		return (-1);
	}

	if (API_CALL_MEM->buffer[0x20] == '\0') {
		return 0;
	}

	int i;
	for (i = 0; i < cnt && API_CALL_MEM->buffer[i + 0x20] != '\0'; i++) {
		buf[i] = API_CALL_MEM->buffer[i + 0x20];
	}

	return i - 1;
}