#ifndef NEONUCLEUS_H
#define NEONUCLEUS_H

#ifdef __cplusplus
extern "C" {
#endif

// Platform checking support, to help out users.
// Used internally as well.
// Based off https://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
	#ifndef NN_WINDOWS
	#define NN_WINDOWS
	#endif
#elif __APPLE__
    #ifndef NN_MACOS
    #define NN_MACOS
    #endif
#elif __linux__
    #ifndef NN_LINUX
    #define NN_LINUX
    #endif
#endif

#if __unix__
    #ifndef NN_UNIX
    #define NN_UNIX
    #endif
    #ifndef NN_POSIX
    #define NN_POSIX
    #endif
#elif defined(_POSIX_VERSION)
    #ifndef NN_POSIX
    #define NN_POSIX
    #endif
#endif


#if defined(_MSC_VER) && !defined(__cplusplus)
#ifndef NN_ATOMIC_MSVC
#define NN_ATOMIC_MSVC
#endif
#endif

#ifdef _MSC_VER
#define NN_INIT(type)
#else
#define NN_INIT(type) (type)
#endif

// every C standard header we depend on, conveniently put here
#include <stddef.h> // for NULL,
#include <stdint.h> // for intptr_t
#include <stdbool.h> // for true, false and bool

/* MSVC can't use VLA;
	* What we see 							: NN_VLA(const char *, comps, len);
	* What compiler see after preproccessor : const char * comps[len];
	* What actaully was						: const char *comps[len];
*/
// Test: gcc -E -DNN_VLA\(type,name,count\)="type name[count]" -x c - <<< 'NN_VLA(const char *, comps, len);'
#ifdef _MSC_VER
// avoid #include <malloc.h>, for it can pollute the namespace
// with symbols to functions which are not linked with in baremetal.
void *_alloca(size_t);
#define NN_VLA(type, name, count) type *name = (type *)_alloca(sizeof(type) * (count))
#else
#define NN_VLA(type, name, count) type name[count]
#endif

// Internally we need stdatomic.h and, if NN_BAREMETAL is not defined, stdlib.h and time.h

// The entire NeoNucleus API, in one header file

// Internal limits or constants

#define NN_KiB (1024)
#define NN_MiB (1024 * NN_KiB)
#define NN_GiB (1024 * NN_MiB)
#define NN_TiB ((size_t)1024 * NN_GiB)

// the alignment an allocation should have
#define NN_ALLOC_ALIGN 16
// the maximum amount of items the callstack can have.
#define NN_MAX_STACK 256
// the maximum size a path is allowed to have, including the NULL terminator!
#define NN_MAX_PATH 256
// the maximum size of a label
#define NN_MAX_LABEL 256
// maximum size of a wakeup message
#define NN_MAX_WAKEUPMSG 2048
// the maximum amount of file descriptors that can be open simultaneously
#define NN_MAX_OPENFILES 16
// the maximum amount of userdata that can be sent simultaneously.
#define NN_MAX_USERDATA 64
// maximum size of a signal, computed the same as modem packet costs.
#define NN_MAX_SIGNALSIZE 8192
// maximum amount of signals.
#define NN_MAX_SIGNALS 128
// the maximum value of a port. Ports start at 1.
#define NN_MAX_PORT 65535
// the magic port number to close all ports
#define NN_CLOSEPORTS 0
// maximum amount of architectures one machine can support.
#define NN_MAX_ARCHITECTURES 32
// maximum size of the architecture name EEPROMs can store
#define NN_MAX_ARCHNAME 64
// maximum size of an address.
// This only matters in places where an address is returned through a component, as it is the amount of space to allocate for the response.
// Past this there would be a truncation which would invalidate the address.
// However, 64 is unrealistically long, as UUIDv4 only needs 36.
// Please, do not go above this.
#define NN_MAX_ADDRESS 64
// the port used by tunnel cards. This port is invalid for modems.
#define NN_TUNNEL_PORT 0
// maximum amount of users a computer can have
#define NN_MAX_USERS 64
// maximum length of a username
#define NN_MAX_USERNAME 128

// the maximum size of a UTF-8 character
#define NN_MAX_UNICODE_BUFFER 4

// the maximum size of a component error message. If the error is bigger than this,
// it is truncated.
#define NN_MAX_ERROR_SIZE 1024

// unicode (UTF-8) support library

typedef unsigned int nn_codepoint;

bool nn_unicode_validate(const char *s, size_t len);
// validates only the *first* codepoint in the NULL-terminated string.
// This returns the length in bytes of the codepoint, with 0 meaning
// invalid.
size_t nn_unicode_validateFirstChar(const char *s, size_t len);

// returns the amount of unicode codepoints in the UTF-8 string.
// Undefined behavior for invalid UTF-8, make sure to validate it if needed.
size_t nn_unicode_len(const char *s, size_t len);
// returns the amount of unicode codepoints in the UTF-8 string.
// If s is invalid UTF-8, all invalid bytes are considered a 1-byte codepoint.
size_t nn_unicode_lenPermissive(const char *s, size_t len);

// Writes the codepoints of s into codepoints.
// Undefined behavior for invalid UTF-8, make sure to validate it if needed.
// The codepoints buffer must be big enough to store the string, use nn_unicode_len()
// to get the required buffer length.
void nn_unicode_codepoints(const char *s, size_t len, nn_codepoint *codepoints);
// Writes the codepoints of s into codepoints.
// If s is invalid UTF-8, all invalid bytes are considered a 1-byte codepoint.
// The codepoints buffer must be big enough to store the string, use nn_unicode_lenPermissive()
// to get the required buffer length.
void nn_unicode_codepointsPermissive(const char *s, size_t len, nn_codepoint *codepoints);

// Returns the first codepoint from a UTF-8 string.
// If s is invalid UTF-8, the behavior is undefined.
nn_codepoint nn_unicode_firstCodepoint(const char *s);
// Returns the size, in bytes, required by UTF-8 for a codepoint.
size_t nn_unicode_codepointSize(nn_codepoint codepoint);
// Writes the UTF-8 bytes for a given codepoint into buffer.
// It does NOT write a NULL terminator, but it does return the length.
size_t nn_unicode_codepointToChar(char buffer[NN_MAX_UNICODE_BUFFER], nn_codepoint codepoint);
// the width, on a screen, for a codepoint.
// This matters for emojies.
size_t nn_unicode_charWidth(nn_codepoint codepoint);
// The width, on a screen, for an entire string.
// The behavior is undefined for 
size_t nn_unicode_wlen(const char *s, size_t len);
size_t nn_unicode_wlenPermissive(const char *s, size_t len);

// Returns the amount of bytes needed to store the UTF-8 encoded text.
// The behavior on invalid codepoints is undefined.
size_t nn_unicode_countBytes(nn_codepoint *codepoints, size_t len);
// Writes the UTF-8 encoded text.
// DOES NOT WRITE A NULL TERMINATOR.
// s must be big enough to store the string, use nn_unicode_bytelen()
// to allocate the correct amount of space.
// The behavior on invalid codepoints is undefined.
void nn_unicode_writeBytes(char *s, nn_codepoint *codepoints, size_t len);

// Returns the uppercase version of the codepoint
nn_codepoint nn_unicode_upper(nn_codepoint codepoint);
// Returns the lowercase version of the codepoint
nn_codepoint nn_unicode_lower(nn_codepoint codepoint);

// The type of a the function used as the allocator.
// The expected behavior is as follows:
// alloc(state, NULL, 0, newSize) -> malloc(newSize)
// alloc(state, memory, oldSize, 0) -> free(memory)
// alloc(state, memory, oldSize, newSize) -> realloc(memory, newSize)
// 
// NeoNucleus will ensure oldSize is what the application requested on the last allocation.
// This is useful for allocators which may not do extensive bookkeeping.
// In the case of Out Of Memory, you are expected to return NULL.
typedef void *nn_AllocProc(void *state, void *memory, size_t oldSize, size_t newSize);

// Meant to return the time, in seconds, since some epoch.
typedef double nn_TimeProc(void *state);

typedef size_t nn_RngProc(void *state);

typedef enum nn_LockAction {
	// create the mutex 
	NN_LOCK_CREATE,
	// destroy the mutex
	NN_LOCK_DESTROY,
	// lock the mutex
	NN_LOCK_LOCK,
	// unlock the mutex
	NN_LOCK_UNLOCK,
} nn_LockAction;

typedef struct nn_LockRequest {
	// mutate it for NN_LOCK_INIT
	void *lock;
	nn_LockAction action;
} nn_LockRequest;

// Intended for a plain mutex.
// This is used for synchronization. OpenComputers achieves synchronization
// between the worker threads by sending them as requests to a central thread (indirect methods).
// In NeoNucleus, it is the same stuff, but direct ones may still be used across threads.
// Do note that locks are only used in "full" component implementations, such as the volatile storage devices.
// The interfaces do not do any automatic synchronization via locks, all synchronization is assumed
// to be handled in the implementer of the interface, because only you know how to best synchronize
// it with the outside world.
typedef void nn_LockProc(void *state, nn_LockRequest *req);

// The *context* NeoNucleus is operating in.
// This determines:
// - How memory is allocated
// - How random numbers are generated and what the range is
// - What the current time is
// - How locks work
typedef struct nn_Context {
	void *state;
	nn_AllocProc *alloc;
	nn_TimeProc *time;
	// the maximum value the RNG can produce.
	// The RNG is assumed to generate [0, rngMaximum]. INCLUSIVE.
	// When a [0, 1) range is needed, the value is divided by (rngMaximum+1),
	// so rngMaximum+1 MUST NOT OVERFLOW.
	size_t rngMaximum;
	nn_RngProc *rng;
	nn_LockProc *lock;
} nn_Context;

// if NN_BAREMETAL is defined when NeoNucleus is compiled,
// this function will fill in a bogus context that does nothing.
// Otherwise, it fills in a context which uses the C standard library.
// This function is only meant to be called once, to get the initial context.
// It does seed the RNG when using the C standard library, so do not
// call it in loops.
void nn_initContext(nn_Context *ctx);

// Memory allocation!!!

void *nn_alloc(nn_Context *ctx, size_t size);
void nn_free(nn_Context *ctx, void *memory, size_t size);
void *nn_realloc(nn_Context *ctx, void *memory, size_t oldSize, size_t newSize);

char *nn_strdup(nn_Context *ctx, const char *s);
void nn_strfree(nn_Context *ctx, char *s);

typedef struct nn_Lock nn_Lock;

nn_Lock *nn_createLock(nn_Context *ctx);
void nn_destroyLock(nn_Context *ctx, nn_Lock *lock);
void nn_lock(nn_Context *ctx, nn_Lock *lock);
void nn_unlock(nn_Context *ctx, nn_Lock *lock);

double nn_currentTime(nn_Context *ctx);

// generate a random RNG from 0 to the maximum
size_t nn_rand(nn_Context *ctx);
// generate a random float [0, 1)
double nn_randf(nn_Context *ctx);
// generate a random float [0, 1]
double nn_randfi(nn_Context *ctx);

typedef char nn_uuid[37];
void nn_randomUUID(nn_Context *ctx, nn_uuid uuid);

// Basic utils

// Does canonical path handling. Is used for sandboxing paths.
// It will turn \ to /, will turn invalid characters into spaces,
// and will resolve ...
// it also gets rid of / prefixes, / suffixes and //
void nn_simplifyPath(const char original[NN_MAX_PATH], char simplified[NN_MAX_PATH]);

typedef enum nn_Exit {
	// no error
	NN_OK = 0,
	// out of memory.
	NN_ENOMEM,
	// over the limit. For example, adding too many architectures to a machine.
	NN_ELIMIT,
	// internal stack underflow when managing the stack.
	NN_EBELOWSTACK,
	// internal stack overflow when carrying values.
	NN_ENOSTACK,
	// bad invocation, error message stored in computer state
	NN_EBADCALL,
	// bad state, the function was called at the wrong time
	NN_EBADSTATE,
} nn_Exit;

// This stores necessary data between computers
typedef struct nn_Universe nn_Universe;

nn_Universe *nn_createUniverse(nn_Context *ctx, void *userdata);
void nn_destroyUniverse(nn_Universe *universe);
void *nn_getUniverseData(nn_Universe *universe);
size_t nn_getUniverseMemoryLimit(nn_Universe *universe);
size_t nn_limitMemory(nn_Universe *universe, size_t memory);
void nn_setUniverseMemoryLimit(nn_Universe *universe, size_t limit);
size_t nn_getUniverseStorageLimit(nn_Universe *universe);
void nn_setUniverseStorageLimit(nn_Universe *universe, size_t limit);
size_t nn_limitStorage(nn_Universe *universe, size_t storage);

// The actual computer
typedef struct nn_Computer nn_Computer;

typedef enum nn_ComputerState {
	// the machine is running just fine
	NN_RUNNING = 0,
	// machine is initializing. This is the state after createComputer but before the first nn_tick.
	// It is a required state of various initialization functions.
	NN_BOOTUP,
	// the machine has powered off.
	NN_POWEROFF,
	// the machine demands being restarted.
	NN_RESTART,
	// the machine has crashed. RIP
	NN_CRASHED,
	// the machine ran out of energy.
	NN_BLACKOUT,
	// change architecture.
	NN_CHARCH,
} nn_ComputerState;

typedef enum nn_ArchitectureAction {
	// create the local state
	NN_ARCH_INIT,
	// destroy the local state
	NN_ARCH_DEINIT,
	// run 1 tick or synchronized task
	NN_ARCH_TICK,
	// get the free memory
	NN_ARCH_FREEMEM,
	// deserialize from an encoded state, passed into request.
	NN_ARCH_DESERIALIZE,
	// serialize to an encoded state, pushed it as a string
	NN_ARCH_SERIALIZE,
	// information about usage, for GC languages to know when it is safe to reclaim references.
	// computer and localState are both NULL when these are emitted.
	NN_ARCH_RETAIN,
	NN_ARCH_DROP,
} nn_ArchitectureAction;

typedef struct nn_ArchitectureRequest {
	// the state pointer passed through
	void *globalState;
	// the computer which made the request
	nn_Computer *computer;
	// the local state bound to this computer.
	// In NN_ARCH_INIT, this is NULL, and must be set to the new state, or an appropriate exit code returned.
	void *localState;
	// the action requested
	nn_ArchitectureAction action;
	union {
		// in the case of NN_ARCH_TICK, where the tick is synchronized
		bool synchronized;
		// in the case of NN_ARCH_FREEMEM, the free memory
		size_t freeMemory;
		// in the case of NN_ARCH_DESERIALIZE, the buffer.
		struct {
			const char *memIn;
			size_t memLen;
		};
	};
} nn_ArchitectureRequest;

typedef nn_Exit nn_ArchitectureHandler(nn_ArchitectureRequest *req);

typedef struct nn_Architecture {
	const char *name;
	void *state;
	nn_ArchitectureHandler *handler;
} nn_Architecture;

// Standard RAM sizes.
// Standard OC goes from tier 1 to tier 6,
// NN adds 2 more tiers.
extern size_t nn_ramSizes[8];

typedef struct nn_Beep {
	// frequenc, in Hz
	double frequency;
	// duration, in seconds
	double duration;
	// 0 is mute, 1 is 100%
	double volume;
} nn_Beep;

// Morse beep, like a normal beep except it follows a morse code pattern.
// . is a short sound
// - is a long sound, 2x in length
// a space is a pause, 2x in length and dead silent.
// Every sound, including actual pauses, have a 50ms pause between them.
typedef struct nn_MorseBeep {
	const char *pattern;
	// frequency, in Hz
	double frequency;
	// duration of a ., in seconds
	double beepDuration;
	// 0 is mute, 1 is 100%
	double volume;
} nn_MorseBeep;

typedef enum nn_EnvironmentAction {
	NN_ENV_DRAWENERGY,
	NN_ENV_POWERON,
	NN_ENV_POWEROFF,
	NN_ENV_CRASHED,
	NN_ENV_BEEP,
	NN_ENV_BEEPMORSE,
	// information about usage, for GC languages to know when it is safe to reclaim references
	NN_ENV_RETAIN,
	NN_ENV_DROP,
} nn_EnvironmentAction;

typedef struct nn_EnvironmentRequest {
	nn_Computer *computer;
	void *userdata;
	nn_EnvironmentAction action;
	union {
		// for DRAWENERGY, is the amount to remove, and must be set to the amount remaining
		double energy;
		// for BEEP, information about the beep
		nn_Beep beep;
		// for BEEPMORSE, information about the beep
		nn_MorseBeep morseBeep;
	};
} nn_EnvironmentRequest;

typedef void nn_EnvironmentHandler(nn_EnvironmentRequest *req);

typedef struct nn_Environment {
	void *userdata;
	nn_EnvironmentHandler *handler;
} nn_Environment;

// The state of a *RUNNING* computer.
// Powered off computers shall not have a state, and as far as NeoNucleus is aware,
// not exist.
// The computer API *is not thread-safe*, so it is recommended that you use an external lock to manage it if you are running
// it in a multi-threaded environment.
// The userdata pointer is meant to store external data required by the environment.
// totalMemory is a hint to the architecture as to how much memory to allow the runner to use. It is in bytes.
// maxComponents and maxDevices determine how many components can be connected simultaneously to this computer, and how much device info can be
// registered on this computer.
nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char *address, size_t totalMemory, size_t maxComponents, size_t maxDevices);
void nn_retainComputer(nn_Computer *computer);
void nn_retainComputerN(nn_Computer *computer, size_t n);
// Destroys the state, effectively shutting down the computer.
void nn_destroyComputer(nn_Computer *computer);
void nn_destroyComputerN(nn_Computer *computer, size_t n);
void nn_lockComputer(nn_Computer *computer);
void nn_unlockComputer(nn_Computer *computer);
// stops the computer if an architecture state is already present,
// will also clear the signal buffer and set the state to NN_BOOTUP.
nn_Exit nn_startComputer(nn_Computer *computer);
// destroys the architecture state if present.
// Will also do other shutdown routines, such as unmounting every
void nn_stopComputer(nn_Computer *computer);
void nn_forceCrashComputer(nn_Computer *computer, const char *s);
// returns whether an architecture state is present
bool nn_isComputerOn(nn_Computer *computer);
void nn_setComputerEnvironment(nn_Computer *computer, nn_Environment env);
void nn_retainComputerEnvironment(nn_Environment env);
void nn_dropComputerEnvironment(nn_Environment env);

void nn_setDirectCost(nn_Computer *computer, double directCost);
double nn_getDirectCost(nn_Computer *computer);

// Device information

// Standard device attribute fields

#define NN_DEVICEATTR_CLASS "class"
#define NN_DEVICEATTR_DESC "description"
#define NN_DEVICEATTR_VENDOR "vendor"
#define NN_DEVICEATTR_PRODUCT "product"
#define NN_DEVICEATTR_VERSION "version"
#define NN_DEVICEATTR_SERIAL "serial"
#define NN_DEVICEATTR_CAPACITY "capacity"
#define NN_DEVICEATTR_SIZE "size"
#define NN_DEVICEATTR_CLOCK "clock"
#define NN_DEVICEATTR_WIDTH "width"

// Standard device classes

#define NN_DEVICECLASS_SYSTEM "system"
#define NN_DEVICECLASS_BRIDGE "bridge"
#define NN_DEVICECLASS_MEMORY "memory"
#define NN_DEVICECLASS_PROCESSOR "processor"
#define NN_DEVICECLASS_ADDRESS "address"
#define NN_DEVICECLASS_STORAGE "storage"
#define NN_DEVICECLASS_DISK "disk"
#define NN_DEVICECLASS_TAPE "tape"
#define NN_DEVICECLASS_BUS "bus"
#define NN_DEVICECLASS_NETWORK "network"
#define NN_DEVICECLASS_DISPLAY "display"
#define NN_DEVICECLASS_INPUT "input"
#define NN_DEVICECLASS_PRINTER "printer"
#define NN_DEVICECLASS_MULTIMEDIA "multimedia"
#define NN_DEVICECLASS_COMMUNICATION "communication"
#define NN_DEVICECLASS_POWER "power"
#define NN_DEVICECLASS_VOLUME "volume"
#define NN_DEVICECLASS_GENERIC "generic"

typedef struct nn_DeviceField {
	const char *name;
	const char *value;
} nn_DeviceField;

typedef struct nn_CommonDeviceInfo {
	const char *CLASS;
	const char *DESC;
	const char *VENDOR;
	const char *PRODUCT;
	const char *VERSION;
	const char *SERIAL;
	const char *CAPACITY;
	const char *SIZE;
	const char *CLOCK;
	const char *WIDTH;
} nn_CommonDeviceInfo;

const char *nn_deviceInfoAt(nn_Computer *computer, size_t idx);
const nn_DeviceField *nn_getDeviceInfo(nn_Computer *computer, size_t idx, size_t *fieldCount);
nn_Exit nn_addDeviceInfo(nn_Computer *computer, const char *addr, const nn_DeviceField *fields);
nn_Exit nn_addDeviceInfoL(nn_Computer *computer, const char *addr, const nn_DeviceField *fields, size_t fieldCount);
nn_Exit nn_addCommonDeviceInfo(nn_Computer *computer, const char *addr, nn_CommonDeviceInfo info);
// Sets every field to NULL.
void nn_clearCommonDeviceInfo(nn_CommonDeviceInfo *info);
bool nn_removeDeviceInfo(nn_Computer *computer, const char *addr);

void nn_beepComputer(nn_Computer *computer, nn_Beep beep);
void nn_beepComputerMorse(nn_Computer *computer, nn_MorseBeep beep);

// get the userdata pointer
void *nn_getComputerUserdata(nn_Computer *computer);
const char *nn_getComputerAddress(nn_Computer *computer);
nn_Universe *nn_getComputerUniverse(nn_Computer *computer);
nn_Context *nn_getUniverseContext(nn_Universe *universe);
nn_Context *nn_getComputerContext(nn_Computer *computer);

// Returns the memory usage limit of the computer.
size_t nn_getTotalMemory(nn_Computer *computer);
// Gets the total amount of free memory the computer has available. The total memory - this is the amount of memory used.
size_t nn_getFreeMemory(nn_Computer *computer);
// Gets the total amount of used memory the computer has allocated.
// This is just the total minus the free, and does not take into
// account the overhead of storing the computer instance.
size_t nn_getUsedMemory(nn_Computer *computer);
// gets the current uptime of a computer. When the computer is not running, this value can be anything and loses all meaning.
double nn_getUptime(nn_Computer *computer);

// Deserialize an encoded computer state.
// Encoding depends on architecture.
// Will push it as a string and then call the architecture, which will decode it and pop it.
nn_Exit nn_deserializeComputer(nn_Computer *computer, const char *buf, size_t buflen);

// Serialize the computer state.
// Encoding depends on architecture.
// Pushes the output, if successful, as a string on the stack.
nn_Exit nn_serializeComputer(nn_Computer *computer);

// address is copied.
// It can be NULL if you wish to have no tmp address.
// It can fail due to out-of-memory errors.
nn_Exit nn_setTmpAddress(nn_Computer *computer, const char *address);
// can return NULL if none was set
const char *nn_getTmpAddress(nn_Computer *computer);

// Registers a user to the computer.
nn_Exit nn_addUser(nn_Computer *computer, const char *user);
// Unregisters a user from the computer.
// If they were never there, nothing is removed and all is fine.
// It returns if the user was originally there.
bool nn_removeUser(nn_Computer *computer, const char *user);
// NULL for out-of-bound users
// Can be used to iterate all users.
const char *nn_getUser(nn_Computer *computer, size_t idx);
// Helper function.
// Always returns true if 0 users are registered.
// If users are registered, it will only return true if the specified
// user is registered.
// This can be used for checking signals.
bool nn_hasUser(nn_Computer *computer, const char *user);

// Sets the computer's architecture.
// The architecture determines everything from how the computer runs, to how it turns off.
// Everything is limited by the architecture.
// The architecture is copied, it can be freed after this is called.
void nn_setArchitecture(nn_Computer *computer, const nn_Architecture *arch);
// Gets the current architecture.
nn_Architecture nn_getArchitecture(nn_Computer *computer);
// Notify the architecture it is being retained. This is to help GC'd languages that provide architectures know
// when the garbage collector can safely reclaim the memory again.
void nn_retainArchitecture(nn_Architecture arch);
// Notify the architecture it is being dropped. This is to help GC'd languages that provide architectures know
// when the garbage collector can safely reclaim the memory again.
void nn_dropArchitecture(nn_Architecture arch);
// Sets the computer's desired architecture.
// The desired architecture indicates, when the computer state is CHARCH, what the new architecture should be.
// This is set even if it is not in the supported architecture list, *you must check if it is in that list first.*
// The architecture is copied, it can be freed after this is called.
void nn_setDesiredArchitecture(nn_Computer *computer, const nn_Architecture *arch);
// Gets the desired architecture. This is the architecture the computer should use after changing architectures.
nn_Architecture nn_getDesiredArchitecture(nn_Computer *computer);
// Adds a new supported architecture, which indicates to the code running on this computer that it is possible to switch to that architecture.
// The architecture is copied, it can be freed after this is called.
nn_Exit nn_addSupportedArchitecture(nn_Computer *computer, const nn_Architecture *arch);
// Returns the array of supported architectures, as well as the length.
const nn_Architecture *nn_getSupportedArchitectures(nn_Computer *computer, size_t *len);
// Helper function for searching for an architecture using a computer which supports it and the architecture name.
// If the architecture is not found, it returns one with a NULL name.
nn_Architecture nn_findSupportedArchitecture(nn_Computer *computer, const char *name);

// sets the energy capacity of the computer.
void nn_setTotalEnergy(nn_Computer *computer, double maxEnergy);
// gets the energy capacity of the computer
double nn_getTotalEnergy(nn_Computer *computer);
// gets the current amount of energy
double nn_getEnergy(nn_Computer *computer);
// Returns true if there is no more energy left, and a blackout has occured.
bool nn_removeEnergy(nn_Computer *computer, double energy);

// copies the string into the local error buffer. The error is NULL terminated, but also capped by NN_MAX_ERROR_SIZE
void nn_setError(nn_Computer *computer, const char *s);
// set a default error message from an exit.
// Does nothing for EBADCALL.
void nn_setErrorFromExit(nn_Computer *computer, nn_Exit exit);
// copies the string into the local error buffer. The error is capped by NN_MAX_ERROR_SIZE-1. The -1 is there because the NULL terminator is still inserted at the end.
// Do note that nn_getError() still returns a NULL-terminated string, thus NULL terminators in this error will lead to a shortened error.
void nn_setLError(nn_Computer *computer, const char *s, size_t len);
// Gets a pointer to the local error buffer. This is only meaningful when NN_EBADCALL is returned.
const char *nn_getError(nn_Computer *computer);
// clears the computer's error buffer, making nn_getError return a simple empty string.
void nn_clearError(nn_Computer *computer);

// sets the computer state to the desired state. This is meant for architectures to report things like reboots or shutdowns, DO NOT ABUSE THIS.
void nn_setComputerState(nn_Computer *computer, nn_ComputerState state);
// gets the current computer state
nn_ComputerState nn_getComputerState(nn_Computer *computer);

// Checks if the uptime is below the idle timestamp.
bool nn_isComputerIdle(nn_Computer *computer);
// Shifts over the idle timestamp.
void nn_addIdleTime(nn_Computer *computer, double time);
void nn_resetIdleTime(nn_Computer *computer);
// runs a tick of the computer. Make sure to check the state as well!
// Does not do anything if we're currently waiting on a synced call
// This automatically resets the component budgets and call budget.
// It also sets the idle timestamp to the current uptime.
nn_Exit nn_tick(nn_Computer *computer);

// runs a synchronized tick of the computer. How this differs depends on architecture.
// Generally, this is meant to be in the same thread for all computers, and is if the external world is fundamentally not thread-safe,
// however components must interact with it.
// In this case, those component methods would be marked as NN_INDIRECT, or more accurately will not be marked as NN_DIRECT, and the architecture would queue them as synchronized tasks.
// Architectures should generally NOT ignore this if they can.
nn_Exit nn_tickSynchronized(nn_Computer *computer);

// raw component and methods

typedef struct nn_Component nn_Component;

typedef enum nn_MethodFlags {
	// calling will consume the entire call budget
	NN_INDIRECT = 0,
	// calling will only consume 1 call from the call budget
	NN_DIRECT = (1<<0),
	// this indicates this method wraps a *field*
	// getter means calling it with no arguments will return the current value,
	NN_GETTER = (1<<1),
	// this indicates this method wraps a *field*
	// setter means calling it with 1 argument will try to set the value.
	NN_SETTER = (1<<2),
} nn_MethodFlags;

#define NN_FIELD_MASK (NN_GETTER | NN_SETTER)

typedef struct nn_Method {
	const char *name;
	const char *doc;
	nn_MethodFlags flags;
} nn_Method;

typedef enum nn_ComponentAction {
	// component dropped
	NN_COMP_DROP,
	// component method invoked
	NN_COMP_INVOKE,
	// checking if component method is enabled
	// (may be locked by tier)
	NN_COMP_CHECKMETHOD,
	// userdata request
	NN_COMP_USERDATA,
} nn_ComponentAction;

typedef enum nn_UserdataAction {
	NN_USER_DROP,
	NN_USER_SERIALIZE,
	NN_USER_DESERIALIZE,
	NN_USER_GETMETHOD,
	NN_USER_INVOKE,
} nn_UserdataAction;

typedef struct nn_UserdataRequest {
	void *state;
	nn_UserdataAction action;
	union {
		struct {
			const char *data;
			size_t len;
		} deserialize;
		struct {
			size_t idx;
			// set to NULL if idx is out of bounds
			nn_Method *method;
		} getmethod;
		struct {
			const char *method;
			size_t returnCount;
		} invoke;
	};
} nn_UserdataRequest;

typedef struct nn_ComponentRequest {
	nn_Context *ctx;
	nn_Computer *computer;
	void *state;
	void *classState;
	const char *compAddress;
	nn_ComponentAction action;
	// method index
	unsigned int methodIdx;
	union {
		// return count
		size_t returnCount;
		// method enabled
		bool methodEnabled;
		nn_UserdataRequest *user;
	};
} nn_ComponentRequest;

typedef nn_Exit (nn_ComponentHandler)(nn_ComponentRequest *request);

// creates a blank component.
// It has no methods, 
nn_Component *nn_createComponent(nn_Universe *universe, const char *address, const char *type);
void nn_retainComponent(nn_Component *c);
void nn_retainComponentN(nn_Component *c, size_t n);
void nn_dropComponent(nn_Component *c);
void nn_dropComponentN(nn_Component *c, size_t n);

// configure the state
void nn_setComponentHandler(nn_Component *c, nn_ComponentHandler *handler);
void nn_setComponentState(nn_Component *c, void *state);
void nn_setComponentClassState(nn_Component *c, void *state);
// sets the methods, same implications as setComponentMethodsArray.
// methods is NULL-terminated, as in, it is terminated by a method with a NULL name.
nn_Exit nn_setComponentMethods(nn_Component *c, const nn_Method *methods);
// sets the methods.
// The memory of the strings is copied, so they can be freed after this returns.
// This operation is NOT atomic, if it fails, it will clear out the previous methods.
nn_Exit nn_setComponentMethodsArray(nn_Component *c, const nn_Method *methods, size_t count);
// Sets an internal type ID, which is meant to be a more precise typename.
// For example, ncomplib would set ncl-screen for the screen component,
// so the GPU can confirm it is being bound to a screen it knows how to use.
nn_Exit nn_setComponentTypeID(nn_Component *c, const char *internalTypeID);

// Sets the method flags
void nn_setComponentMethodFlags(nn_Component *c, const char *method, nn_MethodFlags flags);
// combines method flags
void nn_addComponentMethodFlags(nn_Component *c, const char *method, nn_MethodFlags flags);
// removes method flags
void nn_removeComponentMethodFlags(nn_Component *c, const char *method, nn_MethodFlags flags);

// get component state
void *nn_getComponentState(nn_Component *c);
// get component class state
void *nn_getComponentClassState(nn_Component *c);
// counts how many methods are registered. May return too many if some of them are not enabled.
size_t nn_countComponentMethods(nn_Component *c);
// will fill the methodnames array with the names of the *enabled* methods.
// Will set *len to the amount of methods.
void nn_getComponentMethods(nn_Component *c, const char **methodnames, size_t *len);
// whether a method is defined and enabled
bool nn_hasComponentMethod(nn_Component *c, const char *method);
const char *nn_getComponentDoc(nn_Component *c, const char *method);
nn_MethodFlags nn_getComponentMethodFlags(nn_Component *c, const char *method);
const char *nn_getComponentType(nn_Component *c);
const char *nn_getComponentTypeID(nn_Component *c);
const char *nn_getComponentAddress(nn_Component *c);

// Adds a component to the computer on a given slot.
// This will also queue a component_added signal if the computer is in a running state, unless silent is true.
// If the component already is mounted, an error is returned.
nn_Exit nn_mountComponent(nn_Computer *c, nn_Component *comp, int slot, bool silent);
// Removes a component from the computer.
// This will also queue a component_removed signal if the computer is in a running state, unless silent is true.
// If the component is not mounted, no error is returned.
nn_Exit nn_unmountComponent(nn_Computer *c, const char *address, bool silent);
nn_Exit nn_swapComponents(nn_Computer *c, nn_Component *previous, nn_Component *next, int slot);
// gets a component by address. Will return NULL if there is none.
nn_Component *nn_getComponent(nn_Computer *c, const char *address);
int nn_getComponentSlot(nn_Computer *c, const char *address);
size_t nn_countComponents(nn_Computer *c);
void nn_getComponents(nn_Computer *c, const char **components);

// invoke the component method.
// Everything on-stack is taken as an argument.
// Will pop off trailing nulls.
// Every remaining stack value is what the component returned.
// In the case of errors, the contents of the stack is undefined
nn_Exit nn_invokeComponent(nn_Computer *computer, const char *compAddress, const char *method);

// send a signal to a component.
// Computer actually can be NULL, but the component may crash if the signal
// assumes one is specified.
nn_Exit nn_signalComponent(nn_Component *component, nn_Computer *computer, const char *signal);

// Userdata!!!!

// Allocates a userdata index. Returns -1 on failure or if there are too many.
int nn_allocUserdata(nn_Computer *computer, void *state, const char *compAddress);

// Frees a userdata index.
void nn_freeUserdata(nn_Computer *computer, size_t userdata);

// Returns whether the userdata index is valid
bool nn_isUserdataValid(nn_Computer *computer, size_t userdata);

// If compAddress is correct and userdata is valid, returns the state pointer.
// If not, returns NULL, to prevent UB.
void *nn_unwrapUserdata(nn_Computer *computer, size_t userdata, const char *compAddress);

// gets the component address which manages this userdata
const char *nn_getUserdataComponent(nn_Computer *computer, size_t userdata);

// Gets information about a method of this userdata, by index.
// If idx is out of bounds, this returns true, which means to stop iteration.
// If method->name is NULL, the method should be skipped.
bool nn_getUserdataMethod(nn_Computer *computer, size_t userdata, size_t idx, nn_Method *method);

// Invokes a method on some userdata, same semantics as nn_invokeComponent
nn_Exit nn_invokeUserdata(nn_Computer *computer, size_t userdata, const char *method);

// Serializes the userdata into a buffer and pushes it as a string.
// Make sure to keep track of its index and component address!
nn_Exit nn_serializeUserdata(nn_Computer *computer, size_t userdata);

// Deserializes userdata at a particular index.
// NOTE: if the component does not exist, or the userdata index is already taken, this errors.
nn_Exit nn_deserializeUserdata(nn_Computer *computer, size_t userdata, const char *compAddress, const char *buf, size_t len);

// default call budgets for 4 tiers of CPUs
extern double nn_defaultCallBudgets[4];
// the call budget of a creative CPU
extern double nn_unlimitedCallBudget;
// default component limits for 4 tiers of component buses / CPUs
extern size_t nn_defaultComponentLimits[4];
// the component limit of a creative component bus
extern size_t nn_creativeComponentLimit;

// Sets the call budget.
// The default is 1.
void nn_setCallBudget(nn_Computer *computer, double budget);

// gets the total call budget
double nn_getCallBudget(nn_Computer *computer);

// returns the remaining call budget
double nn_callBudgetRemaining(nn_Computer *computer);

// automatically called by nn_tick()
void nn_resetCallBudget(nn_Computer *computer);

// returns whether there is no more call budget left.
// At this point, the architecture should exit with a yield.
bool nn_componentsOverused(nn_Computer *computer);

void nn_resetComponentBudgets(nn_Computer *computer);

// Uses 1/perTick to the component budget.
// Upon a full component budget being used for that component, it returns true.
// nn_componentsOverused() will also return true.
// This indicates the architecture should yield, to throttle the computer for overuse.
bool nn_costComponent(nn_Computer *computer, double perTick);
// Uses amount/perTick to the component budget.
// Upon a full component budget being used for that component, it returns true.
// nn_componentsOverused() will also return true.
// This indicates the architecture should yield, to throttle the computer for overuse.
bool nn_costComponentN(nn_Computer *computer, double amount, double perTick);

// call stack operations.
// The type system and API are inspired by Lua, as Lua remains the most popular architecture for OpenComputers.
// This does support other languages, however it may make some APIs clunky due to the usage of tables and 1-based indexing.
// Internally, reference counting is used to manage the memory automatically. The API is designed such that strong reference cycles
// cannot occur.

// returns if there is enough space for [amount] values
bool nn_checkstack(nn_Computer *computer, size_t amount);

// pushes a null on the call stack
nn_Exit nn_pushnull(nn_Computer *computer);
// pushes a boolean on the call stack
nn_Exit nn_pushbool(nn_Computer *computer, bool truthy);
// pushes a number on the call stack
nn_Exit nn_pushnumber(nn_Computer *computer, double num);
// casts [num] to a double and pushes it on the call stack
nn_Exit nn_pushinteger(nn_Computer *computer, intptr_t num);
// pushes a NULL-terminated string on the call stack. The string is copied, so you can free it afterwards without worry.
nn_Exit nn_pushstring(nn_Computer *computer, const char *str);
// pushes a string on the call stack. The string is copied, so you can free it afterwards without worry. The copy will have a NULL terminator inserted
// at the end for APIs which need it, but the length is also stored.
nn_Exit nn_pushlstring(nn_Computer *computer, const char *str, size_t len);
// pushes a computer userdata to the stack. This is indicative of a resource, such as an HTTP request.
nn_Exit nn_pushuserdata(nn_Computer *computer, size_t userdataIdx);
// pushes a table meant to be an array. [len] is the length of the array. The keys are numbers and 1-indexed, just like in Lua.
// The values are popped, then the array is pushed.
nn_Exit nn_pusharraytable(nn_Computer *computer, size_t len);
// pushes a table. [len] is the amount of pairs. Keys should not be duplicated, as they are not de-duplicated.
// The stack should have a sequence of K1,V1,K2,V2,etc., len pairs long.
// The pairs are popped, then the array is pushed.
nn_Exit nn_pushtable(nn_Computer *computer, size_t len);

// stack management

// pops the top value off the stack
nn_Exit nn_pop(nn_Computer *computer);
// pops the top N values off the stack
nn_Exit nn_popn(nn_Computer *computer, size_t n);
// pushes the top value onto the stack, effectively duplicating the top value.
nn_Exit nn_dupe(nn_Computer *computer);
// pushes the top N values onto the stack, effectively duplicating the top N values.
nn_Exit nn_dupen(nn_Computer *computer, size_t n);

// pushes the value at idx.
nn_Exit nn_dupeat(nn_Computer *computer, size_t idx);

// get the current amount of values on the call stack.
// For component calls, calling this at the start effectively gives you the argument count.
size_t nn_getstacksize(nn_Computer *computer);
// Removes all values from the stack.
// It is recommended to do this when initiating a component call or
// after returning from errors, as the call stack may have
// random junk on it.
void nn_clearstack(nn_Computer *computer);

// type check! The API may misbehave if types do not match, so always type-check!

// Returns whether the value at [idx] is a null.
// [idx] starts at 0.
bool nn_isnull(nn_Computer *computer, size_t idx);
// Returns whether the value at [idx] is a boolean.
// [idx] starts at 0.
bool nn_isboolean(nn_Computer *computer, size_t idx);
// Returns whether the value at [idx] is a number.
// [idx] starts at 0.
bool nn_isnumber(nn_Computer *computer, size_t idx);
// Returns whether the value at [idx] is a number AND
// the number can safely be cast to an intptr_t.
bool nn_isinteger(nn_Computer *computer, size_t idx);
// Returns whether the value at [idx] is a string.
// [idx] starts at 0.
bool nn_isstring(nn_Computer *computer, size_t idx);
// Returns whether the value at [idx] is a userdata.
// [idx] starts at 0.
bool nn_isuserdata(nn_Computer *computer, size_t idx);
// Returns whether the value at [idx] is a table.
// [idx] starts at 0.
bool nn_istable(nn_Computer *computer, size_t idx);
// Returns the name of the type of the value at that index.
// For out of bounds indexes, "none" is returned.
const char *nn_typenameof(nn_Computer *computer, size_t idx);

// Argument helpers

// Returns true if the argument at that index is not null.
bool nn_checknull(nn_Computer *computer, size_t idx, const char *errMsg);
// Returns true if the argument at that index is not a boolean.
bool nn_checkboolean(nn_Computer *computer, size_t idx, const char *errMsg);
// Returns true if the argument at that index is not a number.
bool nn_checknumber(nn_Computer *computer, size_t idx, const char *errMsg);
// Returns true if the argument at that index is not an integer.
bool nn_checkinteger(nn_Computer *computer, size_t idx, const char *errMsg);
// Returns true if the argument at that index is not a string.
bool nn_checkstring(nn_Computer *computer, size_t idx, const char *errMsg);
// Returns true if the argument at that index is not userdata.
bool nn_checkuserdata(nn_Computer *computer, size_t idx, const char *errMsg);
// Returns true if the argument at that index is a table.
bool nn_checktable(nn_Computer *computer, size_t idx, const char *errMsg);

// Checks if idx is equal to the stack size.
// If it is, it will push a null.
nn_Exit nn_defaultnull(nn_Computer *computer, size_t idx);
// Checks if idx is equal to the stack size.
// If it is, it will push a boolean [value].
nn_Exit nn_defaultboolean(nn_Computer *computer, size_t idx, bool value);
// Checks if idx is equal to the stack size.
// If it is, it will push a number [num].
nn_Exit nn_defaultnumber(nn_Computer *computer, size_t idx, double num);
// Checks if idx is equal to the stack size.
// If it is, it will push an integer [num].
nn_Exit nn_defaultinteger(nn_Computer *computer, size_t idx, intptr_t num);
// Checks if idx is equal to the stack size.
// If it is, it will push a string [str].
nn_Exit nn_defaultstring(nn_Computer *computer, size_t idx, const char *str);
// Checks if idx is equal to the stack size.
// If it is, it will push a string [str].
nn_Exit nn_defaultlstring(nn_Computer *computer, size_t idx, const char *str, size_t len);
// Checks if idx is equal to the stack size.
// If it is, it will push the userdata [userdataIdx].
nn_Exit nn_defaultuserdata(nn_Computer *computer, size_t idx, size_t userdataIdx);
// Checks if idx is equal to the stack size.
// If it is, it will push an empty table.
nn_Exit nn_defaulttable(nn_Computer *computer, size_t idx);

// NOTE: behavior of the nn_to*() functions and nn_dumptable() when the values have the wrong types or at out of bounds indexes is undefined.

// Returns the boolean value at [idx].
bool nn_toboolean(nn_Computer *computer, size_t idx);
// Returns the number value at [idx].
double nn_tonumber(nn_Computer *computer, size_t idx);
// Returns the number value at [idx] cast to an intptr_t.
// NOTE: for numbers where nn_isinteger() returns false,
// the cast is undefined.
// This includes values such as infinity and NaN, where 
// the behavior is platform, ABI and compiler-specific.
intptr_t nn_tointeger(nn_Computer *computer, size_t idx);
// Returns the string value at [idx].
const char *nn_tostring(nn_Computer *computer, size_t idx);
// Returns the string value and its length at [idx].
const char *nn_tolstring(nn_Computer *computer, size_t idx, size_t *len);
// Returns the userdata index at [idx].
size_t nn_touserdata(nn_Computer *computer, size_t idx);
// Takes a table value and pushes onto the stack the key-value pairs, as well as writes how many there were in [len].
// It pushes them as K1,V1,K2,V2,K3,V3,etc., just like went in to pushtable. And yes, the keys are not de-duplicated.
nn_Exit nn_dumptable(nn_Computer *computer, size_t idx, size_t *len);

// computes the cost of the top [values] values using the same algorithm as
// the modem.
// It will return -1 if the values are invalid.
// The algorithm is as mentioned in https://ocdoc.cil.li/component:modem, with 1 small change to match OC's code.
// and is as follows:
// - Every value adds a 2 byte overhead
// - Numbers add another 8 bytes, true/false/null another 1 byte, strings as
// many bytes as they contain, except empty strings count as 1 byte.
int nn_countValueCost(nn_Computer *computer, size_t values);

// computes the signal cost.
// This is a slightly modified version of value cost, except it allows
// tables and userdata.
// All values are always valid.
// For userdata and tables:
// - Userdata adds another 8 bytes overhead like numbers do.
// - Tables add yet another 2 byte overhead for their terminator, and the sum of all of the size of the keys and values they contain as per this algorithm.
size_t nn_countSignalCost(nn_Computer *computer, size_t values);

// Returns the amount of signals stored
size_t nn_countSignals(nn_Computer *computer);
// Pops [valueCount] values from the call stack and pushes them as a signal.
nn_Exit nn_pushSignal(nn_Computer *computer, size_t valueCount);
// Removes the first signal and pushes the values onto the call stack, while setting valueCount to the amount of values in the signal.
// If there is no signal, it returns EBADSTATE
nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount);

nn_Exit nn_transferErrorFrom(nn_Exit exit, nn_Computer *from, nn_Computer *to);

// Signal helpers

// common mouse buttons, not an exhaustive list
#define NN_BUTTON_LEFT 0
#define NN_BUTTON_RIGHT 1
#define NN_BUTTON_MIDDLE 2

// OC keycodes
// taken from https://github.com/MightyPirates/OpenComputers/blob/52da41b5e171b43fea80342dc75d808f97a0f797/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_keyboard.lua
#define NN_KEY_UNKNOWN 0
#define NN_KEY_1 0x02
#define NN_KEY_2 0x03
#define NN_KEY_3 0x04
#define NN_KEY_4 0x05
#define NN_KEY_5 0x06
#define NN_KEY_6 0x07
#define NN_KEY_7 0x08
#define NN_KEY_8 0x09
#define NN_KEY_9 0x0A
#define NN_KEY_0 0x0B
#define NN_KEY_A 0x1E
#define NN_KEY_B 0x30
#define NN_KEY_C 0x2E
#define NN_KEY_D 0x20
#define NN_KEY_E 0x12
#define NN_KEY_F 0x21
#define NN_KEY_G 0x22
#define NN_KEY_H 0x23
#define NN_KEY_I 0x17
#define NN_KEY_J 0x24
#define NN_KEY_K 0x25
#define NN_KEY_L 0x26
#define NN_KEY_M 0x32
#define NN_KEY_N 0x31
#define NN_KEY_O 0x18
#define NN_KEY_P 0x19
#define NN_KEY_Q 0x10
#define NN_KEY_R 0x13
#define NN_KEY_S 0x1F
#define NN_KEY_T 0x14
#define NN_KEY_U 0x16
#define NN_KEY_V 0x2F
#define NN_KEY_W 0x11
#define NN_KEY_X 0x2D
#define NN_KEY_Y 0x15
#define NN_KEY_Z 0x2C

#define NN_KEY_APOSTROPHE 0x28
#define NN_KEY_AT 0x91
#define NN_KEY_BACK 0x0E
#define NN_KEY_BACKSLASH 0x2B
// caps-lock
#define NN_KEY_CAPITAL 0x3A
#define NN_KEY_COLON 0x92
#define NN_KEY_COMMA 0x33
#define NN_KEY_ENTER 0x1C
#define NN_KEY_EQUALS 0x0D
// accent grave
#define NN_KEY_GRAVE 0x29
#define NN_KEY_LBRACKET 0x1A
#define NN_KEY_LCONTROL 0x1D
// left alt
#define NN_KEY_LMENU 0x38
#define NN_KEY_LSHIFT 0x2A
#define NN_KEY_MINUS 0x0C
#define NN_KEY_NUMLOCK 0x45
#define NN_KEY_PAUSE 0xC5
#define NN_KEY_PERIOD 0x34
#define NN_KEY_RBRACKET 0x1B
#define NN_KEY_RCONTROL 0x9D
// right alt
#define NN_KEY_RMENU 0xB8
#define NN_KEY_RSHIFT 0x36
// scroll lock
#define NN_KEY_SCROLL 0x46
#define NN_KEY_SEMICOLON 0x27
#define NN_KEY_SLASH 0x35
#define NN_KEY_SPACE 0x39
#define NN_KEY_STOP 0x95
#define NN_KEY_TAB 0x0F
#define NN_KEY_UNDERLINE 0x93

#define NN_KEY_UP 0xC8
#define NN_KEY_DOWN 0xD0
#define NN_KEY_LEFT 0xCB
#define NN_KEY_RIGHT 0xCD
#define NN_KEY_HOME 0xC7
#define NN_KEY_END 0xCF
#define NN_KEY_PAGEUP 0xC9
#define NN_KEY_PAGEDOWN 0xD1
#define NN_KEY_INSERT 0xD2
#define NN_KEY_DELETE 0xD3

#define NN_KEY_F1 0x3B
#define NN_KEY_F2 0x3C
#define NN_KEY_F3 0x3D
#define NN_KEY_F4 0x3E
#define NN_KEY_F5 0x3F
#define NN_KEY_F6 0x40
#define NN_KEY_F7 0x41
#define NN_KEY_F8 0x42
#define NN_KEY_F9 0x43
#define NN_KEY_F10 0x44
#define NN_KEY_F11 0x57
#define NN_KEY_F12 0x58
#define NN_KEY_F13 0x64
#define NN_KEY_F14 0x65
#define NN_KEY_F15 0x66
#define NN_KEY_F16 0x67
#define NN_KEY_F17 0x68
#define NN_KEY_F18 0x69
#define NN_KEY_F19 0x71

#define NN_KEY_KANA 0x70
#define NN_KEY_KANJI 0x94
#define NN_KEY_CONVERT 0x79
#define NN_KEY_NOCONVERT 0x7B
#define NN_KEY_YEN 0x7D
#define NN_KEY_CIRCUMFLEX 0x90
#define NN_KEY_AX 0x96

#define NN_KEY_NUMPAD0 0x52
#define NN_KEY_NUMPAD1 0x4F
#define NN_KEY_NUMPAD2 0x50
#define NN_KEY_NUMPAD3 0x51
#define NN_KEY_NUMPAD4 0x4B
#define NN_KEY_NUMPAD5 0x4C
#define NN_KEY_NUMPAD6 0x4D
#define NN_KEY_NUMPAD7 0x47
#define NN_KEY_NUMPAD8 0x48
#define NN_KEY_NUMPAD9 0x49
#define NN_KEY_NUMPADMUL 0x37
#define NN_KEY_NUMPADDIV 0xB5
#define NN_KEY_NUMPADSUB 0x4A
#define NN_KEY_NUMPADADD 0x4E
#define NN_KEY_NUMPADDECIMAL 0x53
#define NN_KEY_NUMPADCOMMA 0xB3
#define NN_KEY_NUMPADENTER 0x9C
#define NN_KEY_NUMPADEQUALS 0x8D

// the bottom side, can also be downwards / negative y
#define NN_SIDE_BOTTOM 0
// the top side, can also be upwards / positive y
#define NN_SIDE_TOP 1
// backwards, can also be north / negative z
#define NN_SIDE_BACK 2
// forwards, can also be south / positive z
#define NN_SIDE_FRONT 3
// to the right, can also be west / negative x
#define NN_SIDE_RIGHT 4
// to the left, can also be east / positive x
#define NN_SIDE_LEFT 5
// absolutely no clue
#define NN_SIDE_UNKNOWN 6

// pushes a screen_resized signal
nn_Exit nn_pushScreenResized(nn_Computer *computer, const char *screenAddress, int newWidth, int newHeight);
// pushes a touch signal
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushTouch(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player);
// pushes a drag signal
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushDrag(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player);
// pushes a drop signal
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushDrop(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player);
// pushes a scroll signal
// A positive direction usually means up, a negative one usually means down.
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushScroll(nn_Computer *computer, const char *screenAddress, double x, double y, double direction, const char *player);
// pushes a walk signal
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushWalk(nn_Computer *computer, const char *screenAddress, double x, double y, const char *player);

// pushes a key_down event
// charcode is the unicode code-point of the typed character. It should be uppercase/lowercase depending on shift or capslock.
// keycode is an OC-specific keycode, and should be from the NN_KEY_* constants.
// player is the name of the player which used the keyboard. Some programs use it for splitscreen games.
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushKeyDown(nn_Computer *computer, const char *keyboardAddress, nn_codepoint charcode, int keycode, const char *player);
// pushes a key_up event
// charcode is the unicode code-point of the typed character. It should be uppercase/lowercase depending on shift or capslock.
// keycode is an OC-specific keycode, and should be from the NN_KEY_* constants.
// player is the name of the player which used the keyboard. Some programs use it for splitscreen games.
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushKeyUp(nn_Computer *computer, const char *keyboardAddress, nn_codepoint charcode, int keycode, const char *player);
// pushes a clipboard event
// clipboard should be a NULL-terminated string.
// NN does no truncation of the contents, but it is best to limit it.
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushClipboard(nn_Computer *computer, const char *keyboardAddress, const char *clipboard, const char *player);
// pushes a clipboard event
// len is the length of the clipboard.
// NN does no truncation of the contents, but it is best to limit it.
// The signal is checked, as in, the player must be a user of the computer if users are defined.
nn_Exit nn_pushLClipboard(nn_Computer *computer, const char *keyboardAddress, const char *clipboard, size_t len, const char *player);

// pushes a redstone_changed signal.
// side is the side of the device the redstone changed on
// oldValue is the old value
// newValue is the new value, must not be equal to oldValue
// color is the color of the redstone signal
// if color < 0, it is set to null
nn_Exit nn_pushRedstoneChanged(nn_Computer *computer, const char *redstoneAddress, int side, int oldValue, int newValue, int color);

// pushes a motion signal.
// Do note that it is meant to only be sent if the entity has a direct line of sight and if |(relX, relY, relZ)| >= sensitivity.
// This signal does *not* check if the motion sensor actually is sensitive enough to detect it, so make sure to check it yourself.
// relX, relY and relZ are the relative postion in 3D Cartesian space.
// entityName can be NULL if the entity has no name.
nn_Exit nn_pushMotion(nn_Computer *computer, double relX, double relY, double relZ, const char *entityName);

// Pushes an internet_ready signal.
// This signal is queued when an internet socket is ready for commmunication.
nn_Exit nn_pushInternetReady(nn_Computer *computer, const char *id, size_t idlen);

// A buffer with encoded values
typedef struct nn_EncodedNetworkContents {
	nn_Context *ctx;
	char *buf;
	size_t buflen;
	size_t valueCount;
} nn_EncodedNetworkContents;

// applies basic encoding to a network message. This encoding has a header, and thus should remain backwards-compatible.
// The encoding serves 2 purposes:
// 1. Prevent shared memory between computers. Values do not use atomic reference counting, and thus this could lead to UAF or memory leaks.
// 2. Simplify implementing packet queues, which should be used in relays. The lack of access to raw values would force implementers to use
// an encoding anyways.
// This only encodes the contents, not the sender, hops, or other metadata which may be needed in the queue.
// This does not pop the values, in case you need them afterwards. If you don't just call nn_popn().
// The encoding is architecture-dependent, so be careful with storing it on-disk.
// Do note that the architecture-dependent parts are sizeof(double), sizeof(size_t) and endianness.
// The encoding is simple:
// - 0x00 for null
// - 0x01 for true
// - 0x02 for false
// - 0x03 + <bytes of double> for a number
// - 0x04 + <bytes of size_t length> + <bytes> for a string
// - 0x05 + <bytes of size_t id> for resource
// - 0x06 + <bytes of size_t length> + <values> for a table
nn_Exit nn_encodeNetworkContents(nn_Computer *computer, nn_EncodedNetworkContents *contents, size_t valueCount);
// Allocates a copy of [buf] and stores it in contents.
// This is useful for copying network contents, either from storage or from another buffer.
nn_Exit nn_copyNetworkContents(nn_Context *ctx, nn_EncodedNetworkContents *contents, const char *buf, size_t buflen, size_t valueCount);
void nn_dropNetworkContents(nn_EncodedNetworkContents *contents);
// Pushes the encoded contents onto the stack.
// This does not drop the network contents.
nn_Exit nn_pushNetworkContents(nn_Computer *computer, const nn_EncodedNetworkContents *contents);

// push a modem_message, can be queued by both modems and tunnels.
// This does not check if the modem has that port open, so make sure to check it yourself.
// It does not check if the distance is within the modem's range, if it is wireless, and thus does not send it.
// Note that relays should change the sender.
nn_Exit nn_pushModemMessage(nn_Computer *computer, const char *modemAddress, const char *sender, int port, double distance, const nn_EncodedNetworkContents *contents);

// EEPROM class

// reads and writes are always 1/1
typedef struct nn_EEPROM {
	// the maximum capacity of the EEPROM
	size_t size;
	// the maximum capacity of the EEPROM's associated data
	size_t dataSize;
	// the energy cost of reading an EEPROM
	double readEnergyCost;
	// the energy cost of reading an EEPROM's associated data
	double readDataEnergyCost;
	// the energy cost of writing to an EEPROM
	double writeEnergyCost;
	// the energy cost of writing to an EEPROM's associated data
	double writeDataEnergyCost;
	// idle time added when writing code
	double writeDelay;
	// idle time added when writing data
	double writeDataDelay;
} nn_EEPROM;

typedef enum nn_EEPROMAction {
	// component is dropped
	NN_EEPROM_DROP,
	// check if readonly. If so, buflen should be 1, else it should be 0.
	NN_EEPROM_ISRO,
	// make the EEPROM readonly. Checksum already verified.
	NN_EEPROM_MKRO,
	// write the contents of the code into buf.
	// Set buflen to the length.
	NN_EEPROM_GET,
	// store the contents in buf into the EEPROM as code.
	// the length of buf is in buflen.
	NN_EEPROM_SET,
	NN_EEPROM_GETDATA,
	NN_EEPROM_SETDATA,
	NN_EEPROM_GETARCH,
	NN_EEPROM_SETARCH,
	NN_EEPROM_GETLABEL,
	NN_EEPROM_SETLABEL,
} nn_EEPROMAction;

typedef struct nn_EEPROMRequest {
	nn_Context *ctx;
	nn_Computer *computer;
	void *state;
	const nn_EEPROM *eeprom;
	nn_EEPROMAction action;
	union {
		char *buf;
		const char *robuf;
		bool readonly;
	};
	size_t buflen;
} nn_EEPROMRequest;

typedef nn_Exit (nn_EEPROMHandler)(nn_EEPROMRequest *request);

// Tier 1 - The normal EEPROM equivalent
// Tier 2 - A better EEPROM
// Tier 3 - An even better EEPROM
// Tier 4- The best EEPROM
extern const nn_EEPROM nn_defaultEEPROMs[4];

nn_Component *nn_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, void *state, nn_EEPROMHandler *handler);

// Filesystem class

typedef struct nn_Filesystem {
	// the maximum capacity of the filesystem
	size_t spaceTotal;
	// how many read calls can be done per tick
	// seek also count as reads.
	double readsPerTick;
	// how many write calls can be done per tick
	double writesPerTick;
	// how many open calls can be done per tick
	double opensPerTick;
	// The energy cost of an actual read/write.
	// It is per-byte, so if a read returns 4096 bytes, then this cost is multiplied by 4096.
	double dataEnergyCost;
	// maximum size of a read.
	// Do note that this entire buffer is allocated, and thus if you
	// set it to a high number, you may get weird high allocations.
	// This also determines read performance.
	size_t maxReadSize;
} nn_Filesystem;

typedef enum nn_FSAction {
	NN_FS_DROP,
	
	// drive metadata
	NN_FS_SPACEUSED,
	NN_FS_GETLABEL,
	NN_FS_SETLABEL,
	NN_FS_ISRO,

	// for file I/O
	NN_FS_OPEN,
	NN_FS_CLOSE,
	NN_FS_READ,
	NN_FS_WRITE,
	NN_FS_SEEK,

	// for list
	NN_FS_OPENDIR,
	NN_FS_READDIR,
	NN_FS_CLOSEDIR,

	// checking metadata
	NN_FS_STAT,
	// make directory, recursively
	NN_FS_MKDIR,
	
	// rename, if renamed to NULL then remove
	NN_FS_RENAME,
} nn_FSAction;

typedef enum nn_FSWhence {
	NN_SEEK_SET,
	NN_SEEK_CUR,
	NN_SEEK_END,
} nn_FSWhence;

typedef struct nn_FSRequest {
	nn_Context *ctx;
	nn_Computer *computer;
	void *state;
	const nn_Filesystem *fs;
	nn_FSAction action;
	int fd;
	union {
		struct {
			const char *path;
			const char *mode;
		} open;
		struct {
			char *buf;
			size_t len;
		} read;
		struct {
			const char *buf;
			size_t len;
		} write;
		struct {
			nn_FSWhence whence;
			// set to new offset
			int off;
		} seek;
		const char *opendir;
		struct {
			// directory path, as a reminder if need be
			const char *dirpath;
			char *buf;
			// set to length of entry name
			size_t len;
		} readdir;
		struct {
			// set to NULL if missing
			const char *path;
			// whether it is a directory
			bool isDirectory;
			// in seconds. Result will be multiplied by 1000.
			// This is because OpenOS code is garbage.
			intptr_t lastModified;
			// size. 0 for directories.
			size_t size;
		} stat;
		struct {
			const char *from;
			// if NULL, delete from, recursively.
			const char *to;
		} rename;
		const char *mkdir;
		struct {
			const char *buf;
			size_t len;
		} setlabel;
		struct {
			char *buf;
			size_t len;
		} getlabel;
		bool isReadonly;
		size_t spaceUsed;
	};
} nn_FSRequest;

typedef nn_Exit (nn_FSHandler)(nn_FSRequest *request);

// 4 Tiers.
// 0 - Tier 1 equivalent
// 1 - Tier 2 equivalent
// 2 - Tier 3 equivalent
// 3 - Tier 4, a better version of Tier 3.
extern const nn_Filesystem nn_defaultFilesystems[4];
// a basic floppy
extern const nn_Filesystem nn_defaultFloppy;
// a generic tmpfs
extern const nn_Filesystem nn_defaultTmpFS;

nn_Component *nn_createFilesystem(nn_Universe *universe, const char *address, const nn_Filesystem *fs, void *state, nn_FSHandler *handler);

bool nn_mergeFilesystems(nn_Filesystem *merged, const nn_Filesystem *fs, size_t len);

// Drive class

typedef struct nn_Drive {
	// The capacity of the drive.
	// It is in bytes, but it MUST be a multiple of the sector size.
	// The total amount of sectors, as in capacity / sectorSize, must also be divisible by the platter count.
	// If it is not, it is UB.
	size_t capacity;
	// the sector size, typically 512
	size_t sectorSize;
	// the amount of platters the drive has. This contributes to how many "rotations" are needed.
	// A drive with 8 sectors but 1 platter, when seeking from sector 1 to 8, would mean 7 rotations.
	// However, if it has 2 platters, it'd be seen as 1 to 4 being at the same angle as 5 to 8, which
	// would mean only 3 rotations.
	size_t platterCount;
	// how many reads can be issued per tick.
	// Anything that kicks out the current cacheline counts as a read.
	size_t readsPerTick;
	// how many writes can be issued per tick.
	// Writing a sector counts as 1 write.
	// Writing a byte counts as 1 read (may be eaten by cache) and 1 write,
	// you can imagine it as reading the sector, editing the byte,
	// then writing the sector back.
	size_t writesPerTick;
	// Set to 0 for *infinite*, effectively an SSD with infinite lifespan.
	// This would mean there is 0 penalty for seeking (technically unreliastic even for an SSD).
	// This is simply used to compute idle time. It is in literal full rotations per minute.
	// It is aligned to the cache lines.
	size_t rpm;
	// If false, it behaves like a normal OC drive, where the drive can spin backwards to seek.
	// However, this is unrealistic, as doing so may crack the sensitive platter and make the
	// reader lose lift.
	// For fans of physics, this option only allows the seeks to go forwards.
	// This is super punishing at a slow RPM, so it is recommended to bump up
	// the RPM to something like 7200 RPM.
	bool onlySpinForwards;
	// The energy cost of an actual read/write.
	// It is per-byte, so if a read returns 4096 bytes, then this cost is multiplied by 4096.
	double dataEnergyCost;
} nn_Drive;

extern const nn_Drive nn_defaultDrives[4];
extern const nn_Drive nn_floppyDrive;


typedef enum nn_DriveAction {
	// drive gone
	NN_DRIVE_DROP,
	// get current label
	NN_DRIVE_GETLABEL,
	// set or remove current label
	NN_DRIVE_SETLABEL,
	// get the current head position, as a sector
	NN_DRIVE_CURPOS,
	// read a sector
	NN_DRIVE_READSECTOR,
	// write a sector
	NN_DRIVE_WRITESECTOR,
	// write a byte
	NN_DRIVE_WRITEBYTE,
	// is drive read-only
	NN_DRIVE_ISRO,
} nn_DriveAction;

typedef struct nn_DriveRequest {
	nn_Context *ctx;
	nn_Computer *computer;
	void *state;
	const nn_Drive *drv;
	nn_DriveAction action;
	union {
		struct {
			char *buf;
			size_t len;
		} getlabel;
		struct {
			const char *label;
			size_t len;
		} setlabel;
		size_t curpos;
		struct {
			// 1-indexed
			size_t sector;
			char *buf;
		} readSector;
		struct {
			// 1-indexed
			size_t sector;
			const char *buf;
		} writeSector;
		struct {
			// 1-indexed
			size_t byte;
			unsigned char value;
		} writeByte;
		bool readonly;
	};
} nn_DriveRequest;

typedef nn_Exit (nn_DriveHandler)(nn_DriveRequest *request);

nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const nn_Drive *drive, void *state, nn_DriveHandler *handler);

bool nn_mergeDrives(nn_Drive *merged, const nn_Drive *drives, size_t len);

typedef struct nn_NandFlash {
	// capacity of flash
	size_t capacity;
	// sector size
	size_t sectorSize;
	// reads per tick
	size_t readsPerTick;
	// writes per tick
	size_t writesPerTick;
	// The layering, in bits.
	// 1 is SLC, 2 is MLC, 3 is TLC, etc.
	// This number may amplify how quickly the total write count increases.
	size_t cellLevel;
	// the maximum amount of write amplification.
	// Set to 0 to disable amplification RNG.
	// The game will generate, using Context RNG, a real number from [0, 1]
	// then raise it to writeAmplificationExponent,
	// then multiply it by this number, and by the cell level.
	// then clamp it to be at least 1 and at most this maximum.
	unsigned int maxWriteAmplification;
	int writeAmplificationExponent;
	// the maximum amount of writes *per sector.*
	// Set to 0 to make the nandflash eternal.
	size_t maxWriteCount;
	// how much per byte
	double dataEnergyCost;
} nn_NandFlash;

typedef enum nn_FlashAction {
	NN_FLASH_DROP,
	NN_FLASH_GETLABEL,
	NN_FLASH_SETLABEL,
	NN_FLASH_ISRO,
	// read a sector
	NN_FLASH_READSECTOR,
	// write a sector
	// also adds an amount of writes
	NN_FLASH_WRITESECTOR,
	// write a sector
	// also adds an amount of writes
	NN_FLASH_WRITEBYTE,
	// get the amount of writes
	NN_FLASH_GETWRITES,
} nn_FlashAction;

typedef struct nn_FlashRequest {
	nn_Context *ctx;
	nn_Computer *computer;
	void *state;
	const nn_NandFlash *flash;
	nn_FlashAction action;
	union {
		struct {
			char *buf;
			size_t len;
		} getlabel;
		struct {
			const char *buf;
			size_t len;
		} setlabel;
		struct {
			char *buf;
			// 1-indexed
			size_t sec;
		} readsector;
		struct {
			const char *buf;
			// 1-indexed
			size_t sec;
			// how many writes to add
			size_t writesAdded;
		} writesector;
		struct {
			size_t byte;
			char val;
			// how many writes to add
			size_t writesAdded;
		} writebyte;
		// for GETWRITES
		size_t writeCount;
		bool readonly;
	};
} nn_FlashRequest;

typedef nn_Exit (nn_FlashHandler)(nn_FlashRequest *request);

extern const nn_NandFlash nn_defaultSSDs[4];
extern const nn_NandFlash nn_floppySSD;

nn_Component *nn_createFlash(nn_Universe *universe, const char *address, const nn_NandFlash *drive, void *state, nn_FlashHandler *handler);

bool nn_mergeFlash(nn_NandFlash *merged, const nn_NandFlash *flash, size_t len);

// Screen class

typedef enum nn_ScreenFeatures {
	NN_SCRF_NONE = 0,
	// whether it supports mouse input.
	// If it doesn't, it should not emit
	// touch, drag or other mouse events.
	// Walk events should also not be emitted.
	NN_SCRF_MOUSE = 1<<0,
	// Whether precise mode is supported.
	NN_SCRF_PRECISE = 1<<1,
	// Whether touch inverted is supported.
	NN_SCRF_TOUCHINVERTED = 1<<2,
	// it indicates that the palette can be edited.
	NN_SCRF_EDITABLECOLORS = 1<<3,
} nn_ScreenFeatures;

// A struct for the reference screen configurations
// This does not influence the interface at all,
// however it exists as a runtime reference of what
// the conventional screen tiers are.
typedef struct nn_ScreenConfig {
	// maximum width
	int maxWidth;
	// maximum height
	int maxHeight;
	// screen features
	nn_ScreenFeatures features;
	// default palette, if applicable.
	// Can be NULL if there is none,
	// in which case consider memsetting
	// them to #000000.
	int *defaultPalette;
	// the amount of editable palette colors
	int paletteColors;
	// how many editable palette colors there are.
	// It'd always be the first N ones.
	int editableColors;
	// the maximum depth of the screen
	char maxDepth;
	// energy per fully white pixel.
	// Scaled to mathc luminance of each pixel.
	// This is meant to be per Minecraft tick, so 20 times per second.
	double energyPerPixel;
	// minimum brightness. Default brightness is always 100%
	// The value here is meant to be scaled such that 1 means 100% brightness
	double minBrightness;
	// maximum brightness. Note that brightness rendering is emulator-specific
	// The value here is meant to be scaled such that 1 means 100% brightness
	double maxBrightness;
} nn_ScreenConfig;

// OC has 3 tiers, NN adds a 4th one as well.
extern const nn_ScreenConfig nn_defaultScreens[4];


// GPU class
typedef struct nn_GPU {
	// the minimum between these and the screen's
	// are the maximum width/height/depth supported.
	int maxWidth;
	int maxHeight;
	char maxDepth;
	// this is in pixels.
	size_t totalVRAM;
	// amount of times copy can be called before running out of budget.
	int copyPerTick;
	// amount of times fill can be called before running out of budget.
	int fillPerTick;
	// amount of times set can be called before running out of budget.
	int setPerTick;
	// amount of times setForeground can be called before running out of budget.
	int setForegroundPerTick;
	// amount of times setBackground can be called before running out of budget.
	int setBackgroundPerTick;
	// energy per pixel set.
	double energyPerWrite;
	// energy per pixel filled
	double energyPerFill;
	// energy per pixel copied
	double energyPerCopy;
	// energy per space set.
	double energyPerClear;
} nn_GPU;

// 1 GPU tier for every screen.
extern const nn_GPU nn_defaultGPUs[4];

typedef enum nn_GPUAction {
    NN_GPU_DROP,
    NN_GPU_BIND,
    NN_GPU_GETSCREEN,
    NN_GPU_GETBG,
    NN_GPU_SETBG,
    NN_GPU_GETFG,
    NN_GPU_SETFG,
    NN_GPU_GETPALETTE,
    NN_GPU_SETPALETTE,
    NN_GPU_MAXDEPTH,
    NN_GPU_GETDEPTH,
    NN_GPU_SETDEPTH,
    NN_GPU_MAXRES,
    NN_GPU_GETRES,
    NN_GPU_SETRES,
    NN_GPU_GETVIEWPORT,
    NN_GPU_SETVIEWPORT,
    NN_GPU_GET,
    NN_GPU_SET,
    NN_GPU_COPY,
    NN_GPU_FILL,
    NN_GPU_GETACTIVEBUF,
    NN_GPU_SETACTIVEBUF,
    NN_GPU_BUFFERS,
    NN_GPU_ALLOCBUF,
    NN_GPU_FREEBUF,
    NN_GPU_FREEALLBUFS,
    NN_GPU_FREEMEM,
    NN_GPU_GETBUFSIZE,
    NN_GPU_BITBLT,
} nn_GPUAction;

typedef struct nn_GPURequest {
    nn_Context *ctx;
    nn_Computer *computer;
    void *state;
    const nn_GPU *gpu;
    nn_GPUAction action;
    union {
        struct {
            const char *address;
            bool reset;
        } bind;
        // GETSCREEN result
        char screenAddr[NN_MAX_ADDRESS];
        // GET/SET BG/FG
        struct {
            int color;
            bool isPalette;
            int oldColor;
            bool wasPalette;
            int oldPaletteIdx; // -1 if none
        } color;
        // GET/SET PALETTE
        struct {
            int index;
            int color;
            int oldColor;
        } palette;
        // MAXDEPTH / GETDEPTH / SETDEPTH
        struct {
            char depth;
            char oldDepth;
        } depth;
        // MAXRES/GETRES/SETRES/GETVIEWPORT/SETVIEWPORT
        struct {
            int width;
            int height;
        } resolution;
        // GET pixel
        struct {
            int x, y;
            nn_codepoint codepoint;
            int fg, bg;
            int fgIdx, bgIdx; // -1 if not palette
        } get;
        // SET string
        struct {
            int x, y;
            const char *value;
            size_t len;
            bool vertical;
        } set;
        // COPY
        struct {
            int x, y, w, h, tx, ty;
        } copy;
        // FILL
        struct {
            int x, y, w, h;
            nn_codepoint codepoint;
        } fill;
        // GET/SET ACTIVE BUFFER, FREE BUFFER
        struct {
            int index;
        } buffer;
        // ALLOCATE BUFFER
        struct {
            int w, h, index;
        } allocBuf;
        // TOTALMEM / FREEMEM
        size_t memory;
        // GETBUFSIZE
        struct {
            int index, w, h;
        } bufSize;
        // BITBLT
        struct {
            int dst, col, row, w, h;
            int src, fromCol, fromRow;
        } bitblt;
        // BUFFERS / count returned here, indices
        // pushed on stack by handler
        size_t bufCount;
    };
} nn_GPURequest;

typedef nn_Exit (nn_GPUHandler)(nn_GPURequest *req);

nn_Component *nn_createGPU(
    nn_Universe *universe, const char *address,
    const nn_GPU *gpu, void *state,
    nn_GPUHandler *handler);

typedef enum nn_ScreenAction {
    NN_SCREEN_DROP,
    NN_SCREEN_ISON,
    NN_SCREEN_TURNON,
    NN_SCREEN_TURNOFF,
    NN_SCREEN_GETASPECTRATIO,
    NN_SCREEN_GETKEYBOARDS,
    NN_SCREEN_SETPRECISE,
    NN_SCREEN_ISPRECISE,
    NN_SCREEN_SETTOUCHINVERTED,
    NN_SCREEN_ISTOUCHINVERTED,
	// sets the brightness (from 0 to 1)
	NN_SCREEN_SETBRIGHT,
	// get the brightness (from 0 to 1)
	NN_SCREEN_GETBRIGHT,
} nn_ScreenAction;

typedef struct nn_ScreenRequest {
    nn_Context *ctx;
    nn_Computer *computer;
    void *state;
    const nn_ScreenConfig *screen;
    nn_ScreenAction action;
    union {
        // turnOn / turnOff / isOn
        struct {
            bool wasOn;
            bool isOn;
        } power;
        // getAspectRatio
        struct {
            int w, h;
        } aspect;
        // getKeyboards — addresses pushed on stack by
        // handler; count returned here
        size_t kbCount;
        // setPrecise / isPrecise /
        // setTouchModeInverted / isTouchModeInverted
        bool flag;
		double brightness;
    };
} nn_ScreenRequest;

typedef nn_Exit (nn_ScreenHandler)(nn_ScreenRequest *req);

nn_Component *nn_createScreen(
    nn_Universe *universe, const char *address,
    const nn_ScreenConfig *scrconf, void *state,
    nn_ScreenHandler *handler
);

// Computes a CRC32 checksum
unsigned int nn_computeCRC32(const char *data, size_t datalen);

typedef struct nn_DataCard {
	// The buffer size of the data card, limit in both input and output.
	// In OC, this value is 1MiB regardless of tier.
	// As there is no out buffer and you are expected to push strings,
	// the buffer is not pre-allocated. This is intentional, as memory
	// would be wasted and it could be a potential attack vector otherwise.
	size_t limit;

	// The maximum amount of secure random bytes that can be generated.
	// In OC, this was hardcoded to 1024. Here, its configurable.
	// Unlike the normal limit, this does not preallocate the maximum capacity,
	// as the amount of bytes needed is known perfectly.
	size_t maxRandom;

	// for encoding/decoding. OC defaulted to 32
	size_t base64PerTick;
	// for deflate/inflate. OC defaulted to 4
	size_t deflatingPerTick;
	// OC defaulted to 32
	size_t crc32PerTick;
	// OC defaulted to 8
	size_t md5PerTick;
	// OC defaulted to 4
	size_t sha256PerTick;
	// for encrypt/decrypt. OC defaulted to 4
	size_t encryptPerTick;
	// for generateKeyPair. OC defaulted to 1
	size_t genPerTick;
	// for deserializeKey. OC defaulted to 8
	size_t deserializePerTick;
	// OC defaulted to 1
	size_t ecdhPerTick;
	// OC defaulted to 1
	size_t ecdsaPerTick;
	// OC defaults to 4
	size_t randomPerTick;

	// Capabilities
	bool canHash;
	bool canEncrypt;
	bool canECDH;
	bool canCompress;

	// Trivial operation cost (CRC32 and base64 encoding/decoding)
	double trivialCost;
	double trivialCostByte;

	// Simple operation cost (MD5 and encryption/decryption)
	double simpleCost;
	double simpleCostByte;

	double complexCost;
	double complexCostByte;

	// Assymetric operation cost (ECDH, ECDSA). ECDH has no per-byte cost, and ECDSA uses complexCostByte as it relies on SHA256.
	double assymetricCost;
} nn_DataCard;

typedef enum nn_DataCardAction {
	// Data card destroyed
	NN_DATA_DROP,

	// If you want to match the behavior of OC, which you should if you want
	// data compressed or encrypted in OC to work in your emulators, you should
	// aim to match what the JVM, com.google.common.hash.Hashing and javax.crypto do.
	// For more details, see https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/scala/li/cil/oc/server/component/DataCard.scala

	// encoding base64
	NN_DATA_ENCODE64,

	NN_DATA_DECODE64,
	// hashing
	
	// CRC32, little endian
	NN_DATA_CRC32,

	// SHA2-256 hash, optional HMAC key (javax.crypto HmacSHA256)
	NN_DATA_SHA256,

	// MD5 hash, optional HMAC key (javax.crypto HmacMD5)
	NN_DATA_MD5,

	// Deflate. To match OC, make it follow the ZLIB format, which has a 2 byte header.
	NN_DATA_DEFLATE,

	// Inflate. Should support the ZLIB format, as thats what OC uses, and GZIP support is optional.
	NN_DATA_INFLATE,

	// Encrypt data with AES-128. The full algorithm is AES/CBC/PKCS5, as is use PKCS5 for padding, CBC for block sequences, and AES-128 for encrypting blocks.
	// It does also receive a 128-bit AES Initialization Vector, for better security.
	NN_DATA_ENCRYPT,

	// Decrypt data, also using AES/CBC/PKCS5.
	NN_DATA_DECRYPT,

	// Meant to be *secure RNG*, and can generate anywhere between 1 and the data card's maxRandom.
	NN_DATA_RANDOM,

	// Generate an ECDH public/private pair of either 256 or 384 bits each.
	NN_DATA_GENKEYS,

	// validate key, cuz we feel like it
	NN_DATA_VALIDATEKEY,

	// Does an ECDH pass, matching javax.crypto.KeyAgreement.
	// This generates a shared secret as binary data.
	// This is not an AES key as output, the AES key is often computed by either MD5 hashing
	// the shared secret, or SHA256 hashing it and chopping the hash in half.
	NN_DATA_ECDH,

	// ECDSA algorithm, sign data using an ECDH (private) key.
	NN_DATA_ECDSA_SIGN,
	// ECDSA algorithm, verify data using an ECDH (public) key and signature.
	NN_DATA_ECDSA_VERIFY,
} nn_DataCardAction;

// The representation of datacard key userdata
typedef struct nn_DataKey {
	bool isPublic;
	unsigned short bytelen;
	char bytes[];
} nn_DataKey;

typedef struct nn_DataCardRequest {
    nn_Context *ctx;
    nn_Computer *computer;
    void *state;
    const nn_DataCard *dataCard;
	nn_DataCardAction action;
	// TODO: the fields
	union {
		struct {
			const char *data;
			size_t datalen;
			char checksum[4];
		} crc32;
		struct {
			const char *data;
			size_t datalen;
			char checksum[16];
		} md5;
		struct {
			const char *data;
			size_t datalen;
			char checksum[32];
		} sha256;
		// for encrypt/decrypt
		struct {
			const char *data;
			size_t datalen;
			const char *key;
			const char *iv;
		} encrypt;
		struct {
			char *buf;
			size_t buflen;
		} randbuf;
		// for deflate, inflate, encode64 and decode64
		struct {
			const char *data;
			size_t datalen;
		};
		struct {
			const char *buf;
			size_t len;
			bool isPublic;
		} validatekey;
		struct {
			const nn_DataKey *publicKey;
			const nn_DataKey *privateKey;
		} ecdh;
		struct {
			const char *data;
			size_t len;
			const nn_DataKey *privateKey;
		} sign;
		struct {
			const char *data;
			size_t datalen;
			const nn_DataKey *publicKey;
			const char *signature;
			size_t siglen;
			// returns whether the signature actually passed
			bool sigpassed;
		} checksig;
		int genkeybitsize;
	};
} nn_DataCardRequest;

typedef nn_Exit (nn_DataCardHandler)(nn_DataCardRequest *req);

extern nn_DataCard nn_defaultDataCards[3];

nn_Component *nn_createDataCard(nn_Universe *universe, const char *address, const nn_DataCard *dataCard, void *state, nn_DataCardHandler *handler);

typedef struct nn_Modem {
	// maximum range. Set to 0 for non-wireless modems
	size_t maxRange;
	// maximum values in a packet
	size_t maxValues;
	// maximum logical packet size. Note that the encoding is more efficient than the packet size algorithm estimates
	size_t maxPacketSize;
	// the maximum amount of open ports
	size_t maxOpenPorts;
	// whether the modem supports wired connectivity.
	// Support for wireless checks if maxRange > 0.
	bool isWired;
	// base energy cost of 1 network message
	double basePacketCost;
	// energy cost of a full packet, at the maximum logical size
	double fullPacketCost;
	// energy cost per wireless packet strength level
	double costPerStrength;
} nn_Modem;

extern nn_Modem nn_defaultWiredModem;
extern nn_Modem nn_defaultWirelessModems[2];

typedef enum nn_ModemAction {
	// modem dropped
	NN_MODEM_DROP,
	// check whether a port is open
	NN_MODEM_ISOPEN,
	// open a port
	NN_MODEM_OPEN,
	// close a port
	NN_MODEM_CLOSE,
	// get open ports
	NN_MODEM_GETPORTS,
	// send/broadcast a message
	NN_MODEM_SEND,
	// get current modem strength
	NN_MODEM_GETSTRENGTH,
	// set current modem strength
	NN_MODEM_SETSTRENGTH,
	// returns the wake message
	NN_MODEM_GETWAKEMESSAGE,
	// set the wake message
	NN_MODEM_SETWAKEMESSAGE,
} nn_ModemAction;

typedef struct nn_ModemRequest {
    nn_Context *ctx;
    nn_Computer *computer;
    void *state;
    const nn_Modem *modem;
	const char *localAddress;
	nn_ModemAction action;
	union {
		struct {
			size_t port;
			bool opened;
		} isOpen;
		size_t openPort;
		// NN_CLOSEPORTS means close all
		size_t closePort;
		struct {
			// store the port numbers in this buffer
			unsigned short *activePorts;
			// the amount of active ports.
			// the initial value is the capacity of activePorts
			size_t len;
		} getPorts;
		struct {
			const nn_EncodedNetworkContents *contents;
			// NULL for broadcast
			const char *address;
			size_t port;
			// The signal strength it was sent at
			// This is an *OUT* field, it is assumed the handler keeps track of the correct strength
			size_t strengthSent;
		} send;
		// for getStrength, setStrength.
		size_t strength;
		struct {
			char *buf;
			size_t len;
			bool isFuzzy;
		} getWake;
		struct {
			const char *buf;
			size_t len;
			bool isFuzzy;
		} setWake;
	};
} nn_ModemRequest;

typedef nn_Exit (nn_ModemHandler)(nn_ModemRequest *req);

nn_Component *nn_createModem(nn_Universe *universe, const char *address, const nn_Modem *modem, void *state, nn_ModemHandler *handler);

typedef struct nn_Tunnel {
	// maximum values in a packet
	size_t maxValues;
	// maximum logical packet size. Note that the encoding is more efficient than the packet size algorithm estimates
	size_t maxPacketSize;
	// minimum energy cost of 1 transmission
	double basePacketCost;
	// extra energy cost of 1 full transmission
	double fullPacketCost;
} nn_Tunnel;

extern nn_Tunnel nn_defaultTunnel;

typedef enum nn_TunnelAction {
	// tunnel dropped
	NN_TUNNEL_DROP,
	// gets the channel, result should be pushed to the top of the stack
	NN_TUNNEL_GETCHANNEL,
	// send/broadcast a message
	NN_TUNNEL_SEND,
	// returns the wake message
	NN_TUNNEL_GETWAKEMESSAGE,
	// set the wake message
	NN_TUNNEL_SETWAKEMESSAGE,
} nn_TunnelAction;

typedef struct nn_TunnelRequest {
    nn_Context *ctx;
    nn_Computer *computer;
    void *state;
    const nn_Tunnel *tunnel;
	const char *localAddress;
	nn_TunnelAction action;
	union {
		// for send
		const nn_EncodedNetworkContents *toSend;
		struct {
			char *buf;
			size_t len;
			bool isFuzzy;
		} getWake;
		struct {
			const char *buf;
			size_t len;
			bool isFuzzy;
		} setWake;
	};
} nn_TunnelRequest;

typedef nn_Exit (nn_TunnelHandler)(nn_TunnelRequest *req);

nn_Component *nn_createTunnel(nn_Universe *universe, const char *address, const nn_Tunnel *modem, void *state, nn_TunnelHandler *handler);

typedef enum nn_InternetProtocol {
	NN_INET_NONE = 0,
	NN_INET_HTTP = 1<<0,
	NN_INET_TCP = 1<<1,
	NN_INET_UDP = 1<<2,
	NN_INET_WEBSOCKET = 1<<3,
	NN_INET_TLS = 1<<4,

	NN_INET_ALL = NN_INET_HTTP | NN_INET_TCP | NN_INET_UDP | NN_INET_WEBSOCKET | NN_INET_TLS,
} nn_InternetProtocol;

typedef struct nn_InternetCard {
	// bitwise OR multiple of them
	unsigned char protocolsSupported;
	unsigned int maxReadSize;
	// per-byte cost of a write
	double transmissionEnergyCost;
} nn_InternetCard;

extern nn_InternetCard nn_defaultInternetCard;

typedef struct nn_InternetConnection {
	nn_InternetProtocol protocol;
	int port;
	void *state;
	nn_uuid id;
} nn_InternetConnection;

typedef struct nn_HTTPHeader {
	const char *name;
	const char *value;
} nn_HTTPHeader;

typedef enum nn_InternetAction {
	NN_INTERNET_DROP,

	// start a connection, remember to check connection->protocol to know which type of connection to establish
	NN_INTERNET_CONNECT,

	// Ensure a connection is established, error if not.
	// It is assumed this operation will return whether it is currently connected,
	// and should be called repeatedly until it works
	NN_INTERNET_FINISHCONNECT,

	// close a connection
	NN_INTERNET_CLOSE,

	// read from a connection. Set read.buf to NULL to mark EoF. read.len is the capacity, but also set how much was read in it.
	NN_INTERNET_READ,

	// write to a connection. HTTP connections do not have this method
	NN_INTERNET_WRITE,

	// get an HTTP response. Should push the response code, message (ie. OK for 200) and table
	NN_INTERNET_RESPONSE,
} nn_INternetAction;

typedef struct nn_InternetRequest {
    nn_Context *ctx;
    nn_Computer *computer;
    void *state;
    const nn_InternetCard *inet;
	const char *localAddress;
	nn_InternetConnection *connection;
	nn_INternetAction action;
	union {
		// does a socket connection, for any of the supported protocols
		struct {
			// URL of connection
			const char *url;
			// port. Useless for HTTP connections, as they should use 80 for HTTP and 443 for HTTPS
			unsigned short port;
			union {
				struct {
					// HTTP specific
					size_t postdatalen;
					const char *postdata;
					const nn_HTTPHeader *headers;
					size_t headerlen;
				} http;
				struct {
					const char * const * subprotocols;
					size_t subprotocolcount;
				} websockets;
			};
		} connect;
		struct {
			const char *buf;
			size_t len;
		} write;
		struct {
			char *buf;
			size_t len;
		} read;
		bool isConnected;
	};
} nn_InternetRequest;

typedef nn_Exit (nn_InternetHandler)(nn_InternetRequest *req);

nn_Component *nn_createInternet(nn_Universe *universe, const char *address, const nn_InternetCard *inet, void *state, nn_InternetHandler *handler);

// Colors and palettes.
// Do note that the 

// The NeoNucleus 2-bit palette
extern int nn_palette2[4];

// The NeoNucleus 3-bit palette
extern int nn_palette3[8];

// The OC 4-bit palette.
extern int nn_ocpalette4[16];

// The Minecraft 4-bit palette, using dye colors.
extern int nn_mcpalette4[16];

// The OC 8-bit palette.
extern int nn_ocpalette8[256];

// initializes the contents of the palettes.
void nn_initPalettes();

// Returns a number from 0 to 1 representing the perceived luminance.
double nn_colorLuminance(int color);
// Expensive.
// Maps a color to the closest match in a palette.
int nn_mapColor(int color, int *palette, size_t len);
// Expensive.
// Maps a color within a given depth.
// Invalid depths behave identically to 24-bit, in which case the color is left unchanged.
int nn_mapDepth(int color, int depth);

// the name of a depth, if valid.
// If invalid, NULL is returned, thus this can be used to check
// if a depth is valid as well.
// Valid depths are 1, 2, 3, 4, 8, 16 and 24.
const char *nn_depthName(int depth);

#ifdef __cplusplus
}
#endif

#endif
