#include <amistdio.h>
#include <string.h>
#include <exec/execbase.h>
#include <exec/resident.h>
#include <proto/dos.h>
#include <proto/exec.h>

extern void exit(int);

#pragma GCC push_options
#pragma GCC optimize ("-Os")
#pragma GCC optimize ("-fno-toplevel-reorder")
#pragma GCC optimize ("-fomit-frame-pointer")

// make cli happy
long __safefail(void) {
	return -1;
}

// library header stuff - kind of forward decls - results in a rom tag directly behind the cli-do-nothing-function.
// the real values are defined at the end.
const struct Resident __RomTag;
const char __libName[80];

// place into data segment -> init with zero
__attribute__((section(".data")))
 struct Library __lib = { {0, 0, NT_LIBRARY, 0, (char *)__libName},
		 LIBF_CHANGED | LIBF_SUMUSED, 0,
		 0, 0,
		 0, 0};
// track all instantiated libs, the lib_Sum fields is used to track the opening Task
__attribute__((section(".data")))
 struct List __libList = { 0 };

const char __libName[80] = { 0 };
const APTR __FuncTable__[];

struct ExecBase *SysBase = 0;
struct Library *__theMasterLib = 0;
long __segList = 0;
long __initFailed = 0;

// needed per instance
unsigned short __cleanupflag = 0;
long __save_a4;
long __save_sp;

struct A4List {
	struct A4List * next;
	void * code;
	ULONG a4;
} __a4List;

__attribute__((section(".list___INIT_LIST__")))
const int __INIT_LIST__[1] = { 0 };
__attribute__((section(".list___EXIT_LIST__")))
const int __EXIT_LIST__[1] = { 0 };

__attribute__((section(".dlist___LIB_LIST__")))
int __LIB_LIST__[1] = { 0 };

__attribute__((section(".list___CTOR_LIST__")))
const int __CTOR_LIST__[1] = { 0 };

__attribute__((section(".list___DTOR_LIST__")))
const int __DTOR_LIST__[1] = { 0 };

// the section for the exported functions
__attribute__((section(".dlist_so_export")))
long __so_xlib_export[1] = { 0 };

__attribute__((section(".end_of_lists")))
const int __ZZZ_LIST__[1] = { 0 };
__attribute__((section(".end_of_dlists")))
int __ZZZ_DLIST__[1] = { 0 };

long __LibClose(struct Library *childLib asm("a6"));
void __callfuncs(const int *p asm("a2"), unsigned short prioo asm("d2"));

static inline VOID __NewList(struct List *_NewList_list) {
	_NewList_list->lh_TailPred = (struct Node*) _NewList_list;
	_NewList_list->lh_Head = (struct Node*) &_NewList_list->lh_Tail;
	_NewList_list->lh_Tail = 0;
}

// init the library. defer real init stuff to LibOpen!
APTR __LibInit(long __segListIn asm("a0"), struct Library *_masterlib asm("d0"), struct ExecBase *__sysBase asm("a6")) {
	// get access to the data
	register long *a4 asm("a4");
	asm volatile("move.l	a4,-(a7)" :: "r"(a4));
	asm volatile("lea	___a4_init,%0;\n" : "=r"(a4));

	struct Library *masterlib = _masterlib;

#if 0
	{
	struct DosLibrary * DOSBase = (struct DosLibrary *)OldOpenLibrary("dos.library");
	Printf("neg=%ld, pos=%ld\n", masterlib->lib_NegSize, masterlib->lib_PosSize);
	Flush(Output());
	CloseLibrary(&DOSBase->dl_lib);
	}
#endif

	/* setup private data */
	SysBase = __sysBase;
	__theMasterLib = masterlib;
	__segList = __segListIn;

//	/* set up header data */
	masterlib->lib_Node.ln_Type = NT_LIBRARY;
	masterlib->lib_Node.ln_Name = __RomTag.rt_Name;
	masterlib->lib_Flags = LIBF_CHANGED | LIBF_SUMUSED;
	masterlib->lib_Version = __lib.lib_Version;
	masterlib->lib_Revision = __lib.lib_Revision;
	masterlib->lib_IdString = __RomTag.rt_IdString;

	CopyMem(masterlib, &__lib, sizeof(__lib));

	__NewList(&__libList);

	asm volatile("move.l	(a7)+,a4" : "=r"(a4));

	/* this will be added to SysBase->LibList */
	return masterlib;
}

// bye bye library
long __LibExpunge(struct Library *_masterlib asm("a6")) {
	register long *a4 asm("a4");
	asm volatile("move.l	a4,-(a7)" :: "r"(a4));
	asm volatile("lea	___a4_init,%0;\n" : "=r"(a4));

	struct Library *masterlib = _masterlib;

	/* set delayed expunge flag */
	masterlib->lib_Flags |= LIBF_DELEXP;

	long sl = __segList;
	/* still in use? */
	if (!masterlib->lib_OpenCnt) {
		/* remove lib from SysBase->LibList */
		Remove(&masterlib->lib_Node);

		// free the allocated ram of the lib's struct
		int neg = masterlib->lib_NegSize;
		FreeMem((-neg + (char* )masterlib), neg + masterlib->lib_PosSize);
	}
	/* return the seglist for UnLoadSeg() */
	asm volatile("move.l	(a7)+,a4" : "=r"(a4));
	return sl;
}

// open the library
struct Library*
__LibOpen(struct Library *_masterlib asm("a6")) {
	register long *a4 asm("a4");
	asm volatile("move.l	a4,-(a7)" :: "r"(a4));
	asm volatile("lea	___a4_init,%0;\n" : "=r"(a4));

	struct Library *masterlib = _masterlib;

	// Forbid is on - increase the counter
	++masterlib->lib_OpenCnt;

	/* clear delayed expunge flag */
	masterlib->lib_Flags &= ~LIBF_DELEXP;

	struct Task *task = SysBase->ThisTask;
	// search the List if the task already opened this library.
	for (struct Node *node = __libList.lh_Head; node != (struct Node*) &__libList.lh_Tail; node = node->ln_Succ) {
		struct Library *childLib = (struct Library*) node->ln_Name;
		if (childLib->lib_Sum == (ULONG) task) {
			++childLib->lib_OpenCnt;
			asm volatile("move.l	(a7)+,a4" : "=r"(a4));
			return childLib;
		}
	}

	// get memory per lib
	unsigned sz = masterlib->lib_PosSize + masterlib->lib_NegSize;
	char *to = (char*) AllocVec(sz, MEMF_PUBLIC);

	// copy jump table
	CopyMemQuick(((char* )masterlib) - masterlib->lib_NegSize, to, masterlib->lib_NegSize);

	to += masterlib->lib_NegSize;

	// copy data segment
	CopyMemQuick(&__lib, to, masterlib->lib_PosSize);

	struct Library *childLib = (struct Library*) to;
	childLib->lib_Sum = (ULONG) task;
	childLib->lib_OpenCnt = 1;
	struct Node* node = (struct Node* )&childLib[1];
	node->ln_Name = (char *)childLib;
	AddHead(&__libList, node); // the child's libList as node...

	// apply datadata relocs
	long *p;
	asm volatile("lea	___datadata_relocs,%0" : "=r"(p));
	long count = *p++;
	long diff = (char*) &__lib - to;
	while (count > 0) {
		long t = (long) *p++;
		*(long*)(t + to) -= diff;
		--count;
	}
	CacheClearU();

	// reload a4 for the child library
	asm volatile(
			"move.l	%1,%0;\n"
			"add.l #32766,%0;\n"
			: "=r"(a4) : "r"(childLib));

	// run init
	__callfuncs(&__INIT_LIST__[1], 0);

	if (__initFailed) {
		__LibClose(childLib);
		childLib = 0;
	}

	// queue the library into pr_Task->tc_TrapData
	__a4List.next = (struct A4List *)task->tc_TrapData;
	__a4List.code = __LibOpen;
	__a4List.a4 = (ULONG)a4;
	task->tc_TrapData = &__a4List;

	asm volatile("move.l	(a7)+,a4" : "=r"(a4));
	return childLib;
}

/**
 * A safe way to restore a4: search the A4List
 */
void __restore_a4() {
	asm volatile("movem.l d0/a0/a1,-(a7)");
	struct ExecBase * SysBase = *(struct ExecBase **)4;
	struct Task * task = SysBase->ThisTask;
	struct A4List * a4i = (struct A4List *)task->tc_TrapData;
	while (a4i) {
		if (a4i->code == __LibOpen) {
			asm volatile("move.l %0,a4" :: "a"(a4i->a4));
			break;
		}
		a4i = a4i->next;
	}
	asm volatile("movem.l (a7)+,d0/a0/a1");
}

// close the library
long __LibClose(struct Library *childLib asm("a6")) {
	register long *a4 asm("a4");
	asm volatile("move.l	a4,-(a7)" :: "r"(a4));
	asm volatile("lea	32766(a6),%0;\n" : "=r"(a4));

	Forbid();
	--__theMasterLib->lib_OpenCnt;
	// still in user
	long r = 0;
	if (!--childLib->lib_OpenCnt) {
		// disable forbid during cleanup
		Permit();

		// run exit
		__cleanupflag ^= -1;
		__callfuncs(&__EXIT_LIST__[1], -1);

		// forbid again
		Forbid();
		// free the instance
		Remove((struct Node* )&childLib[1]);
		FreeVec(((char* )childLib) - childLib->lib_NegSize);

		// load a4 with initial data segment
		asm volatile("lea	___a4_init,%0;\n" : "=r"(a4));
		/* one less user */
		if (!__theMasterLib->lib_OpenCnt && (__theMasterLib->lib_Flags & LIBF_DELEXP))
			r = __LibExpunge(__theMasterLib);

	}
	Permit();
	asm volatile("move.l	(a7)+,a4" : "=r"(a4));
	return r;
}


extern char __export_stubs_start;
extern char __export_stubs_end;

__regargs
char const * __so_xlib_init(char const *name, void **to) {
	long *p = &__so_xlib_export[1];
	while (*p) {
		char const *oname = (char const*) *p++;
//		{
//		struct DosLibrary * DOSBase = (struct DosLibrary *)OldOpenLibrary("dos.library");
//		Printf("%s==%s\n", name, oname);
//		Flush(Output());
//		CloseLibrary(&DOSBase->dl_lib);
//		}
		void *v = (void*) *p++;
		if (0 == strcmp(name, oname)) {
			*to = v;
			if ((char *)v > &__export_stubs_start && (char *)v < &__export_stubs_end) {
				register long * l asm("a2") = (long *)v - 1;
				asm volatile("move.l a4,(%0)" :: "r"(l));
			}
			return 0;
		}
	}
	return name;
}

char const * __ResolveSymbols(long *p asm("a0"), struct Library *childLib asm("a6")) {
	register long *a4 asm("a4");
	asm volatile("move.l	a4,-(a7)" :: "r"(a4));
	asm volatile("lea	32766(a6),%0;\n" : "=r"(a4));
	char const * r = 0;
	while (!r && *p) {
		char const *name = (char const*) *p++;
		void **to = (void**) *p++;
		r = __so_xlib_init(name, to);
	}

	CacheClearU();

	asm volatile("move.l	(a7)+,a4" : "=r"(a4));
	return r;
}

extern void __initlibraries();
extern void __initcpp();

// the function table
const APTR __FuncTable__[] = { __LibOpen, __LibClose, __LibExpunge, NULL, __ResolveSymbols, (APTR) -1,
// link __initlibraries and __initcpp behind end marker^
		__initlibraries, __initcpp };

// the libraries init table
extern long __data_size;
const APTR __InitTab[4] = { (APTR) &__data_size, (APTR) &__FuncTable__[0], (APTR) NULL, (APTR) &__LibInit };

// that's what the library loader is looking for: the rom tag with references to the remaining data.
const struct Resident __RomTag = { RTC_MATCHWORD, (struct Resident*) &__RomTag, (struct Resident*) &__RomTag + 1,
RTF_AUTOINIT, 1,
NT_LIBRARY, 0, (char*) __libName,
(char*) __libName, // the linker will fix this!
(APTR) &__InitTab };

void __callfuncs(const int *q asm("a2"), unsigned short order asm("d2")) {
	for (;;) {
		const int *p = q;
		unsigned short curprio = __cleanupflag;
		unsigned short nextprio = -1;

		while (*p) {
			unsigned short prio = *((unsigned short*) p + 3) ^ order;

			// invoke
			if (prio == curprio)
				((void (*)(void)) *p)();

			// update next prio
			if (prio < nextprio && prio > curprio)
				nextprio = prio;

			p += 2;
		}
		if (nextprio == curprio)
			break;

		__cleanupflag = nextprio;
	}
}

// just in case callfunc calls something which calls exit
void exit(int x) {
	__initFailed = x;
}
