/*
 *
 * Copyright (C) 2008 coresystems GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/**
 * @file libc/readline.c
 * Simple readline implementation
 */

#include <libpayload.h>

static char *readline_buffer;
static int readline_bufferlen;

/**
 * Read a line from the terminal and return it.
 *
 * This readline implementation is rather simple, but it does more than the
 * original readline() because it allows us to have a pre-filled buffer.
 * To pre-fill the buffer, use the getline() function.
 *
 * @param prompt A prompt to display on the line.
 * @return A pointer to the input string.
 */
char *readline(const char *prompt)
{
	char *buffer;
	int current, ch, nonspace_seen;

	if (!readline_buffer || !readline_bufferlen) {
#define READLINE_BUFFERSIZE	256
		readline_buffer = malloc(READLINE_BUFFERSIZE);
		if (!readline_buffer)
			return NULL;
		readline_bufferlen = READLINE_BUFFERSIZE;
		memset(readline_buffer, 0, readline_bufferlen);
	}

	buffer = readline_buffer;

	/* print prompt */
	if (prompt) {
		current = 0;
		while (prompt[current]) {
			putchar(prompt[current]);
			current++;
		}
	}

	/* print existing buffer, if there is one */
	current = 0;
	while (buffer[current]) {
		putchar(buffer[current]);
		current++;
	}

	while (1) {
		ch = getchar();
		switch (ch) {
		case '\r':
		case '\n':
			/* newline */
			putchar('\n');
			goto out;
		case '\b':
		case '\x7f':
			/* backspace */
			if (current > 0) {
				putchar('\b');
				putchar(' ');
				putchar('\b');
				current--;
			}
			break;
		case 'W' & 0x1f:	/* CTRL-W */
			/* word erase */
			nonspace_seen = 0;
			while (current) {
				if (buffer[current - 1] != ' ')
					nonspace_seen = 1;
				putchar('\b');
				putchar(' ');
				putchar('\b');
				current--;
				if (nonspace_seen && (current < readline_bufferlen - 1)
				    && (current > 0) && (buffer[current - 1] == ' '))
					break;
			}
			break;
		case 'U' & 0x1f:	/* CTRL-U */
			/* line erase */
			while (current) {
				putchar('\b');
				putchar(' ');
				putchar('\b');
				current--;
			}
			current = 0;
			break;
		default:
			/* all other characters */

			/* ignore control characters */
			if (ch < 0x20)
				break;

			/* ignore unprintable characters */
			if (ch >= 0x7f)
				break;

			if (current + 1 < readline_bufferlen) {
				/* print new character */
				putchar(ch);
				/* and add it to the array */
				buffer[current] = ch;
				current++;
			}
		}
	}

out:
	if (current >= readline_bufferlen)
		current = readline_bufferlen - 1;
	buffer[current] = '\0';

	return buffer;
}

/**
 * Read a line from the input and store it in a buffer.
 *
 * This function allows the user to pass a predefined buffer to readline().
 * The buffer may be filled with a default value which will be displayed by
 * readline() and can be edited as normal.
 * The final input string returned by readline() will be returned in
 * the buffer and the function will return the length of the string.
 *
 * @param buffer Pointer to a buffer to store the line in.
 * @param len Length of the buffer.
 * @return The final length of the string.
 */
int getline(char *buffer, int len)
{
	readline_buffer = buffer;
	readline_bufferlen = len;
	readline(NULL);

	return strlen(buffer);
}
