/*      -*- linux-c -*-
 *
 * Copyright (c) 2004 by Intel Corp.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  This
 * file and program are licensed under a BSD style license.  See
 * the Copying file included with the OpenHPI distribution for
 * full licensing terms.
 *
 * Authors:
 *	   Hu Yin     <hu.yin@intel.com>
 * Changes:
 *	11.30.2004 - Kouzmich: porting to HPI-B
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef _WIN32
#include <io.h>
#include <windows.h>
#else
#include <sys/ioctl.h>
#include <unistd.h>
#endif

#if defined(__sun) && defined(__SVR4)
#include <termios.h>
#endif

#include "hpi_cmd.h"


#define MAX_IN_FILES		10
#define MAX_REDIRECTIONS	10


term_def_t	*terms;
int		read_stdin = 1;
int		read_file = 0;
com_enum_t	block_type = MAIN_COM;
FILE		*input_file = (FILE *)NULL;
ret_code_t	shell_error = HPI_SHELL_OK;
int		in_files_count = 0;
FILE		*input_files[MAX_IN_FILES];
int		out_files_count = 0;
int		output_files[MAX_REDIRECTIONS];
char		Title[1024];
int		is_more = 1;

/* local variables */
static char	input_buffer[READ_BUF_SIZE];	// command line buffer
static char	*input_buf_ptr = input_buffer;	// current pointer in input_buffer
static char	cmd_line[LINE_BUF_SIZE];	// current command line
static char	new_cmd_line[LINE_BUF_SIZE];	// new command line
static int	max_term_count = 0;
static int	term_count = 0;
static int	current_term = 0;
static int	window_nrows = 24;
static int	window_ncols = 80;
static int	current_row = 0;


static int set_redirection(char *name, int redir)
{
	char	*flags;
	FILE	*out;

	if (out_files_count >= MAX_REDIRECTIONS) {
		printf("Too many redirections\n");
		return(-1);
	};
	if (redir == 1) flags = "w";
	else flags = "a";
	out = fopen(name, flags);
	if (out == (FILE *)NULL) {
		printf("Can not open/create file: %s\n", name);
		return(-1);
	};
#ifdef _WIN32
	output_files[out_files_count] = _dup(1);
#else
	output_files[out_files_count] = dup(STDOUT_FILENO);
#endif
	fflush(stdout);
#ifdef _WIN32
	_dup2(_fileno(out), 1);
#else
	dup2(fileno(out), STDOUT_FILENO);
#endif
	fclose(out);
	out_files_count++;
	return(0);
}

static void remove_reditection(void)
{
	if (out_files_count == 0) return;
	out_files_count--;
	fflush(stdout);
#ifdef _WIN32
	_dup2(output_files[out_files_count], 1);
	_close(output_files[out_files_count]);
#else
	dup2(output_files[out_files_count], STDOUT_FILENO);
	close(output_files[out_files_count]);
#endif
}

static int delete_input_file(void)
{
	FILE		*f;

	if (in_files_count < 1) return(-1);
	in_files_count--;
	f = input_files[in_files_count];
	fclose(f);
	if (in_files_count == 0) return(-1);
	input_file = input_files[in_files_count - 1];
	return(0);
}

static void new_command(void)
{
	term_count = 0;
	current_row = 0;
	current_term = 0;
	*cmd_line = 0;
}

static void go_to_dialog(void)
{
	if (read_stdin) return;
	if (delete_input_file() == 0) return;
	read_stdin = 1;
	read_file = 0;
	new_command();
}

static void add_term(char *term, term_t type)
{
	term_def_t	*tmp_terms;

	if (term_count >= max_term_count) {
		max_term_count += 5;
		tmp_terms = (term_def_t *)malloc(max_term_count * sizeof(term_def_t));
		memset(tmp_terms, 0, max_term_count * sizeof(term_def_t));
		if (term_count > 0) {
			memcpy(tmp_terms, terms, term_count * sizeof(term_def_t));
			free(terms);
		};
		terms = tmp_terms;
	};
	tmp_terms = terms + term_count;
	tmp_terms->term_type = type;
	tmp_terms->term = term;
	if (debug_flag) printf("add_term: term = %s  type = %d\n", term, type);
	term_count++;
}

static char *find_cmd_end(char *str)
{
	while (*str != 0) {
		if (*str == '\"') {
			str++;
			while ((*str != 0) && (*str != '\"')) str++;
			if (*str == 0) return(str);
		};
		if (*str == ';') return(str + 1);
		str++;
	};
	return(str);
}

static char *get_input_line(char *mes, int new_cmd)
{
	FILE	*f = stdin;
	char	*res;
	int	len;

	if (strlen(input_buf_ptr) > 0) {
		res = input_buf_ptr;
		input_buf_ptr = find_cmd_end(input_buf_ptr);
		return(res);
	};
	input_buf_ptr = input_buffer;
	fflush(stdout);
	memset(input_buffer, 0, READ_BUF_SIZE);
	current_row = 0;
	if (read_stdin) {
		if (mes != (char *)NULL) snprintf(Title, READ_BUF_SIZE, "%s", mes);
		else strcpy(Title, "hpi_shell> ");
		printf("%s", Title);
		res = get_command_line(new_cmd, COMPL_CMD);
		if (res != (char *)NULL) {
			strcpy(input_buffer, res);
			res = input_buffer;
		}
	} else if (read_file) {
		f = input_file;
		res = fgets(input_buffer, READ_BUF_SIZE - 1, f);
	} else {
		printf("Internal error: get_input_line:\n"
			" No input file\n");
		exit(1);
	};
	if (res != (char *)NULL) {
		len = strlen(res);
		if ((len > 0) && (res[len - 1] == '\n'))
			res[len - 1] = 0;
		input_buf_ptr = find_cmd_end(input_buf_ptr);
	};
	return(res);
}

static command_def_t *get_cmd(char *name)
{
	const char	*p;
	command_def_t	*c, *res = (command_def_t *)NULL;
	int		len = strlen(name);
	int		n = 0;

	for (c = commands; (p = c->cmd) != NULL; c++) {
		if ((c->type != MAIN_COM) && (c->type != block_type) &&
			(c->type != UNDEF_COM))
			continue;
		if (strncmp(p, name, len) == 0) {
			if (n == 0) res = c;
			n++;
		};
		if (strcmp(p, name) == 0) {
			return(c);
		}
	};
	if (n == 1) return(res);
	return((command_def_t *)NULL);
}

static void add_to_cmd_line(char *str)
{
	int	len;

	len = strlen(cmd_line);
	if (len == 0)
		strcpy(cmd_line, str);
	else
		snprintf(cmd_line + len, LINE_BUF_SIZE - len, " %s", str);
}

static int check_cmd_for_redirect(void)
{
	int	i, redirect = 0;

	for (i = 0; i < term_count; i++) {
		if (terms[i].term_type == CMD_ERROR_TERM)
			return(HPI_SHELL_SYNTAX_ERROR);
		if (terms[i].term_type == CMD_REDIR_TERM) {
			if (redirect != 0)
				return(HPI_SHELL_SYNTAX_ERROR);
			if (strcmp(">>", terms[i].term) == 0)
				redirect = 2;
			else redirect = 1;
		}
	};
	return(redirect);
}

ret_code_t cmd_parser(char *mes, int as, int new_cmd, int *redirect)
// as = 0  - get command
// as = 1  - may be exit with empty command
// new_cmd = 1 - new command
// new_cmd = 0 - get added items
//     returned redirect value (for new_cmd = 1):
//   0 - no redirect output
//   1 - redirect to the empty file
//   2 - add output to the existent file
{
	char	*cmd, *str, *beg, *tmp;
	term_t	type;
	int	i;
	char	new_line[LINE_BUF_SIZE];

	*redirect = 0;
	for (;;) {
		if (new_cmd) {
			new_command();
			type = CMD_TERM;
		} else type = ITEM_TERM;
		cmd = get_input_line(mes, new_cmd);
		if (cmd == (char *)NULL) {
			go_to_dialog();
			continue;
		};
		strcpy(new_cmd_line, cmd);
		str = new_cmd_line;
		while (isspace(*str)) str++;
		if (strlen(str) == 0) {
			if (as) return HPI_SHELL_OK;
			continue;
		};
		beg = str;
		if (*beg == '#') continue;
		while (*str != 0) {
			if (isspace(*str)) {
				*str++ = 0;
				if (strlen(beg) > 0) {
					add_term(beg, type);
					type = ITEM_TERM;
				};
				while (isspace(*str)) str++;
				add_to_cmd_line(beg);
				beg = str;
				continue;
			};
			if (*str == '\"') {
				str++;
				while ((*str != 0) && (*str != '\"')) str ++;
				if (*str == 0) {
					add_to_cmd_line(beg);
					add_term(beg, type);
					if (read_file)
						add_term(";", CMD_ERROR_TERM);
					break;
				};
				if (*beg == '\"') {
					beg++;
					*str = 0;
					add_term(beg, type);
					type = ITEM_TERM;
					add_to_cmd_line(beg);
					beg = str + 1;
				};
				str++;
				continue;
			};
			if (*str == '>') {
				*str++ = 0;
				if (strlen(beg) > 0) {
					add_to_cmd_line(beg);
					add_term(beg, type);
				};
				if (*str == '>') {
					add_to_cmd_line(">>");
					add_term(">>", CMD_REDIR_TERM);
					str++;
				} else {
					add_to_cmd_line(">");
					add_term(">", CMD_REDIR_TERM);
				};
				type = ITEM_TERM;
				beg = str;
				continue;
			};
			if ((*str == '!') && read_stdin) {
				if (str[1] == '!') {
					i = 2;
					tmp = get_last_history();
				} else {
					i = 1; 
					tmp = get_def_history(str + 1, &i);
				};
				if (tmp == (char *)NULL) {
					str += i;
					continue;
				};
				*str = 0;
				str += i;
				snprintf(new_line, LINE_BUF_SIZE, "%s%s%s", beg, tmp, str);
				str = new_cmd_line + strlen(beg);
				strcpy(new_cmd_line, new_line);
				beg = new_cmd_line;
				continue;
			};
			if (*str == ';') {
				*str++ = 0;
				add_to_cmd_line(beg);
				break;
			};
			str++;
		};
		if (strlen(beg) > 0) {
			add_to_cmd_line(beg);
			add_term(beg, type);
		};
		if (read_file)
			add_term(";", CMD_END_TERM);
		if (read_stdin) set_current_history(cmd_line);
		if (new_cmd == 0)
			return(HPI_SHELL_OK);
		*redirect = check_cmd_for_redirect();
		return(HPI_SHELL_OK);
	}
}

int run_command(void)
//  returned:	-1  - command not found
//		0   - command found and ran
//		1   - no command
//		2   - other block command (do not run command)
//		3   - get new command
{
	term_def_t	*term;
	command_def_t	*c;
	ret_code_t	rv;

	if (debug_flag) printf("run_command:\n");
	term = get_next_term();
	if (term == NULL) return(1);
	if (term->term_type != CMD_TERM) return(1);
	c = get_cmd(term->term);
	if (c == (command_def_t *)NULL) {
		printf("Invalid command:\n%s\n", cmd_line);
		go_to_dialog();
		help(0);
		shell_error = HPI_SHELL_CMD_ERROR;
		return(-1);
	};
	if (c->fun) {
		if (debug_flag)
			printf("run_command: c->type = %d\n", c->type);
		term->term = c->cmd;
		if ((block_type != c->type) && (c->type != UNDEF_COM)) {
			block_type = c->type;
			if (debug_flag) printf("run_command: ret = 2\n");
			return(2);
		};
		rv = c->fun();
		if (rv == HPI_SHELL_PARM_ERROR) {
			printf("Invalid parameters:\n%s\n", cmd_line);
			printf("%s\n", c->help);
			go_to_dialog();
			shell_error = HPI_SHELL_PARM_ERROR;
			return(3);
		} else if (rv == HPI_SHELL_CMD_ERROR) {
			printf("Command failed:\n%s\n", cmd_line);
			go_to_dialog();
			return(3);
		};
		shell_error = rv;
	} else {
		printf("Unimplemented command:\n%s\n", cmd_line);
		go_to_dialog();
		shell_error = HPI_SHELL_CMD_ERROR;
		return(3);
	};
	return(0);
}

int get_new_command(char *mes)
{
	int		redir = 0, i, res;
	char		*name;
	term_def_t	*term;

	if (debug_flag) printf("get_new_command:\n");
	term = get_next_term();
	if ((term == NULL) || (term->term_type != CMD_TERM))
		cmd_parser(mes, 0, 1, &redir);
	else {
		unget_term();
		redir = check_cmd_for_redirect();
	};
	if (redir != 0) {
		for (i = 0; i < term_count; i++) {
			if (terms[i].term_type == CMD_REDIR_TERM)
				break;
		};
		if (i >= term_count - 1) {
			printf("Syntax error:\n%s\n", cmd_line);
			go_to_dialog();
			return(3);
		};
		if (terms[i + 1].term_type != ITEM_TERM) {
			printf("Syntax error:\n%s\n", cmd_line);
			go_to_dialog();
			return(3);
		};
		name = terms[i + 1].term;
		res = set_redirection(name, redir);
		if (res != 0) {
			printf("Command failed:\n%s\n", cmd_line);
			go_to_dialog();
			return(3);
		};
		res = run_command();
		remove_reditection();
		return(res);
	};
	res = run_command();
	return(res);
}

void cmd_shell(void)
{
	*Title = 0;
	init_history();
	help(0);
	domain_proc();
	for (;;) {
		if (debug_flag) printf("cmd_shell:\n");
		shell_error = HPI_SHELL_OK;
		get_new_command((char *)NULL);
		if ((shell_error != HPI_SHELL_OK) && read_file) {
			go_to_dialog();
		}
	}
}

int get_int_param(char *mes, int *val)
{
	int		res, redir;
	char		*str;
	term_def_t	*term;

	term = get_next_term();
	if (term == NULL) {
		cmd_parser(mes, 1, 0, &redir);
		term = get_next_term();
	};
	if (term == NULL) {
		go_to_dialog();
		return(HPI_SHELL_CMD_ERROR);
	};
	str = term->term;
	if (isdigit(*str) || *str == '-' ) {
		res = sscanf(str, "%d", val);
		return(res);
	};
	return(0);
}

int get_hex_int_param(char *mes, int *val)
{
	char		*str, buf[32];
	int		redir;
	term_def_t	*term;

	term = get_next_term();
	if (term == NULL) {
		cmd_parser(mes, 1, 0, &redir);
		term = get_next_term();
	};
	if (term == NULL) {
		go_to_dialog();
		return(HPI_SHELL_CMD_ERROR);
	};
	str = term->term;
	if (strncmp(str, "0x", 2) == 0)
		snprintf(buf, 31, "%s", str);
	else
		snprintf(buf, 31, "0x%s", str);
	*val = strtol(buf, (char **)NULL, 16);
	return(1);
}

int get_hex_string_param(char *mes, char *res, int max_length)
{
	char		*str, buf[32];
	int		redir, i, val;
	term_def_t	*term;

	for (i = 0; i < max_length; i++) {
		term = get_next_term();
		if (term == NULL) {
			cmd_parser(mes, 1, 0, &redir);
			term = get_next_term();
		};
		if (term == NULL) return(i);
		str = term->term;
		if (strncmp(str, "0x", 2) == 0)
			snprintf(buf, 31, "%s", str);
		else
			snprintf(buf, 31, "0x%s", str);
		val = strtol(buf, (char **)NULL, 16);
		res[i] = val;
	};
	return(i);
}

int get_string_param(char *mes, char *val, int len)
{
	term_def_t	*term;
	int		i;

	term = get_next_term();
	if (term == NULL) {
		cmd_parser(mes, 1, 0, &i);
		term = get_next_term();
	};
	if (term == NULL) {
		return(HPI_SHELL_CMD_ERROR);
	};
	memset(val, 0, len);
	strncpy(val, term->term, len);
	for (i = 0; i < len; i++)
		if (val[i] == '\n') val[i] = 0;
	return(0);
}

term_def_t *get_next_term(void)
{
	term_def_t	*res;

	for (;;) {
		if (current_term >= term_count){
			if (debug_flag) printf("get_next_term: term = (NULL)\n");
			return((term_def_t *)NULL);
		};
		res = terms + current_term;
		if (read_file && (res->term_type == CMD_END_TERM))
			return((term_def_t *)NULL);
		current_term++;
		if (res->term_type != EMPTY_TERM) break;
	};
	if (debug_flag) printf("get_next_term: term = %s\n", res->term);
	return(res);
}

ret_code_t unget_term(void)
{
	if (debug_flag) printf("unget_term:\n");
	if (current_term > 0)
		current_term--;
	return (current_term);
}

int add_input_file(char *path)
{
	int		i;
	FILE		*f;

	if (in_files_count >= MAX_IN_FILES) return(-1);
	i = access(path, R_OK | F_OK);
	if (i != 0) {
		printf("Can not access file: %s\n", path);
		return(HPI_SHELL_PARM_ERROR);
	};
	f = fopen(path, "r");
	if (f == (FILE *)NULL) {
		printf("Can not open file: %s\n", path);
		return(HPI_SHELL_PARM_ERROR);
	};
	input_files[in_files_count] = f;
	in_files_count++;
	input_file = f;
	return(0);
}

static void get_term_size( int * rows, int * cols )
{
    *rows = 24;
    *cols = 80;

#ifdef _WIN32
    COORD ws = GetLargestConsoleWindowSize(GetStdHandle(STD_OUTPUT_HANDLE));
    if ((ws.Y != 0) && (ws.X != 0)) {
        *rows = ws.Y;
        *cols = ws.X;
    }
#else
    struct winsize ws;
    int cc = ioctl(termfd, TIOCGWINSZ, &ws);

    if ((cc == 0) && (ws.ws_row != 0) && (ws.ws_col != 0)) {
        *rows = ws.ws_row;
        *cols = ws.ws_col;
    }
#endif
}

Pr_ret_t ui_print(char *Str)
{
	int		i, len, c, add_nl = 1;
	char		*tmp, *s;
	char		buf[LINE_BUF_SIZE];
        const char green[] = "\033[0;40;32m";
//        const char yellow[] = "\033[0;40;33m";
        const char reset[] = "\033[0m";

	get_term_size(&window_nrows, &window_ncols);
	strncpy(buf, Str, LINE_BUF_SIZE);
	s = tmp = buf;
	for (i = 0, len = 0; *tmp != 0; i++, tmp++) {
		c = *tmp;
		switch (c) {
		case '\b': continue;
		case '\n':
			*tmp = 0;
			printf("%s\n", s);
			current_row += add_nl;
			if ((current_row >= (window_nrows - 1)) && is_more) {
				printf("%s - more - %s", green, reset);
				i = getchar();
				printf("\r          \r");
				current_row = 0;
				if (i == 'q') return(HPI_UI_END);
			};
			s = tmp + 1;
			len = 0;
			add_nl = 1;
			break;
		case '\r': len = 0; continue;
		case '\t': len += 8; len = (len / 8) * 8; break;
		default  : len++; break;
		};
		if (len >= window_ncols) {
			add_nl++;
			len = 0;
		}
	};
	if (*s != 0) printf("%s", s);
	return(HPI_UI_OK);
}
