#include <common.h>
#include <bootmenu.h>
#include <linux/ctype.h>
#include <epi3.h>
#include <mmc_spi.h>
#include <malloc.h>
#ifdef CONFIG_USB_STORAGE
#include <usb.h>
#endif

DECLARE_GLOBAL_DATA_PTR;

#ifdef CONFIG_IPV6
extern ulong		NetIpVersion;
#endif

static const char *fs_types[] = 
{ 
	"flash", /* TYPE_FLASH */
	"card",  /* TYPE_SDCARD */
	"card",  /* TYPE_CFCARD - still called 'card:' to the outside world */
	"usb",
};

struct list_head file_list;
int upgrading_g = 0;

#ifdef CONFIG_USB_STORAGE
static int usb_stor_curr_dev = -1; /* current device */
#endif

static boot_file *bootmenu_list_and_get_bootable_file(void);

static void free_file_list(void)
{
	while (!list_empty(&file_list))
	{
		boot_file *file = list_first_entry(&file_list, boot_file, list);
		list_del(&file->list);
		free(file);
	}
}

int bootmenu_one_off_boot(int one_off, int upgrading)
{
	char	key;
	u8	quit;

	quit = 0;
	
	if (upgrading)
		upgrading_g = 1;
	else
		upgrading_g = 0;
	
	if (upgrading && !getenv("developer"))
	{
		bootmenu_printf("  WARNING!\n\n"
				"  Bootloader upgrades are rarely needed and should NEVER be conducted\n"
				"  without the direct support of an Allied Telesis sales office or\n"
				"  service technician.\n\n"
				"  Continue? (Y/N) ==> "
			       );
		
		key = serial_getc();
	
		if ( key != 'y' && key != 'Y' )
		{
			bootmenu_printf("\n\n");
			return 0;
		}
		
		bootmenu_printf("\n\n");
	}

	if (!one_off)
	{
		bootmenu_printf("  NOTE: These settings are specific to the Bootloader.\n");
		bootmenu_printf("  They are not related in any way to what may be configured\n");
		bootmenu_printf("  by the 'boot system' command in the main software release.\n\n");
	}

	while (!quit)
	{

/*		Display the menu
 */
		serial_puts( "Select device:\n\n"
				"  0. Return to previous menu\n"
				"  ------------------------------------------------------\n"
#ifndef CONFIG_SYS_NO_FLASH
				"  1. Flash   (flash:)\n"
#endif
#if defined(CONFIG_MMC_SPI)				
				"  2. SD Card (card:)\n"
#elif defined(CONFIG_COMPACTFLASH)				
				"  2. CF Card (card:)\n"
#endif
			   );
#if defined(CONFIG_CMD_NET)
			serial_puts(
				"  3. TFTP    (tftp://)\n"
				       );
#endif /* (CONFIG_CMD_NET) */
		    serial_puts("  4. YMODEM  (ymodem:)\n");
			if (upgrading && atl_bootrom_board_attached())
		    serial_puts("  5. EPROM\n");
#ifdef CONFIG_USB_STORAGE
		serial_puts("  6. USB     (usb:)\n");
#endif
		if (!one_off)	
		    serial_puts("  ------------------------------------------------------\n"
				"  9. Boot from default (determined by main CLI)\n"
				   );
			serial_puts(
				"\nEnter selection ==> " );

/*		Wait for a key to be pressed
 */
		key = serial_getc();

		if (isdigit(key))
			serial_putc(key);

		serial_putc('\n');
		serial_putc('\n');

/*		Action the selection
 */
		switch ( key )
		{
			case '0':
				quit = 1;
				break;
#ifndef CONFIG_SYS_NO_FLASH
			case '1':
				if (bootmenu_set_flash_boot(upgrading))
				{
					setenv("bootcmd", "bootmenu");
					/* Don't set GD_FLG_ONEOFFBOOT_FORCED. apparently this 'confuses' testers. See CR15579 */
					return 1;
				}
				break;
#endif
#if defined(CONFIG_MMC_SPI)	
			case '2':
				if (bootmenu_set_sdcard_boot(upgrading))
				{
					setenv("bootcmd", "bootmenu");
					if (one_off)
						gd->flags |= GD_FLG_ONEOFFBOOT_FORCED;
					return 1;
				}
				break;
#elif defined(CONFIG_COMPACTFLASH)
			case '2':
				if (bootmenu_set_cfcard_boot(upgrading))
				{
					setenv("bootcmd", "bootmenu");
					if (one_off)
						gd->flags |= GD_FLG_ONEOFFBOOT_FORCED;
					return 1;
				}
				break;
#endif
#if defined (CONFIG_CMD_NET)
			case '3':
				if (bootmenu_set_tftp_boot(upgrading))
				{
					setenv("bootcmd", "bootmenu");
					if (one_off)
						gd->flags |= GD_FLG_ONEOFFBOOT_FORCED;
					return 1;
				}
				break;
#endif /* (CONFIG_CMD_NET) */
			case '4':	
				bootmenu_set_ymodem_boot(upgrading);
				setenv("bootcmd", "bootmenu");
				if (one_off)
					gd->flags |= GD_FLG_ONEOFFBOOT_FORCED;
				return 1;
				break;
			case '5':
				if (upgrading && atl_bootrom_board_attached())
				{
					bootmenu_set_eprom_boot(upgrading);
					setenv("bootcmd", "bootmenu");
					return 1;
				}
#ifdef CONFIG_USB_STORAGE
			case '6':
				if (bootmenu_set_usbcard_boot(upgrading))
				{
					setenv("bootcmd", "bootmenu");
					if (one_off)
						gd->flags |= GD_FLG_ONEOFFBOOT_FORCED;
					return 1;
				}
				break;
#endif

			case '9':
				bootmenu_set_default_boot();
				return 1;
			default:	
				break;			/* Ignore any other character */
		}
	}
	
	return 0;
}

void bootmenu_set_default_boot(void)
{
	/* clear any IP settings that can interfere if the default method uses BOOTP */
	if (strstr(CONFIG_BOOTCMD_ENV_SETTING, "bootp") != NULL)
	{
		setenv("gatewayip", "0.0.0.0");
		setenv("serverip", "0.0.0.0");
	}
	setenv("bootcmd", CONFIG_BOOTCMD_ENV_SETTING);
}

#if defined(CONFIG_MMC_SPI)

int bootmenu_set_sdcard_boot(int upgrading)
{
	boot_file	*file;
	u8		boot;
	
	boot = 0;
	
	if (!sdcard_is_present())
	{
		serial_puts("  Error: There is currently no SD Card in the slot!\n\n");
		return boot;
	}
	
#if defined(CONFIG_X600)	
	/*
	 * x600 doesn't have any way of knowing when the card changed
	 * so the best we can really do is just re-initialise it every
	 * time we enter this menu
	 */
	mmc_spi_init(1);
#endif	

	INIT_LIST_HEAD(&file_list);
	bootmenu_printf("  Reading filesystem...\n\n");
	run_command("fatls mmcspi 0", upgrading);

	if ((file = bootmenu_list_and_get_bootable_file()) != NULL)
	{
		bootmenu_set_system_boot(file, upgrading);
		boot = 1;
	}
	free_file_list();
	return boot;
}

#elif defined(CONFIG_COMPACTFLASH)

int bootmenu_set_cfcard_boot(int upgrading)
{
	boot_file	*file;
	u8		boot;
	
	boot = 0;
	
	if (!cflash_present())
	{
		serial_puts("  Error: There is currently no CF Card in the slot!\n\n");
		return;
	}
	
	ide_init();

	INIT_LIST_HEAD(&file_list);
	
	bootmenu_printf("  Reading filesystem...\n\n");
	
	run_command("fatls ide 0", 0);	
	
	if ((file = bootmenu_list_and_get_bootable_file()) != NULL)
	{
		bootmenu_set_system_boot(file, upgrading);
		boot = 1;
	}
	free_file_list();
	return boot;
}

#endif

#ifdef CONFIG_USB_STORAGE
int bootmenu_set_usbcard_boot(int upgrading)
{
	boot_file *file;
	u8 boot = 0;

	usb_stop();
	if (usb_init() >= 0)
		usb_stor_curr_dev = usb_stor_scan(1);

	if (usb_stor_curr_dev < 0) {
		serial_puts("  Error: No usable USB storage device found.\n\n");
		return boot;
	}

	INIT_LIST_HEAD(&file_list);
	bootmenu_printf("  Reading filesystem...\n\n");
	run_command("fatls usb 0", 0);

	if ((file = bootmenu_list_and_get_bootable_file()) != NULL)
	{
		bootmenu_set_system_boot(file, upgrading);
		boot = 1;
	}
	free_file_list();
	return boot;
}
#endif

#ifndef CONFIG_SYS_NO_FLASH
int bootmenu_set_flash_boot(int upgrading)
{
	boot_file	*file;
	u8		boot;
	
	boot = 0;
	
	INIT_LIST_HEAD(&file_list);
	
	bootmenu_printf("  Reading filesystem...\n\n");
	
	run_command("ls", 0);	
	
	if ((file = bootmenu_list_and_get_bootable_file()) != NULL)
	{
		bootmenu_set_system_boot(file, upgrading);
		boot = 1;
	}
	free_file_list();
	return boot;
}
#endif

int bootmenu_set_ymodem_boot(int upgrading)
{
	boot_file	file;
	u8		boot;
	
	boot = 0;
	
	file.fs_type = TYPE_YMODEM;
	
	bootmenu_set_system_boot(&file, upgrading);
	boot = 1;
			
	return boot;
}

int bootmenu_set_eprom_boot(int upgrading)
{
	boot_file	file;
	u8		boot;
	
	boot = 0;
	
	file.fs_type = TYPE_EPROM;
	
	bootmenu_set_system_boot(&file, upgrading);
	boot = 1;
			
	return boot;
}

#ifdef CONFIG_ETH_SWITCH
int bootmenu_switchport_menu(void)
{
	char 		buf[512];
	char		buf2[256];
	int		min_port = getenv("developer") ? 0 : 1;
	int		invalid = 1;
	int		port;
	int		max_port;
#ifdef CONFIG_DIAG_EPI3
	epi3Personality *	personality = epi3_getcurrent();

	switch (personality->epiBoardId) {
	case 293: /* x600-48Ts */
	case 294: /* x600-48Ts/XP */
		max_port = 44;
		break;
	case 330: /* x610-48Ts/X-POE+ */
	case 331: /* x610-48Ts-POE+ */
	case 338: /* x610-48Ts/X */
	case 339: /* x610-48Ts */
	case 344: /* RP48x */
	case 347: /* x200-GE-52T */
	case 362: /* x200-FE-50Ts */
	case 363: /* x200-FE-50Ts-80 */
	case 365: /* x400-FT-50Ts */
	case 372: /* x510-52GTX */
	case 373: /* x510-52GPX */
		max_port = 48;
		break;
	case 366: /* x210-9GT */
	case 367: /* x210-16GT */
	case 368: /* x210-24GT */
		/* only use the embedded phy */
		max_port = 8;
		break;
	default:
		max_port = 24;
		break;
	}
#else
	max_port = 24;
#endif

	while (invalid)
	{
		sprintf(buf2, "  Enter physical download port (%d-%d)...",
		        min_port, max_port);

		if (!bootmenu_string_prompt(buf, buf2, getenv("download_port"), sizeof(buf)))
			goto fail;
		
		port = simple_strtoul(buf, NULL, 10);
		
		if (port < min_port || port > max_port)
		{
			bootmenu_printf("\nError: Invalid download port specified\n\n");
			continue;
		}
		
		sprintf(buf2, "%d", port);
		setenv("download_port", buf2);
		
		invalid = 0;
		break;
	}
	return 1;
fail:
	return 0;
}
#endif

#if defined (CONFIG_CMD_NET)
int bootmenu_set_tftp_boot(int upgrading)
{
	boot_file	file;
	char 		buf[CONFIG_SYS_CBSIZE];
	u8		boot;
	
	boot = 0;
	
#ifdef CONFIG_IPV6
	/* if ipversion is not specified, we default to IPv4 for backward compatibility */
	if ( getenv("ipversion") == NULL )
	{
		setenv("ipversion", "4");
		NetIpVersion = 4;
	}

#ifdef CONFIG_ETH_SWITCH
	if (!bootmenu_switchport_menu())
		goto fail;
#else
	bootmenu_printf("\nNote: TFTP downloads can only be performed through the 'eth0' interface\n\n");
#endif

	if (!bootmenu_string_prompt(buf, "  Enter IP version [4|6]................", getenv("ipversion"), sizeof(buf)))
		goto fail;
	
	setenv("ipversion", buf);
	if ( bootmenu_stricmp (getenv("ipversion"), "6") == 0 )
		NetIpVersion = 6;
	else if ( bootmenu_stricmp (getenv("ipversion"), "4") == 0 )
		NetIpVersion = 4;
	else
	{
		NetIpVersion = 4;
		setenv("ipversion", "4");	
	}	

	if ( NetIpVersion == 6 )
	{
		if (!bootmenu_string_prompt(buf, "  Enter IPv6 address for this device ...", getenv("ipaddr6"), sizeof(buf)))
			goto fail;
		
		setenv("ipaddr6", buf);
		if (!bootmenu_string_prompt(buf, "  Enter prefix length ..................", getenv("prefixlength6"), sizeof(buf)))
			goto fail;
		
		setenv("prefixlength6", buf);
		if (!bootmenu_string_prompt(buf, "  Enter IPv6 gateway ...................", getenv("gatewayip6"), sizeof(buf)))
			goto fail;
		
		setenv("gatewayip6", buf);
		if (!bootmenu_string_prompt(buf, "  Enter IPv6 TFTP server ...............", getenv("serverip6"), sizeof(buf)))
			goto fail;
		
		setenv("serverip6", buf);
	}
	else
#endif
	{
		if (!bootmenu_string_prompt(buf, "  Enter IP address for this device .....", getenv("ipaddr"), sizeof(buf)))
			goto fail;
		
		setenv("ipaddr", buf);
		if (!bootmenu_string_prompt(buf, "  Enter subnet mask ....................", getenv("netmask"), sizeof(buf)))
			goto fail;
		
		setenv("netmask", buf);
		if (!bootmenu_string_prompt(buf, "  Enter gateway IP .....................", getenv("gatewayip"), sizeof(buf)))
			goto fail;
		
		setenv("gatewayip", buf);
		if (!bootmenu_string_prompt(buf, "  Enter TFTP server IP .................", getenv("serverip"), sizeof(buf)))
			goto fail;
		
		setenv("serverip", buf);
	}
	
	if (!bootmenu_string_prompt(buf, "  Enter filename .......................", upgrading ? getenv("updafile") : getenv("bootfile"), sizeof(buf)))
		goto fail;
	
	boot = 1;
	file.fs_type = TYPE_TFTP;
	sprintf(file.file_name, "%.*s", FILE_NAME_MAX_LEN, buf);
	bootmenu_set_system_boot(&file, upgrading);
	
	return boot;
	
fail:
	boot = 0;
	bootmenu_printf("\n");
				
	return boot;
}

#endif /* (CONFIG_CMD_NET) */

static boot_file *bootmenu_list_and_get_bootable_file(void)
{
	u32 selected;
	int index;
	int num_items;
	boot_file *file;
	
	if (list_empty(&file_list))
	{
		bootmenu_printf("  Error: There are no usable files in the root directory of this device\n\n");
		return NULL;
	}
	
	bootmenu_printf("Listing of usable files (root directory only)\n\n");

	num_items = 0;
	list_for_each_entry(file, &file_list, list)
	{
		num_items++;
		bootmenu_printf("%3d. %s:%s\n", num_items, fs_types[file->fs_type], file->file_name);
	}
	bootmenu_printf("\nEnter selection (1-%u, 0 to cancel) and press enter ==> ", num_items);

	if (bootmenu_get_int(&selected))
	{
		if (selected == 0)
		{
			bootmenu_printf("\n\n");
			return NULL;
		}
		if (selected > num_items)
		{
			bootmenu_printf("\n\nValue entered must be between 1 and %u\n", num_items);
			return NULL;
		}
		bootmenu_printf("\n\n");

		index = 0;
		list_for_each_entry(file, &file_list, list)
		{
			if (++index == selected)
				return file;
		}
	}
	bootmenu_printf("\n\n  Invalid selection\n\n");

	return NULL;
}

void bootmenu_file_add(long file_len, const char *file_name, u8 dir, u8 type, int if_type)
{
	boot_file *file;
	const char *s;
	
	if (strlen(file_name) < 5)
		return;
	
	s = file_name + (strlen(file_name) - 4);
	
	if (type == TYPE_CARD)
	{
		switch (if_type) {
		case IF_TYPE_MMC:
		case IF_TYPE_SD:
			type = TYPE_SDCARD;
			break;
		case IF_TYPE_IDE:
			type = TYPE_CFCARD;
			break;
		case IF_TYPE_USB:
			type = TYPE_USBCARD;
			break;
		}
	}
	
	if (upgrading_g)
	{
		if (bootmenu_stricmp(s, CONFIG_BOOTFILE_EXT))
			return;
	}
	else
	{
		if (bootmenu_stricmp(s, CONFIG_RELEASEFILE_EXT))
			return;
	}
	
	if (dir)
		return;

	file = malloc(sizeof(*file));
	if (!file)
		return;

	strncpy(file->file_name, file_name, 260);
	file->file_len = file_len;
	file->fs_type = type;
	
	list_add(&file->list, &file_list);
}

