/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3.0 only,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * version 3.0 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3.0 along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <errno.h>

#import "OFPlainThread.h"
#import "OFData.h"
#import "OFString.h"
#import "OFTLSKey.h"

#define Class IntuitionClass
#include <dos/dostags.h>
#include <proto/dos.h>
#include <proto/exec.h>
#undef Class

#ifndef OF_MORPHOS
extern void OFTLSKeyThreadExited(void);
#endif
static OFTLSKey threadKey;

OF_CONSTRUCTOR()
{
	OFEnsure(OFTLSKeyNew(&threadKey) == 0);
}

static void
functionWrapper(void)
{
	bool detached = false;
	OFPlainThread thread =
	    (OFPlainThread)((struct Process *)FindTask(NULL))->pr_ExitData;
	OFEnsure(OFTLSKeySet(threadKey, thread) == 0);

	thread->function(thread->object);

	ObtainSemaphore(&thread->semaphore);
	@try {
		thread->done = true;

#ifndef OF_MORPHOS
		OFTLSKeyThreadExited();
#endif

		if (thread->detached)
			detached = true;
		else if (thread->joinTask != NULL)
			Signal(thread->joinTask, (1ul << thread->joinSigBit));
	} @finally {
		ReleaseSemaphore(&thread->semaphore);
	}

	if (detached)
		free(thread);
}

int
OFPlainThreadAttributesInit(OFPlainThreadAttributes *attr)
{
	attr->priority = 0;
	attr->stackSize = 0;

	return 0;
}

int
OFPlainThreadNew(OFPlainThread *thread, const char *name, void (*function)(id),
    id object, const OFPlainThreadAttributes *attr)
{
	OFMutableData *tags = nil;

	if ((*thread = calloc(1, sizeof(**thread))) == NULL)
		return ENOMEM;

	@try {
		(*thread)->function = function;
		(*thread)->object = object;
		InitSemaphore(&(*thread)->semaphore);

		tags = [[OFMutableData alloc]
		    initWithItemSize: sizeof(struct TagItem)
			    capacity: 12];
#define ADD_TAG(tag, data)			\
		{				\
			struct TagItem t = {	\
				.ti_Tag = tag,	\
				.ti_Data = data	\
			};			\
			[tags addItem: &t];	\
		}
		ADD_TAG(NP_Entry, (ULONG)functionWrapper)
		ADD_TAG(NP_ExitData, (ULONG)*thread)
#ifdef OF_AMIGAOS4
		ADD_TAG(NP_Child, TRUE)
#endif
#ifdef OF_MORPHOS
		ADD_TAG(NP_CodeType, CODETYPE_PPC);
#endif
		if (name != NULL)
			ADD_TAG(NP_Name, (ULONG)name);

		ADD_TAG(NP_Input, ((struct Process *)FindTask(NULL))->pr_CIS)
		ADD_TAG(NP_Output, ((struct Process *)FindTask(NULL))->pr_COS)
		ADD_TAG(NP_Error, ((struct Process *)FindTask(NULL))->pr_CES)
		ADD_TAG(NP_CloseInput, FALSE)
		ADD_TAG(NP_CloseOutput, FALSE)
		ADD_TAG(NP_CloseError, FALSE)

		if (attr != NULL && attr->priority != 0) {
			if (attr->priority < 1 || attr->priority > 1)
				return EINVAL;

			/*
			 * -1 should be -128 (lowest possible priority) while
			 * +1 should be +127 (highest possible priority).
			 */
			ADD_TAG(NP_Priority, (attr->priority > 0
			    ? attr->priority * 127 : attr->priority * 128))
		}

		if (attr != NULL && attr->stackSize != 0)
			ADD_TAG(NP_StackSize, attr->stackSize)
		else
			ADD_TAG(NP_StackSize,
			    ((struct Process *)FindTask(NULL))->pr_StackSize)

		ADD_TAG(TAG_DONE, 0)
#undef ADD_TAG

		(*thread)->task = (struct Task *)CreateNewProc(tags.items);
		if ((*thread)->task == NULL) {
			free(*thread);
			return EAGAIN;
		}
	} @catch (id e) {
		free(*thread);
		@throw e;
	} @finally {
		[tags release];
	}

	return 0;
}

OFPlainThread
OFCurrentPlainThread(void)
{
	return OFTLSKeyGet(threadKey);
}

bool
OFPlainThreadIsCurrent(OFPlainThread thread)
{
	return (thread->task == FindTask(NULL));
}

int
OFPlainThreadJoin(OFPlainThread thread)
{
	ObtainSemaphore(&thread->semaphore);

	if (thread->done) {
		ReleaseSemaphore(&thread->semaphore);

		free(thread);
		return 0;
	}

	@try {
		if (thread->detached || thread->joinTask != NULL)
			return EINVAL;

		if ((thread->joinSigBit = AllocSignal(-1)) == -1)
			return EAGAIN;

		thread->joinTask = FindTask(NULL);
	} @finally {
		ReleaseSemaphore(&thread->semaphore);
	}

	Wait(1ul << thread->joinSigBit);
	FreeSignal(thread->joinSigBit);

	OFAssert(thread->done);
	free(thread);

	return 0;
}

int
OFPlainThreadDetach(OFPlainThread thread)
{
	ObtainSemaphore(&thread->semaphore);

	if (thread->done)
		free(thread);
	else
		thread->detached = true;

	ReleaseSemaphore(&thread->semaphore);

	return 0;
}

void
OFSetThreadName(const char *name)
{
}
