//
//  osxtads_input_key.m
//  XTads
//
//  Created by Rune Berg on 12/07/2020.
//  Copyright © 2020 Rune Berg. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "osxtads_support.h"


int os_getc_internal();

/*
 *   Read a string of input.  Fills in the buffer with a null-terminated
 *   string containing a line of text read from the standard input.  The
 *   returned string should NOT contain a trailing newline sequence.  On
 *   success, returns 'buf'; on failure, including end of file, returns a
 *   null pointer.
 */
unsigned char *os_gets(unsigned char *buf, size_t bufl)
{
	XTOSIFC_DEF_SELNAME(@"os_gets");
	XTOSIFC_TRACE_1(@"bufl=%lu", bufl);
	
	NSString *command = [getGameRunner() waitForCommand];
	
	if (command == nil) {
		return nil;
	}

	const char* commandCStr = [getGameRunner() makeCStringInteractive:command];
	strncpy((char *)buf, commandCStr, bufl);
	buf[bufl - 1] = 0;
	
	return buf;
}

/*
 The new functions os_gets_timeout() and os_gets_cancel() provides an
 event-oriented interface for reading a command line from the keyboard.
 
 This function is similar to the regular os_gets(), but has a timeout
 feature that allows the keyboard read to be interrupted after a
 specified interval has elapsed, and then possibly resumed later.
 Timed keyboard input is optional; if it can't be implemented on a
 platform, simply provide an implementation for os_gets_timeout() that
 returns OS_EVT_NOTIMEOUT when a timeout is provided, and an empty
 implementation for os_gets_cancel()..
 
 Note that even if timed input
 is not allowed, os_gets_timeout() should minimally call the plain
 os_gets() when the caller does not specify a timeout, and only return
 the OS_EVT_NOTIMEOUT error when a timeout is actually requested by the
 caller.
 */

int os_gets_timeout(unsigned char *buf, size_t bufl,
					unsigned long timeout, int use_timeout)
{
	XTOSIFC_DEF_SELNAME(@"os_gets_timeout");
	XTOSIFC_TRACE_2(@"timeout=\"%lu\" use_timeout=\"%d\"", timeout, use_timeout);
	
	if (! use_timeout) {
		//unsigned char *os_gets(unsigned char *buf, size_t bufl)
		unsigned char* s = os_gets(buf, bufl);
		if (s != nil) {
			XTOSIFC_TRACE_0(@"-> OS_EVT_LINE");
			return OS_EVT_LINE;
		} else {
			XTOSIFC_TRACE_0(@"-> OS_EVT_EOF");
			return OS_EVT_EOF;
		}
	} else {
		//TODO? consider implementing
		XTOSIFC_TRACE_0(@"-> OS_EVT_NOTIMEOUT");
		return OS_EVT_NOTIMEOUT;
	}
}

void os_gets_cancel(int reset)
{
	//TODO? consider implementing
	//LOG_CALL_TO_UNIMPLEMENTED_FUNCTION(@"os_gets_cancel");
}

/*
 *   wait for a character to become available from the keyboard
 */
void os_waitc()
{
	XTOSIFC_TRACE_ENTRY(@"os_waitc");
	
	[getGameRunner() showMorePromptAndWaitForKeyPressed];
}

/*
 *   show the MORE prompt and wait for the user to respond
 */
void os_more_prompt()
{
	XTOSIFC_TRACE_ENTRY(@"os_more_prompt");
	
	[getGameRunner() showMorePromptAndWaitForKeyPressed];
}

/*
 *   Read a character from the keyboard, following the same protocol as
 *   os_getc() for CMD_xxx codes (i.e., when an extended keystroke is
 *   encountered, os_getc_raw() returns zero, then returns the CMD_xxx code
 *   on the subsequent call).
 *
 *   This function differs from os_getc() in that this function returns the
 *   low-level, untranslated key code whenever possible.  This means that,
 *   when a functional interpretation of a key and the raw key-cap
 *   interpretation both exist as CMD_xxx codes, this function returns the
 *   key-cap interpretation.  For the Unix Ctrl-E example in the comments
 *   describing os_getc() above, this function should return 5 (the ASCII
 *   code for Ctrl-E), because the ASCII control character interpretation is
 *   the low-level key code.
 *
 *   This function should return all control keys using their ASCII control
 *   codes, whenever possible.  Similarly, this function should return ASCII
 *   27 for the Escape key, if possible.
 *
 *   For keys for which there is no portable ASCII representation, this
 *   should return the CMD_xxx sequence.  So, this function acts exactly the
 *   same as os_getc() for arrow keys, function keys, and other special keys
 *   that have no ASCII representation.  This function returns a
 *   non-translated version ONLY when an ASCII representation exists - in
 *   practice, this means that this function and os_getc() vary only for CTRL
 *   keys and Escape.
 */
/* QTADS:
 *   Read a character from the keyboard in raw mode.  Because the OS code
 *   handles command line editing itself, we presume that os_getc() is
 *   already returning the low-level raw keystroke information, so we'll
 *   just return that same information.
 */
/* TADS2 portnote:
 The purpose of os_getc_raw() is to allow lower-level access to keystroke
 information, particularly for the inputkey() TADS function, while still
 preserving the port-specific functional mapping that os_getc() affords.
 */
// inkey.t test game calls this
int os_getc_raw()
{
	XTOSIFC_TRACE_ENTRY(@"os_getc_raw");
	
	int res = os_getc_internal();
	
	//XT_TRACE_1(@"-> %d", res);
	
	return res;
}

/*
 *   Read a character from the keyboard.  For extended keystrokes, this
 *   function returns zero, and then returns the CMD_xxx code for the
 *   extended keystroke on the next call.  For example, if the user presses
 *   the up-arrow key, the first call to os_getc() should return 0, and the
 *   next call should return CMD_UP.  Refer to the CMD_xxx codes below.
 *
 *   os_getc() should return a high-level, translated command code for
 *   command editing.  This means that, where a functional interpretation of
 *   a key and the raw key-cap interpretation both exist as CMD_xxx codes,
 *   the functional interpretation should be returned.  For example, on Unix,
 *   Ctrl-E is conventionally used in command editing to move to the end of
 *   the line, following Emacs key bindings.  Hence, os_getc() should return
 *   CMD_END for this keystroke, rather than a single 0x05 character (ASCII
 *   Ctrl-E), because CMD_END is the high-level command code for the
 *   operation.
 *
 *   The translation ability of this function allows for system-dependent key
 *   mappings to functional meanings.
 */
int os_getc() // berrosts.gam calls this
{
	XTOSIFC_TRACE_ENTRY(@"os_getc");
	
	int res = os_getc_internal();
	
	XTOSIFC_TRACE_1(@"-> %d", res);
	
	return res;
}

int os_getc_internal()
{
	XTOSIFC_TRACE_ENTRY(@"os_getc_internal");
	
	NSUInteger keyPressed = [getGameRunner() waitForAnyKeyPressed];
	
	if (keyPressed == NSUIntegerMax) {
		// couldn't get a key
		static BOOL sendEofCmd = NO;
		int res = (sendEofCmd ? CMD_EOF : 0);
		sendEofCmd = (! sendEofCmd);
		XTOSIFC_TRACE_1(@"-> (CMD_EOF/0) %d", res);
		return res;
	}
	
	XTOSIFC_TRACE_1(@"-> %lu", keyPressed);
	
	return (int)keyPressed;
}

/*
 *   Get an input event.  The event types are shown above.  If use_timeout is
 *   false, this routine should simply wait until one of the events it
 *   recognizes occurs, then return the appropriate information on the event.
 *   If use_timeout is true, this routine should return OS_EVT_TIMEOUT after
 *   the given number of milliseconds elapses if no event occurs first.
 *
 *   This function is not obligated to obey the timeout.  If a timeout is
 *   specified and it is not possible to obey the timeout, the function
 *   should simply return OS_EVT_NOTIMEOUT.  The trivial implementation thus
 *   checks for a timeout, returns an error if specified, and otherwise
 *   simply waits for the user to press a key.
 *
 *   A timeout value of 0 does *not* mean that there's no timeout (i.e., it
 *   doesn't mean we should wait indefinitely) - that's specified by passing
 *   FALSE for use_timeout.  A zero timeout also doesn't meant that the
 *   function should unconditionally return OS_EVT_TIMEOUT.  Instead, a zero
 *   timeout specifically means that IF an event is available IMMEDIATELY,
 *   without blocking the thread, we should return that event; otherwise we
 *   should immediately return a timeout event.
 */
int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout, os_event_info_t *info)
{
	XTOSIFC_TRACE_ENTRY(@"os_get_event");
	
	if (use_timeout) {
		//TODO revisit when supporting timed i/o
		//XT_TRACE_0(@"-> OS_EVT_NOTIMEOUT");
		//return OS_EVT_NOTIMEOUT;
	}

	XTGameInputEvent *event = [XTGameInputEvent new];
	[getGameRunner() waitForEvent:event];
	
	int eventType = (int)event.type;
	switch (eventType) {
		case OS_EVT_KEY:
			info->key[0] = (int)event.key0;
			info->key[1] = (int)event.key1;
			if (info->key[0] == NSUIntegerMax) {
				eventType = OS_EVT_EOF;
			}
			break;
		case OS_EVT_HREF: {
			if (event.href != nil) {
				const char* ctsring = [event.href UTF8String];
				strncpy(info->href, ctsring, sizeof(info->href) - 1);
			} else {
				strncpy(info->href, "", 2);
			}
			break;
		}
		case OS_EVT_NONE:
		case OS_EVT_EOF:
			// do nothing
			break;
		default:
			XTOSIFC_WARN_1(@"unknown/unsupported event type %d", eventType);
			break;
	}
	
	XTOSIFC_TRACE_1(@"-> %d", eventType);
	return eventType;
}

/*
 *   Check for user break ("control-C", etc) - returns true if a break is
 *   pending, false if not.  If this returns true, it should "consume" the
 *   pending break (probably by simply clearing the OS code's internal
 *   break-pending flag).
 */
int os_break()
{
	// Not relevant on modern OSs
	
	return 0;
}
