void dc_init(void)
{
	if (debug_inited) {
		return;
	}

	debug_inited = TRUE;

	// Init window settings
	dc_font = FONT1;
	row_height = ((Current_font->h) * 3) / 2;	// Row/Line height, in pixels
	col_width = Current_font->w;			// Col/Character width, in pixels
	dc_scroll_x = 0;
	dc_scroll_y = 0;
	DCOLS = (gr_screen.max_w / col_width) - 1;	// Subtract as needed. Windowed mode has some quirks with the resolution
	DROWS = (gr_screen.max_h / row_height) - 2;
	DBCOLS = DCOLS;
	DBROWS = 2 * DROWS;

	// Init History
	dc_history.clear();
	dc_history.push_back("");
	last_oldcommand = dc_history.begin();

	// Init buffers
	dc_buffer.clear();
	dc_buffer.push_back("");
	
	dc_command_buf.reserve(MAX_CLI_LEN);
	dc_command_buf.clear();

	sprintf(dc_title, "FreeSpace Open v%i.%i.%i", FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
	dc_printf("Debug console started.\n" );
}
void dc_draw_window(bool show_prompt)
{
	uint cmd_lines;                 // Number of lines for the command string
	uint buffer_lines;              // Number of lines from the buffer to draw
	uint i;                         // The current row we're drawing
	uint j;                         // The current row of the command string we're drawing
	SCP_string out_str;             // The command string + prompt character
	SCP_string::iterator str_it;    // Iterator to out_str

	out_str = dc_prompt + dc_command_buf;
	cmd_lines = (out_str.size() / DCOLS) + 1;
	if (show_prompt) {
		buffer_lines = DROWS - cmd_lines;
	} else {
		buffer_lines = DROWS;
	}

	// Ensure the window is not bigger than the buffer
	CLAMP(DROWS, DROWS_MIN, DBROWS);
	CLAMP(DCOLS, DCOLS_MIN, DBCOLS);

	// Ensure we don't scroll too far
	dc_scroll_x = MIN(dc_scroll_x, (DBCOLS - DCOLS));
	if (dc_buffer.size() >= buffer_lines) {
		dc_scroll_y = MIN(dc_scroll_y, (dc_buffer.size() - buffer_lines));
	} else {
		dc_scroll_y = 0;	// Disallow vscroll until the buffer is larger than the window
	}

	// Draw the buffer strings
	for (i = 0; i < buffer_lines; ++i) {
		if ((i + dc_scroll_y) < dc_buffer.size()) {
			gr_string(0, ((i * row_height) + row_height), dc_buffer[i + dc_scroll_y].substr(dc_scroll_x).c_str(), GR_RESIZE_NONE);
		}
	}

	// Draw the command string w/ padding only if the prompt is active.
	if (show_prompt) {
		i += 1;		// 1 line between the output and the input text
		j = 0;
		gr_set_color_fast(&Color_bright);
		for (str_it = out_str.begin(); str_it < out_str.end(); ++str_it) {
			if (j == (DCOLS - 1)) {
				// Insert a newline char at every place the string needs to return the 'carriage'
				str_it = out_str.insert(str_it, '\n');
				j = 0;
			} else {
				++j;
			}
		}
		gr_string(0, ((i * row_height) + row_height), out_str.c_str(), GR_RESIZE_NONE);

		dc_draw_cursor(out_str, 0, ((i * row_height)));
		gr_set_color_fast(&Color_normal);
	}
}
void dc_init(void)
{
	if (debug_inited) {
		return;
	}

	debug_inited = TRUE;

	// Init window settings
	dc_font = font::FONT1;
	row_height = ((fl2i(font::get_current_font()->getHeight())) * 3) / 2;	// Row/Line height, in pixels

	// This assumes that FONT1 is monospaced!
	gr_get_string_size(&col_width, nullptr, " "); // Col/Character width, in pixels

	dc_scroll_x = 0;
	dc_scroll_y = 0;
	DCOLS = (gr_screen.center_w / col_width) - 1;	// Subtract as needed. Windowed mode has some quirks with the resolution
	DROWS = (gr_screen.center_h / row_height) - 2;
	DBCOLS = DCOLS;
	DBROWS = 2 * DROWS;

	// Init History
	dc_history.clear();
	dc_history.push_back("");
	last_oldcommand = dc_history.begin();

	// Init buffers
	dc_buffer.clear();
	dc_buffer.push_back("");
	
	dc_command_buf.reserve(MAX_CLI_LEN);
	dc_command_buf.clear();

	sprintf(dc_title, "FreeSpace Open v%i.%i.%i", FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
	dc_printf("Debug console started.\n" );
}
void debug_console(void (*_func)(void))
{
	int done = 0;

	while( key_inkey() ) {
		os_poll();
	}

	if ( !debug_inited ) {
		dc_init();
	}

	dc_draw(TRUE);

	while (!done) {
		// poll the os
		os_poll();

		int k = key_inkey();
		switch( k ) {

		case KEY_SHIFTED+KEY_ENTER:
		case KEY_ESC:
			done = TRUE;
			break;

		case KEY_BACKSP:
			if (!dc_command_buf.empty()) {
				dc_command_buf.erase(dc_command_buf.size() - 1);
			}
			break;

		case KEY_F3:
		case KEY_UP:
			if (last_oldcommand < (dc_history.end() - 1)) {
				++last_oldcommand;
			}

			dc_command_buf = *last_oldcommand;
			break;

		case KEY_DOWN:
			if (last_oldcommand > dc_history.begin()) {
				--last_oldcommand;
			}

			dc_command_buf = *last_oldcommand;
			break;

		case KEY_PAGEUP:
			if (dc_scroll_y > 1) {
				dc_scroll_y--;
			}
			break;

		case KEY_PAGEDOWN:
			if (dc_scroll_y < (DBROWS - DROWS)) {
				dc_scroll_y++;
			} else {
				dc_scroll_y = (DBROWS - DROWS);
			}
			break;

		case KEY_ENTER:
			dc_scroll_y = (DBROWS - DROWS);			// Set the scroll to look at the bottom
			last_oldcommand = dc_history.begin();	// Reset the last oldcommand
			lastline = 0;	// Reset the line counter

			// Clear the command line on the window, but don't print the prompt until the command has processed
			// Stuff a copy of the command line onto the history
			// Search for the command
				// If not found:
				//   abort,
				//   dc_printf("Error: Invalid or Missing command %s", cmd.c_str()), and
				//   dc_printf(dc_prompt) when ready for input
			// Call the function for that command, and strip the cmd token from the command line string
			if (dc_command_buf.empty()) {
				dc_printf("No command given.\n");
				break;
			} // Else, continue to process the cmd_line

			// z64: Thread Note: Maybe lock a mutex here to allow a previous DCF to finish/abort before starting a new one
			// z64: We'll just assume we won't be here unless a command has finished...
			dc_history.push_front(dc_command_buf);	// Push the command onto the history queue
			last_oldcommand = dc_history.begin();	// Reset oldcommand

			while (dc_history.size() > DCMDS) {
				dc_history.pop_back();			// Keep the commands less than or equal to DCMDS
			}

			dc_command_str = dc_command_buf;	// Xfer to the command string for processing
			dc_command_buf.resize(0);			// Nullify the buffer
			dc_printf("%s%s\n", dc_prompt, dc_command_str.c_str());	// Print the command w/ prompt.
			dc_draw(FALSE);					// Redraw the console without the command line.
			dc_do_command(&dc_command_str);	// Try to do the command
			break;

		default:
			// Not any of the control key codes, so it's probably a letter or number.
			ubyte c = (ubyte)key_to_ascii(k);
			if ((c != 255) && (dc_command_buf.size() < MAX_CLI_LEN)) {
				dc_command_buf.push_back(c);
			}
		}

		// Do the passed function
		if ( _func ) {
			_func();
		}

		// All done, and ready for new entry
		dc_draw(TRUE);
	}

	while( key_inkey() ) {
		os_poll();
	}
}
void dc_putc(char c)
{
	SCP_string* line_str = &(dc_buffer.back());
	SCP_string temp_str;
	int i;
	int w;

	if (c == ' ') {
		/**
		 * Push c onto the temp_str and get its gr_string width
		 *
		 * If we run out of room on the line, or 
		 * If we run out of room on the screen, change c to a '\n' and let subsequent block handle it,
		 * Else, push the space onto the line and bail
		 */
		temp_str = *line_str;
		temp_str.push_back(c);
		gr_get_string_size(&w, NULL, temp_str.c_str());

		if ((temp_str.size() >= DBCOLS) || (w > gr_screen.max_w)) {
			c = '\n';
		
		} else {
			lastwhite = temp_str.size();
			*line_str = temp_str;
			return;
		}
	}

	if (c == '\t') {
		/**
		 * Calculate how many spaces to put in to align tabs,
		 * Append temp_str with the spaces and get its gr_string width
		 *
		 * If we run out of room on the line, or
		 * If we run out of room on the screen, change c to a '\n' and let subsequent block handle it,
		 * Else, copy temp_str onto the line, update the lastwhite index, and bail
		 */
		i = DTABS - (line_str->size() % DTABS);
		temp_str = *line_str;
		temp_str.append(i, ' ');
		gr_get_string_size(&w, NULL, temp_str.c_str());

		if ((temp_str.size() >= DBCOLS) || (w > gr_screen.max_w)) {
			c = '\n';

		} else {
			lastwhite = temp_str.size();
			*line_str = temp_str;
			return;
		}
	}

	if (c == '\n') {
		/**
		 * Trash whatever char happens to be past (DBCOLS - 1),
		 * Push a blank line onto the dc_buffer from the bottom,
		 * Increment the scroller, if needed,
		 * Trash the topmost line(s) in the buffer,
		 * Reset the lastwhite index,
		 * Increment the lastline counter, and finally
		 * bail
		 */
		if (line_str->size() > DBCOLS) {
			line_str->resize(DBCOLS);
		}
		dc_buffer.push_back("");

		if ((dc_buffer.size() > DROWS) && (dc_scroll_y < SCROLL_Y_MAX)) {
			dc_scroll_y++;
		}

		while (dc_buffer.size() > DBROWS) {
			dc_buffer.pop_front();
		}

		lastwhite = 0;
		lastline++;
		return;
	}

	// By this point, c is probably a writable character
	temp_str = *line_str;
	temp_str.push_back(c);
	gr_get_string_size(&w, NULL, temp_str.c_str());

	if ((temp_str.size() >= DBCOLS) || (w > gr_screen.max_w)) {
		/**
		 * Word wrapping
		 * Save the word, clear the line of the word, push new line with the word on it
		 * Update scroll_y, if needed,
		 * Pop off old lines, and finally
		 * Push new character onto the new line
		 */
		temp_str = line_str->substr(lastwhite);
		line_str->resize(lastwhite);
		dc_buffer.push_back(temp_str);
		line_str = &dc_buffer.back();

		if ((dc_buffer.size() > DROWS) && (dc_scroll_y < SCROLL_Y_MAX)) {
			dc_scroll_y++;
		}

		while (dc_buffer.size() > DBROWS) {
			dc_buffer.pop_front();
		}

		lastwhite = 0;
		lastline++;
		line_str->push_back(c);
		return;
	}

	// Else, just push the char onto the line
	line_str->push_back(c);
}