/* -*- indent-tabs-mode:t; c-basic-offset:8 -*-
 *
 * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
 * Copyright (C) 2008 NXP B.V.
 * Released under the terms of the GNU GPL v2.0.
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>
#include <ctype.h>

#define LKC_DIRECT_LINK
#include "lkc.h"

static void conf(struct menu *menu);
static void check_conf(struct menu *menu);

enum {
	ask_all,
	ask_new,
	ask_silent,
	set_default,
	set_yes,
	set_mod,
	set_no,
	set_random
} input_mode = ask_all;
char *defconfig_file;
char *pre_question_reminder = NULL;

static int indent = 1;
static int valid_stdin = 1;
static int conf_cnt;
static char line[128];
static struct menu *rootEntry;

static char nohelp_text[] = N_("Sorry, no help available for this option yet.\n");

static void strip(char *str)
{
	char *p = str;
	int l;

	while ((isspace(*p)))
		p++;
	l = strlen(p);
	if (p != str)
		memmove(str, p, l + 1);
	if (!l)
		return;
	p = str + l - 1;
	while ((isspace(*p)))
		*p-- = 0;
}

static void check_stdin(void)
{
	if (!valid_stdin && input_mode == ask_silent) {
		fprintf(stderr, _("aborted!\n\n"));
		fprintf(stderr, _("Console input/output is redirected. "));
		fprintf(stderr, _("Run 'make oldconfig' to update configuration.\n\n"));
		exit(1);
	}
}

static int conf_askvalue(struct symbol *sym, const char *def)
{
	enum symbol_type type = sym_get_type(sym);
	tristate val;

	if (!sym_has_value(sym))
		printf("(NEW) ");

	line[0] = '\n';
	line[1] = 0;

	if (!sym_is_changable(sym)) {
		printf("%s\n", def);
		line[0] = '\n';
		line[1] = 0;
		return 0;
	}

	switch (input_mode) {
	case set_no:
	case set_mod:
	case set_yes:
	case set_random:
		if (sym_has_value(sym)) {
			printf("%s\n", def);
			return 0;
		}
		break;
	case ask_new:
	case ask_silent:
		if (sym_has_value(sym)) {
			printf("%s\n", def);
			return 0;
		}
		check_stdin();
	case ask_all:
		if (pre_question_reminder != NULL) printf("\nReminder: %s\n", pre_question_reminder);
		fflush(stdout);
		fgets(line, 128, stdin);
		return 1;
	case set_default:
		printf("%s\n", def);
		return 1;
	default:
		break;
	}

	switch (type) {
	case S_INT:
	case S_HEX:
	case S_STRING:
		printf("%s\n", def);
		return 1;
	default:
		;
	}
	switch (input_mode) {
	case set_yes:
		if (sym_tristate_within_range(sym, yes)) {
			line[0] = 'y';
			line[1] = '\n';
			line[2] = 0;
			break;
		}
	case set_mod:
		if (type == S_TRISTATE) {
			if (sym_tristate_within_range(sym, mod)) {
				line[0] = 'm';
				line[1] = '\n';
				line[2] = 0;
				break;
			}
		} else {
			if (sym_tristate_within_range(sym, yes)) {
				line[0] = 'y';
				line[1] = '\n';
				line[2] = 0;
				break;
			}
		}
	case set_no:
		if (sym_tristate_within_range(sym, no)) {
			line[0] = 'n';
			line[1] = '\n';
			line[2] = 0;
			break;
		}
	case set_random:
		do {
			val = (tristate)(random() % 3);
		} while (!sym_tristate_within_range(sym, val));
		switch (val) {
		case no: line[0] = 'n'; break;
		case mod: line[0] = 'm'; break;
		case yes: line[0] = 'y'; break;
		}
		line[1] = '\n';
		line[2] = 0;
		break;
	default:
		break;
	}
	printf("%s", line);
	return 1;
}

int conf_string(struct menu *menu)
{
	struct symbol *sym = menu->sym;
	const char *def, *help;

	while (1) {
		printf("%*s%s ", indent - 1, "", menu->prompt->text);
		printf("(%s) ", sym->name);
		def = sym_get_string_value(sym);
		if (sym_get_string_value(sym))
			printf("[%s] ", def);
		if (!conf_askvalue(sym, def))
			return 0;
		switch (line[0]) {
		case '\n':
			break;
		case '?':
			/* print help */
			if (line[1] == '\n') {
				help = nohelp_text;
				if (menu->sym->help)
					help = menu->sym->help;
				printf("\n%s\n", menu->sym->help);
				def = NULL;
				break;
			}
		default:
			line[strlen(line)-1] = 0;
			def = line;
		}
		if (def && sym_set_string_value(sym, def))
			return 0;
	}
}

static int conf_sym(struct menu *menu)
{
	struct symbol *sym = menu->sym;
	int type;
	tristate oldval, newval;
	const char *help;

	while (1) {
		printf("%*s%s ", indent - 1, "", menu->prompt->text);
		if (sym->name)
			printf("(%s) ", sym->name);
		type = sym_get_type(sym);
		putchar('[');
		oldval = sym_get_tristate_value(sym);
		switch (oldval) {
		case no:
			putchar('N');
			break;
		case mod:
			putchar('M');
			break;
		case yes:
			putchar('Y');
			break;
		}
		if (oldval != no && sym_tristate_within_range(sym, no))
			printf("/n");
		if (oldval != mod && sym_tristate_within_range(sym, mod))
			printf("/m");
		if (oldval != yes && sym_tristate_within_range(sym, yes))
			printf("/y");
		if (sym->help)
			printf("/?");
		printf("] ");
		if (!conf_askvalue(sym, sym_get_string_value(sym)))
			return 0;
		strip(line);

		switch (line[0]) {
		case 'n':
		case 'N':
			newval = no;
			if (!line[1] || !strcmp(&line[1], "o"))
				break;
			continue;
		case 'm':
		case 'M':
			newval = mod;
			if (!line[1])
				break;
			continue;
		case 'y':
		case 'Y':
			newval = yes;
			if (!line[1] || !strcmp(&line[1], "es"))
				break;
			continue;
		case 0:
			newval = oldval;
			break;
		case '?':
			goto help;
		default:
			continue;
		}
		if (sym_set_tristate_value(sym, newval))
			return 0;
help:
		help = nohelp_text;
		if (sym->help)
			help = sym->help;
		printf("\n%s\n", help);
	}
}

static int conf_choice(struct menu *menu)
{
	struct symbol *sym, *def_sym;
	struct menu *child;
	int type;
	bool is_new;

    DBG_ENTER("menu [%s]", DBG_MENU_NAME(menu));

	sym = menu->sym;
	type = sym_get_type(sym);
	is_new = !sym_has_value(sym);
    DBG_PRINT("menu [%s] %s new", DBG_MENU_NAME(menu), is_new ? "IS" : "is not");
	if (sym_is_changable(sym)) {
        DBG_PRINT("menu [%s] is changeble", DBG_MENU_NAME(menu));
		conf_sym(menu);
		sym_calc_value(sym);
		switch (sym_get_tristate_value(sym)) {
		case no:
            DBG_LEAVE("menu [%s] => 1", DBG_MENU_NAME(menu));
			return 1;
		case mod:
            DBG_LEAVE("menu [%s] => 0", DBG_MENU_NAME(menu));
			return 0;
		case yes:
            DBG_PRINT("menu [%s] is yes, so doing more...", DBG_MENU_NAME(menu));
			break;
		}
	} else {
        DBG_PRINT("menu [%s] is not changeble", DBG_MENU_NAME(menu));
		switch (sym_get_tristate_value(sym)) {
		case no:
            DBG_LEAVE("menu [%s] => 1", DBG_MENU_NAME(menu));
			return 1;
		case mod:
			printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu));
            DBG_LEAVE("menu [%s] => 0", DBG_MENU_NAME(menu));
			return 0;
		case yes:
            DBG_PRINT("menu [%s] is yes, so doing more...", DBG_MENU_NAME(menu));
			break;
		}
	}

	while (1) {
		int cnt, def;

		printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu));
		def_sym = sym_get_choice_value(sym);
		cnt = def = 0;
		line[0] = 0;
		for (child = menu->list; child; child = child->next) {
			if (!menu_is_visible(child))
				continue;
			if (!child->sym) {
				printf("%*c %s\n", indent, '*', menu_get_prompt(child));
				continue;
			}
			cnt++;
			if (child->sym == def_sym) {
				def = cnt;
				printf("%*c", indent, '>');
			} else
				printf("%*c", indent, ' ');
			printf(" %d. %s", cnt, menu_get_prompt(child));
			if (child->sym->name)
				printf(" (%s)", child->sym->name);
			if (!sym_has_value(child->sym))
				printf(" (NEW)");
			printf("\n");
		}
		printf("%*schoice", indent - 1, "");
		if (cnt == 1) {
			printf("[1]: 1\n");
			goto conf_childs;
		}
		printf("[1-%d", cnt);
		if (sym->help)
			printf("?");
		printf("]: ");
		switch (input_mode) {
		case ask_new:
		case ask_silent:
			if (!is_new) {
				cnt = def;
				printf("%d\n", cnt);
				break;
			}
			check_stdin();
		case ask_all:
			if (pre_question_reminder != NULL) printf("(Reminder: %s)\n", pre_question_reminder);
			fflush(stdout);
			fgets(line, 128, stdin);
			strip(line);
			if (line[0] == '?') {
				printf("\n%s\n", menu->sym->help ?
					menu->sym->help : nohelp_text);
				continue;
			}
			if (!line[0])
				cnt = def;
			else if (isdigit(line[0]))
				cnt = atoi(line);
			else
				continue;
			break;
		case set_random:
			def = (random() % cnt) + 1;
		case set_default:
		case set_yes:
		case set_mod:
		case set_no:
			cnt = def;
			printf("%d\n", cnt);
			break;
		}

	conf_childs:
		for (child = menu->list; child; child = child->next) {
			if (!child->sym || !menu_is_visible(child))
				continue;
			if (!--cnt)
				break;
		}
		if (!child)
			continue;
		if (line[strlen(line) - 1] == '?') {
			printf("\n%s\n", child->sym->help ?
				child->sym->help : nohelp_text);
			continue;
		}
		sym_set_choice_value(sym, child->sym);
		if (child->list) {
			indent += 2;
			conf(child->list);
			indent -= 2;
		}
		return 1;
	}
}

static void conf(struct menu *menu)
{
	struct symbol *sym;
	struct property *prop;
	struct menu *child;

    DBG_ENTER("menu [%s]", DBG_MENU_NAME(menu));

	if (!menu_is_visible(menu)) {
        DBG_LEAVE("menu [%s] not visible", DBG_MENU_NAME(menu));
		return;
	}

    // NXP
    if (menu_is_frozen(menu)) {
        DBG_LEAVE("menu [%s] frozen!", DBG_MENU_NAME(menu));
  		return;
	}

	sym = menu->sym;
	prop = menu->prompt;
	if (prop) {
		const char *prompt;

		switch (prop->type) {
		case P_MENU:
			if (input_mode == ask_silent && rootEntry != menu) {
				check_conf(menu);
                DBG_LEAVE("menu [%s] (input_mode == ask_silent)", DBG_MENU_NAME(menu));
				return;
			}
		case P_COMMENT:
			prompt = menu_get_prompt(menu);
			if (prompt)
				printf("%*c\n%*c %s\n%*c\n",
					indent, '*',
					indent, '*', prompt,
					indent, '*');
		default:
			;
		}
	}

    DBG_PRINT("menu [%s] has no prop's", DBG_MENU_NAME(menu));

	if (!sym)
		goto conf_childs;

	if (sym_is_choice(sym)) {
		conf_choice(menu);
		if (sym->curr.tri != mod) {
            DBG_LEAVE("menu [%s]", DBG_MENU_NAME(menu));
			return;
		}
		goto conf_childs;
	}

	switch (sym->type) {
	case S_INT:
	case S_HEX:
	case S_STRING:
		conf_string(menu);
		break;
	default:
		conf_sym(menu);
		break;
	}

conf_childs:
	if (sym)
		indent += 2;
	for (child = menu->list; child; child = child->next)
		conf(child);
	if (sym)
		indent -= 2;
    DBG_LEAVE("menu [%s]", DBG_MENU_NAME(menu));
}

static void check_conf(struct menu *menu)
{
	struct symbol *sym;
	struct menu *child;

    DBG_ENTER("menu name %s", DBG_MENU_NAME(menu));

	if (!menu_is_visible(menu)) {
        DBG_LEAVE("menu [%s] not visible", DBG_MENU_NAME(menu));
		return;
	}

    // NXP
    if (menu_is_frozen(menu)) {
        DBG_LEAVE("menu [%s] is frozen", DBG_MENU_NAME(menu));
		return;
	}

	sym = menu->sym;
	if (sym) {
        DBG_PRINT("menu [%s] has symbol %s", DBG_MENU_NAME(menu), sym->name);
        // NXP
        menu_is_changable_check(menu);
	}

	if (sym && !sym_has_value(sym)) {
		if (sym_is_changable(sym) ||
		    (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)) {
			if (!conf_cnt++)
				fprintf(stderr, _("*\n* Restart config...\n*\n"));
			rootEntry = menu_get_parent_menu(menu);
			conf(rootEntry);
		}
	}

	for (child = menu->list; child; child = child->next)
		check_conf(child);
    DBG_LEAVE("menu [%s]", DBG_MENU_NAME(menu));
}

// NXP
/* Print in doxygen-safe way. */
void print_doc_str(const char *s, bool insertBreaks)
{
    while (*s) {
#if 0
        int n;
        char name[256];
        char c1[2], c2[2];
        if (sscanf(s, "%1[Ee]nvironment %1[Vv]ariable %[_a-zA-Z0-9]%n", c1, c2, name, &n) >= 3) {
            printf("%snvironment %sariable \\ref %s_envvar", c1, c2, name);
            s += n;
            continue;
        }

        if (strncmp(s, "CONFIG_", 7) == 0) {
            printf("\\ref %c", *s);
        } else {
            if (isalpha(*s)) {
                char *p;
                // find end of identifier:
                for (p = (char*)s + 1; *p && (isalnum(*p) || *p == '_'); p++);
                char saved = *p;
                *p = '\0';
                struct symbol *sym = sym_find(s);
                if (sym && !(sym->flags & SYMBOL_CONST)) {
                    printf("\\ref CONFIG_%s", s);
                } else {
                    printf("%s", s);
                }
                if (saved == '\0')
                    break;
                *p = saved;
                s = p;
                continue;
            }
        }
#endif

        if (*s == '\\') {
            printf("\\\\");
        } else
        if (*s == '\n') {
            printf("<br>\n");
        } else
        if (*s == '<') {
            printf("&lt;");
        } else
        if (*s == '>') {
            printf("&gt;");
        } else
        if (*s == '&') {
            printf("&amp;");
        } else {
            putchar(*s);
        }
        s++;
    }
}

static void printIndent(unsigned int i)
{
    while (i--) putchar(' ');
}

static const char * doxGetMenuLabel(struct menu *menu)
{
    const char* name;
    static char buf[100];
    char c;
    int n = 0;
#define DOX_SYM_PREFIX "_PRJCONFIG_MENU_"

    if (menu->sym && menu->sym->name)
    {
        static char symBuf[100];
        sprintf(symBuf, "CONFIG_%s", menu->sym->name);
        return symBuf;
    }

    if ((!menu->prompt) ||
        menu->prompt->type == P_COMMENT)
    {
        return NULL;
    }

    strcpy(buf, DOX_SYM_PREFIX);
    n = sizeof(DOX_SYM_PREFIX) - 1;
    name = menu->prompt->text;
    for (c = *name; (n < 99) && c; name++, c = *name)
    {
        if (isalnum(c) || (c == '_'))
        {
            buf[n] = c;
        } else {
            buf[n] = '_';
        }
        n++;
    }
    buf[n] = 0;
    return buf;
}

void generateTOC(struct menu *menu, int recurse)
{
    struct symbol *sym;
    unsigned int indent = 0;
    while (menu) {
        sym = menu->sym;
        if (sym && sym->name)
        {
            printIndent(indent);
            printf("- \\ref %s\n", doxGetMenuLabel(menu));
        }
        else
        {
            //DBG_PRINT("A menu: no symbol: %s\n", DBG_MENU_NAME(menu));
            if (menu->prompt &&
                (menu->prompt->type != P_COMMENT) &&
                menu->prompt->text)
            {
                printIndent(indent);
                printf("- \\ref %s\n", doxGetMenuLabel(menu));
            }
        }
        if (recurse && menu->list) {
            indent++;
            menu = menu->list;
            continue;
        }
        if (menu->next) {
            menu = menu->next;
        } else {
            if (recurse)
            {
                while ((indent--), (menu = menu->parent)) {
                    if (menu->next) {
                        menu = menu->next;
                        break;
                    }
                }
            }
            else
                break;
        }
    }
    printf("\n");
}

/* Output in Doxygen format. */
void generateDocumentation(void)
{
    int level;
    struct symbol *sym;
    int i, j;
    struct menu *submenu[8], *menu;
#define HTML_HEADER_LEVEL(x) ((x) >= 6 ? 6 : (x))

    printf("/**\n");
    printf("\\page prjconfig_options Index of prjconfig Options\n");
    printf("\\par Contents\n");

    /* Generate Table of Contents */
    generateTOC(rootmenu.list, 1 /*recurse*/);

    /* Generate doc for each menu and submenu */
    menu = rootmenu.list;
    level = 1;
    while (menu) {
        const char *labelName = doxGetMenuLabel(menu);
        sym = menu->sym;
        if (sym && sym->name)
        {
            bool hit;
            struct property *prop;
            struct gstr r;

            printf("\\globalLabel{%s}\n", labelName);
	    printf("\\anchor %s\n", labelName);
            printf("<h%d><a class=\"anchor\" name=\"%s\">%s</a></h%d>\n",
                   HTML_HEADER_LEVEL(level),
                   labelName, labelName,
		   HTML_HEADER_LEVEL(level));

            for_all_prompts(sym, prop) {
                printf("\\par Prompt:\n%s\n", prop->text);
                if (!expr_is_yes(prop->visible.expr)) {
                    const char *dependencyString;
                    r = str_new();
                    expr_gstr_print(prop->visible.expr, &r);
                    dependencyString = str_get(&r);
                    /* Simple choice's are not interesting to print as a dependency. */
                    if (strcmp(dependencyString, "<choice>") != 0)
                    {
                        printf("\\par Depends on:\n");
                        print_doc_str(dependencyString, false);
                        printf("\n\n");
                    }
                    str_free(&r);
                }
                struct menu *menu1 = prop->menu->parent;
                for (i = 0; menu1 != &rootmenu && i < 8; menu1 = menu1->parent)
                    submenu[i++] = menu1;
                if (i > 0) {
                    printf("\\par Menu Location:\n");
                    for (j = i - 1; j >= 0; j--) {
                        menu1 = submenu[j];
                        if (j != i - 1)
                            printf(" =&gt; ");
                        printf("  \\ref %s", doxGetMenuLabel(menu1));
                        //printf(" %s", menu_get_prompt(menu1));
                        printf("\n");
                    }
                }
            }


            if (sym->help) {
                printf("\\par Description:\n");
                print_doc_str(sym->help, true);
                printf("\n\n");
            }

            if (sym->flags & SYMBOL_CAN_BE_FROZEN) {
                printf("\\par Frozen:\n"
                       "May be frozen. See \\ref prjconfig_frozen_options\n");
            }

            hit = false;
            for_all_properties(sym, prop, P_SELECT) {
                if (!hit) {
                    printf("\\par Selects:\n");
                    hit = true;
                } else
                    printf("&&\n");
                r = str_new();
                expr_gstr_print(prop->expr, &r);
                print_doc_str(str_get(&r),false);
                printf("\n");
                str_free(&r);
            }
            if (hit)
                printf("\n");

            if (sym->rev_dep.expr) {
                printf("\\par Selected by:\n");
                r = str_new();
                expr_gstr_print(sym->rev_dep.expr, &r);
                print_doc_str(str_get(&r), false);
                printf("\n\n");
                str_free(&r);
            }
            if (sym->flags & SYMBOL_FROZEN) {
                printf("\\par FROZEN\nTrue\n\n");
            }

            printf("\n\n");
        } else {
            if (menu->prompt &&
                (menu->prompt->type != P_COMMENT) &&
                menu->prompt->text)
            {
		    printf("\\globalLabel{%s}\n", labelName);
		    printf("\\anchor %s\n", labelName);
		    printf("<h%d><a class=\"anchor\" name=\"%s\">%s</a></h%d>\n",
			   HTML_HEADER_LEVEL(level),
			   labelName, menu->prompt->text,
			   HTML_HEADER_LEVEL(level));
                if (menu->list)
                {
                    printf("\\par Menu entries:\n");
                    generateTOC(menu->list, 0 /*recurse*/);
                }
            }
        }
        if (menu->list) {
            level++;
            menu = menu->list;
            continue;
        }
        if (menu->next) {
            menu = menu->next;
        } else {
            while ((level--), (menu = menu->parent)) {
		    if (menu->next) {
			    menu = menu->next;
			    break;
		    }
            }
        }
    }
    printf("*/\n");
}

int main(int ac, char **av)
{
	int i = 1;
	const char *name;
	struct stat tmpstat;
        bool generateDoc = false;

	while (ac > i && av[i][0] == '-') {
		switch (av[i++][1]) {
		case 'o':
			input_mode = ask_new;
			break;
		case 's':
			input_mode = ask_silent;
			valid_stdin = isatty(0) && isatty(1) && isatty(2);
			break;
		case 'd':
			input_mode = set_default;
			break;
		case 'D':
			input_mode = set_default;
			defconfig_file = av[i++];
			if (!defconfig_file) {
				fprintf(stderr, _("%s: No default config file specified\n"),
					av[0]);
				exit(1);
			}
			break;
		case 'R':
			pre_question_reminder = av[i++];
			if (!pre_question_reminder) {
				fprintf(stderr, "%s: No reminder specified after -R option\n",
					av[0]);
				exit(1);
			}
			break;
		case 'n':
			input_mode = set_no;
			break;
		case 'm':
			input_mode = set_mod;
			break;
		case 'y':
			input_mode = set_yes;
			break;
		case 'r':
			input_mode = set_random;
			srandom(time(NULL));
			break;
		case 'X':
                        generateDoc = true;
			break;
		case 'h':
		case '?':
			fprintf(stderr, "%s [-o|-s] [-R reminder] config\n", av[0]);
                        fprintf(stderr, "-R reminder: print a reminder before prompting for a value\n");
                        fprintf(stderr, "-X: extract documentation for the options\n");
			exit(0);
		}
	}
  	name = av[i];
	if (!name) {
		fprintf(stderr, "%s: configuration file missing\n", av[0]);
		exit(1);
	}
	conf_parse(name);
        if (generateDoc) {
            generateDocumentation();
            exit(0);
        }
	/*zconfdump(stdout);*/
	switch (input_mode) {
	case set_default:
		if (!defconfig_file)
			defconfig_file = conf_get_default_confname();
		if (conf_read(defconfig_file)) {
			fprintf(stderr, "***\n"
				"*** Can't find default configuration \"%s\"!\n"
				"***\n", defconfig_file);
			exit(1);
		}
		break;
	case ask_silent:
		if (stat(".config", &tmpstat)) {
			fprintf(stderr, "***\n"
				"*** You have not yet configured the project!\n"
				"*** (missing .config file)\n"
				"***\n"
				"*** Please run some configurator (e.g. \"prjconfig [config_file]\" or\n"
				"*** \"./setup.sh [config_file]\").\n"
				"***\n");
			exit(1);
		}
	case ask_all:
	case ask_new:
		conf_read(NULL);
		break;
	default:
		break;
	}

	if (input_mode != ask_silent) {
		rootEntry = &rootmenu;
		conf(&rootmenu);
		if (input_mode == ask_all) {
			input_mode = ask_silent;
			valid_stdin = 1;
		}
	}
	do {
		conf_cnt = 0;
		check_conf(&rootmenu);
	} while (conf_cnt);
	if (conf_write(NULL)) {
		fprintf(stderr, "\n*** Error during writing of the project configuration.\n\n");
		return 1;
	}
	return 0;
}
