/*
 * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
 * Released under the terms of the GNU GPL v2.0.
 */

#include <locale.h>      // 引入本地化相关功能的头文件
#include <ctype.h>       // 引入字符处理函数的头文件，例如 isspace、isdigit 等
#include <stdio.h>       // 引入标准输入输出库
#include <stdlib.h>      // 引入标准库，提供内存管理、程序退出等功能
#include <string.h>      // 引入字符串处理函数库
#include <time.h>        // 引入时间日期相关函数
#include <unistd.h>      // 引入 Unix 系统 API，提供与系统交互的功能
#include <getopt.h>      // 引入命令行参数解析函数
#include <sys/stat.h>    // 引入文件状态函数
#include <sys/time.h>    // 引入时间管理函数
#include <errno.h>       // 引入错误码定义
#include "lkc.h"         // 引入项目自定义的头文件

// 函数声明
static void conf(struct menu *menu);                // 配置函数
static void check_conf(struct menu *menu);          // 检查配置函数
static void xfgets(char *str, int size, FILE *in);  // 自定义的 fgets 函数

// 定义输入模式枚举类型
enum input_mode {
    oldaskconfig,       // 用户交互模式
    silentoldconfig,    // 无用户交互模式，使用默认值
    oldconfig,          // 使用旧的配置
    allnoconfig,        // 所有选项都为 no
    allyesconfig,       // 所有选项都为 yes
    allmodconfig,       // 所有选项都为模块
    alldefconfig,       // 所有选项都为默认值
    randconfig,         // 随机配置
    defconfig,          // 使用默认配置
    savedefconfig,      // 保存默认配置
    listnewconfig,      // 列出新的配置项
    olddefconfig,       // 使用旧的默认配置
} input_mode = oldaskconfig;  // 设置默认输入模式为 oldaskconfig

// 静态变量声明
static int indent = 1;            // 配置输出时的缩进级别
static int tty_stdio;             // 标识标准输入输出是否是终端设备
static int valid_stdin = 1;       // 标识标准输入是否有效
static int sync_kconfig;          // 同步配置标志
static int conf_cnt;              // 配置项计数器
static char line[128];            // 用于存储输入行的缓冲区
static struct menu *rootEntry;    // 配置菜单的根节点指针

// 打印帮助信息
static void print_help(struct menu *menu)
{
    struct gstr help = str_new();  // 创建一个新的字符串对象

    menu_get_ext_help(menu, &help);  // 获取菜单项的扩展帮助信息

    printf("\n%s\n", str_get(&help));  // 打印帮助信息
    str_free(&help);  // 释放帮助字符串资源
}

// 去除字符串两端的空白字符
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) {  // 如果标准输入无效
        printf(_("aborted!\n\n"));
        printf(_("Console input/output is redirected. "));
        printf(_("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);  // 获取符号的类型

    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 oldconfig:
    case silentoldconfig:
        if (sym_has_value(sym)) {  // 如果已有值，直接输出默认值
            printf("%s\n", def);
            return 0;
        }
        check_stdin();  // 检查标准输入有效性
        /* fall through */
    case oldaskconfig:
        fflush(stdout);  // 刷新标准输出缓冲区
        xfgets(line, 128, stdin);  // 获取用户输入
        if (!tty_stdio)  // 如果标准输入不是终端，换行
            printf("\n");
        return 1;
    default:
        break;
    }

    // 根据符号类型进行处理
    switch (type) {
    case S_INT:
    case S_HEX:
    case S_STRING:
        printf("%s\n", def);
        return 1;
    default:
        ;
    }
    printf("%s", line);  // 输出用户输入的内容
    return 1;
}

// 配置字符串类型的处理函数
static int conf_string(struct menu *menu)
{
    struct symbol *sym = menu->sym;
    const char *def;

    while (1) {
        printf("%*s%s ", indent - 1, "", _(menu->prompt->text));  // 打印菜单提示
        printf("(%s) ", sym->name);  // 打印符号名称
        def = sym_get_string_value(sym);  // 获取符号的默认字符串值
        if (def)  // 如果有默认值，打印出来
            printf("[%s] ", def);
        if (!conf_askvalue(sym, def))  // 获取用户输入并处理
            return 0;

        // 根据用户输入的内容进行处理
        switch (line[0]) {
        case '\n':
            break;  // 如果用户没有输入内容，继续
        case '?':
            // 打印帮助信息
            if (line[1] == '\n') {
                print_help(menu);  // 打印帮助
                def = NULL;
                break;
            }
            /* fall through */
        default:
            line[strlen(line) - 1] = 0;  // 去掉换行符
            def = line;  // 将用户输入的内容赋给 def
        }

        // 设置符号的字符串值
        if (def && sym_set_string_value(sym, def))
            return 0;
    }
}


// 配置符号的值（如：yes、no、mod）的函数
static int conf_sym(struct menu *menu)
{
    struct symbol *sym = menu->sym;  // 获取菜单项的符号
    tristate oldval, newval;         // 旧值和新值变量，tristate表示三种状态（yes、no、mod）

    while (1) {
        // 打印菜单提示信息，包括符号名称（如果存在）
        printf("%*s%s ", indent - 1, "", _(menu->prompt->text));  // 打印缩进和提示文本
        if (sym->name)  // 如果符号有名称，打印符号名称
            printf("(%s) ", sym->name);
        
        putchar('[');  // 打印开头的方括号
        oldval = sym_get_tristate_value(sym);  // 获取符号的当前值

        // 根据当前的状态值（oldval），打印相应的字符
        switch (oldval) {
        case no:
            putchar('N');  // 输出 'N' 表示当前为 no
            break;
        case mod:
            putchar('M');  // 输出 'M' 表示当前为 mod
            break;
        case yes:
            putchar('Y');  // 输出 'Y' 表示当前为 yes
            break;
        }

        // 根据当前的状态值，显示可选的值（no、mod、yes），如果符号的值在范围内
        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 (menu_has_help(menu))
            printf("/?");

        printf("] ");  // 打印结束的方括号
        // 询问用户输入新值，如果没有输入有效值，返回 0
        if (!conf_askvalue(sym, sym_get_string_value(sym)))
            return 0;

        strip(line);  // 去除用户输入的字符串两端空格

        // 根据用户输入的值进行处理
        switch (line[0]) {
        case 'n':
        case 'N':  // 如果输入是 'n' 或 'N'，设置新值为 no
            newval = no;
            if (!line[1] || !strcmp(&line[1], "o"))
                break;
            continue;  // 如果有其他字符，继续下一次循环
        case 'm':
        case 'M':  // 如果输入是 'm' 或 'M'，设置新值为 mod
            newval = mod;
            if (!line[1])
                break;
            continue;  // 如果有其他字符，继续下一次循环
        case 'y':
        case 'Y':  // 如果输入是 'y' 或 'Y'，设置新值为 yes
            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;  // 如果设置失败，返回 0

help:
        print_help(menu);  // 打印帮助信息
    }
}

// 配置选择菜单项的函数
static int conf_choice(struct menu *menu)
{
    struct symbol *sym, *def_sym;
    struct menu *child;
    bool is_new;

    sym = menu->sym;  // 获取当前菜单项的符号
    is_new = !sym_has_value(sym);  // 检查符号是否有值（是否为新符号）

    // 如果符号可修改，进行符号的配置
    if (sym_is_changable(sym)) {
        conf_sym(menu);  // 调用 conf_sym 函数配置符号
        sym_calc_value(sym);  // 计算符号的值

        // 根据符号的值执行不同的操作
        switch (sym_get_tristate_value(sym)) {
        case no:  // 如果值是 no，返回 1
            return 1;
        case mod:  // 如果值是 mod，返回 0
            return 0;
        case yes:  // 如果值是 yes，继续执行
            break;
        }
    } else {  // 如果符号不可修改
        switch (sym_get_tristate_value(sym)) {
        case no:  // 如果值是 no，返回 1
            return 1;
        case mod:  // 如果值是 mod，打印菜单提示并返回 0
            printf("%*s%s\n", indent - 1, "", _(menu_get_prompt(menu)));
            return 0;
        case yes:  // 如果值是 yes，继续执行
            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 (menu_has_help(menu))
			printf("?");
		printf("]: ");
		switch (input_mode) {
		case oldconfig:
		case silentoldconfig:
			if (!is_new) {
				cnt = def;
				printf("%d\n", cnt);
				break;
			}
			check_stdin();
			/* fall through */
		case oldaskconfig:
			fflush(stdout);
			xfgets(line, 128, stdin);
			strip(line);
			if (line[0] == '?') {
				print_help(menu);
				continue;
			}
			if (!line[0])
				cnt = def;
			else if (isdigit(line[0]))
				cnt = atoi(line);
			else
				continue;
			break;
		default:
			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[0] && line[strlen(line) - 1] == '?') {
			print_help(child);
			continue;
		}
		sym_set_choice_value(sym, child->sym);
		for (child = child->list; child; child = child->next) {
			indent += 2;
			conf(child);
			indent -= 2;
		}
		return 1;
	}
}

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

	if (!menu_is_visible(menu))
		return;

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

		switch (prop->type) {
		case P_MENU:
			if ((input_mode == silentoldconfig ||
			     input_mode == listnewconfig ||
			     input_mode == olddefconfig) &&
			    rootEntry != menu) {
				check_conf(menu);
				return;
			}
			/* fall through */
		case P_COMMENT:
			prompt = menu_get_prompt(menu);
			if (prompt)
				printf("%*c\n%*c %s\n%*c\n",
					indent, '*',
					indent, '*', _(prompt),
					indent, '*');
		default:
			;
		}
	}

	if (!sym)
		goto conf_childs;

	if (sym_is_choice(sym)) {
		conf_choice(menu);
		if (sym->curr.tri != mod)
			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;
}

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

	if (!menu_is_visible(menu))
		return;

	sym = menu->sym;
	if (sym && !sym_has_value(sym)) {
		if (sym_is_changable(sym) ||
		    (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)) {
			if (input_mode == listnewconfig) {
				if (sym->name && !sym_is_choice_value(sym)) {
					printf("%s%s\n", CONFIG_, sym->name);
				}
			} else if (input_mode != olddefconfig) {
				if (!conf_cnt++)
					printf(_("*\n* Restart config...\n*\n"));
				rootEntry = menu_get_parent_menu(menu);
				conf(rootEntry);
			}
		}
	}

	for (child = menu->list; child; child = child->next)
		check_conf(child);
}

static struct option long_opts[] = {
	{"oldaskconfig",    no_argument,       NULL, oldaskconfig},
	{"oldconfig",       no_argument,       NULL, oldconfig},
	{"silentoldconfig", no_argument,       NULL, silentoldconfig},
	{"defconfig",       optional_argument, NULL, defconfig},
	{"savedefconfig",   required_argument, NULL, savedefconfig},
	{"allnoconfig",     no_argument,       NULL, allnoconfig},
	{"allyesconfig",    no_argument,       NULL, allyesconfig},
	{"allmodconfig",    no_argument,       NULL, allmodconfig},
	{"alldefconfig",    no_argument,       NULL, alldefconfig},
	{"randconfig",      no_argument,       NULL, randconfig},
	{"listnewconfig",   no_argument,       NULL, listnewconfig},
	{"olddefconfig",    no_argument,       NULL, olddefconfig},
	/*
	 * oldnoconfig is an alias of olddefconfig, because people already
	 * are dependent on its behavior(sets new symbols to their default
	 * value but not 'n') with the counter-intuitive name.
	 */
	{"oldnoconfig",     no_argument,       NULL, olddefconfig},
	{NULL, 0, NULL, 0}
};

static void conf_usage(const char *progname)
{

	printf("Usage: %s [option] <kconfig-file>\n", progname);
	printf("[option] is _one_ of the following:\n");
	printf("  --listnewconfig         List new options\n");
	printf("  --oldaskconfig          Start a new configuration using a line-oriented program\n");
	printf("  --oldconfig             Update a configuration using a provided .config as base\n");
	printf("  --silentoldconfig       Same as oldconfig, but quietly, additionally update deps\n");
	printf("  --olddefconfig          Same as silentoldconfig but sets new symbols to their default value\n");
	printf("  --oldnoconfig           An alias of olddefconfig\n");
	printf("  --defconfig <file>      New config with default defined in <file>\n");
	printf("  --savedefconfig <file>  Save the minimal current configuration to <file>\n");
	printf("  --allnoconfig           New config where all options are answered with no\n");
	printf("  --allyesconfig          New config where all options are answered with yes\n");
	printf("  --allmodconfig          New config where all options are answered with mod\n");
	printf("  --alldefconfig          New config with all symbols set to default\n");
	printf("  --randconfig            New config with random answer to all options\n");
}

int main(int ac, char **av)
{
	const char *progname = av[0];
	int opt;
	const char *name, *defconfig_file = NULL /* gcc uninit */;
	struct stat tmpstat;

	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);

	tty_stdio = isatty(0) && isatty(1) && isatty(2);

	while ((opt = getopt_long(ac, av, "", long_opts, NULL)) != -1) {
		input_mode = (enum input_mode)opt;
		switch (opt) {
		case silentoldconfig:
			sync_kconfig = 1;
			break;
		case defconfig:
		case savedefconfig:
			defconfig_file = optarg;
			break;
		case randconfig:
		{
			struct timeval now;
			unsigned int seed;
			char *seed_env;

			/*
			 * Use microseconds derived seed,
			 * compensate for systems where it may be zero
			 */
			gettimeofday(&now, NULL);
			seed = (unsigned int)((now.tv_sec + 1) * (now.tv_usec + 1));

			seed_env = getenv(PRODUCT_ENV"_SEED");
			if( seed_env && *seed_env ) {
				char *endp;
				int tmp = (int)strtol(seed_env, &endp, 0);
				if (*endp == '\0') {
					seed = tmp;
				}
			}
			fprintf( stderr, PRODUCT_ENV"_SEED=0x%X\n", seed );
			srand(seed);
			break;
		}
		case oldaskconfig:
		case oldconfig:
		case allnoconfig:
		case allyesconfig:
		case allmodconfig:
		case alldefconfig:
		case listnewconfig:
		case olddefconfig:
			break;
		case '?':
			conf_usage(progname);
			exit(1);
			break;
		}
	}
	if (ac == optind) {
		printf(_("%s: Kconfig file missing\n"), av[0]);
		conf_usage(progname);
		exit(1);
	}
	name = av[optind];
	conf_parse(name);
	if (sync_kconfig) {
		name = conf_get_configname();
		if (stat(name, &tmpstat)) {
			fprintf(stderr, _("***\n"
				"*** Configuration file \"%s\" not found!\n"
				"***\n"
				"*** Please run some configurator (e.g. \"make oldconfig\" or\n"
				"*** \"make menuconfig\" or \"make xconfig\").\n"
				"***\n"), name);
			exit(1);
		}
	}

	switch (input_mode) {
	case defconfig:
		if (!defconfig_file)
			defconfig_file = conf_get_default_confname();
		if (conf_read(defconfig_file)) {
			printf(_("***\n"
				"*** Can't find default configuration \"%s\"!\n"
				"***\n"), defconfig_file);
			exit(1);
		}
		break;
	case savedefconfig:
	case silentoldconfig:
	case oldaskconfig:
	case oldconfig:
	case listnewconfig:
	case olddefconfig:
		conf_read(NULL);
		break;
	case allnoconfig:
	case allyesconfig:
	case allmodconfig:
	case alldefconfig:
	case randconfig:
		name = getenv(PRODUCT_ENV"_ALLCONFIG");
		if (!name)
			break;
		if ((strcmp(name, "") != 0) && (strcmp(name, "1") != 0)) {
			if (conf_read_simple(name, S_DEF_USER)) {
				fprintf(stderr,
					_("*** Can't read seed configuration \"%s\"!\n"),
					name);
				exit(1);
			}
			break;
		}
		switch (input_mode) {
		case allnoconfig:	name = "allno.config"; break;
		case allyesconfig:	name = "allyes.config"; break;
		case allmodconfig:	name = "allmod.config"; break;
		case alldefconfig:	name = "alldef.config"; break;
		case randconfig:	name = "allrandom.config"; break;
		default: break;
		}
		if (conf_read_simple(name, S_DEF_USER) &&
		    conf_read_simple("all.config", S_DEF_USER)) {
			fprintf(stderr,
				_("*** "PRODUCT_ENV"_ALLCONFIG set, but no \"%s\" or \"all.config\" file found\n"),
				name);
			exit(1);
		}
		break;
	default:
		break;
	}

	if (sync_kconfig) {
		if (conf_get_changed()) {
			name = getenv(PRODUCT_ENV"_NOSILENTUPDATE");
			if (name && *name) {
				fprintf(stderr,
					_("\n*** The configuration requires explicit update.\n\n"));
				return 1;
			}
		}
		valid_stdin = tty_stdio;
	}

	switch (input_mode) {
	case allnoconfig:
		conf_set_all_new_symbols(def_no);
		break;
	case allyesconfig:
		conf_set_all_new_symbols(def_yes);
		break;
	case allmodconfig:
		conf_set_all_new_symbols(def_mod);
		break;
	case alldefconfig:
		conf_set_all_new_symbols(def_default);
		break;
	case randconfig:
		/* Really nothing to do in this loop */
		while (conf_set_all_new_symbols(def_random)) ;
		break;
	case defconfig:
		conf_set_all_new_symbols(def_default);
		break;
	case savedefconfig:
		break;
	case oldaskconfig:
		rootEntry = &rootmenu;
		conf(&rootmenu);
		input_mode = silentoldconfig;
		/* fall through */
	case oldconfig:
	case listnewconfig:
	case olddefconfig:
	case silentoldconfig:
		/* Update until a loop caused no more changes */
		do {
			conf_cnt = 0;
			check_conf(&rootmenu);
		} while (conf_cnt &&
			 (input_mode != listnewconfig &&
			  input_mode != olddefconfig));
		break;
	}

	if (sync_kconfig) {
		/* silentoldconfig is used during the build so we shall update autoconf.
		 * All other commands are only used to generate a config.
		 */
		if (conf_get_changed() && conf_write(NULL)) {
			fprintf(stderr, _("\n*** Error during writing of the configuration.\n\n"));
			exit(1);
		}
		if (conf_write_autoconf()) {
			fprintf(stderr, _("\n*** Error during update of the configuration.\n\n"));
			return 1;
		}
	} else if (input_mode == savedefconfig) {
		if (conf_write_defconfig(defconfig_file)) {
			fprintf(stderr, _("n*** Error while saving defconfig to: %s\n\n"),
			        defconfig_file);
			return 1;
		}
	} else if (input_mode != listnewconfig) {
		if (conf_write(NULL)) {
			fprintf(stderr, _("\n*** Error during writing of the configuration.\n\n"));
			exit(1);
		}
	}
	return 0;
}

/*
 * Helper function to facilitate fgets() by Jean Sacren.
 */
void xfgets(char *str, int size, FILE *in)
{
	if (fgets(str, size, in) == NULL)
		fprintf(stderr, "\nError in reading or end of file.\n");
}
