/*
 * Copyright 2012 Google Inc.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <assert.h>
#include <libpayload.h>
#include <sysinfo.h>
#include <vboot_api.h>
#include <vboot_struct.h>

#include "base/cleanup_funcs.h"
#include "drivers/video/coreboot_fb.h"
#include "drivers/video/display.h"
#include "vboot/firmware_id.h"
#include "vboot/util/commonparams.h"
#include "vboot/screens.h"

VbError_t VbExDisplayInit(uint32_t *width, uint32_t *height)
{
	if (display_init())
		return VBERROR_UNKNOWN;

	video_init();
	video_console_cursor_enable(0);

	if (gbb_copy_in_bmp_block())
		return VBERROR_UNKNOWN;

	if (lib_sysinfo.framebuffer) {
		*width = lib_sysinfo.framebuffer->x_resolution;
		*height = lib_sysinfo.framebuffer->y_resolution;
	} else {
		*width = *height = 0;
	}

	return VBERROR_SUCCESS;
}

VbError_t VbExDisplayBacklight(uint8_t enable)
{
	if (backlight_update(enable))
		return VBERROR_UNKNOWN;

	return VBERROR_SUCCESS;
}

static void print_string(const char *str)
{
	int str_len = strlen(str);
	while (str_len--) {
		if (*str == '\n')
			video_console_putchar('\r');
		video_console_putchar(*str++);
	}
}

static void print_string_newline(const char *str)
{
	print_string(str);
	print_string("\n");
}

void print_on_center(const char *msg)
{
	unsigned int rows, cols;
	video_get_rows_cols(&rows, &cols);
	video_console_set_cursor((cols - strlen(msg)) / 2, rows / 2);
	print_string(msg);
}

VbError_t VbExDisplayScreen(uint32_t screen_type, VbNvContext *vnc,
                            uint32_t locale)
{
	unsigned int rows, cols;
	uint32_t default_boot = 0;
	uint32_t boot_signed_only = 0;
	uint32_t boot_usb = 0;
	uint32_t boot_legacy = 0;
	const char *fw_id;
	int fw_index;
	void *blob = NULL;
	int size = 0;
	char *msg;

	/*
	 * Show the debug messages for development. It is a backup method
	 * when GBB does not contain a full set of bitmaps.
	 */
	switch (screen_type) {
	case VB_SCREEN_BLANK:
		// clear the screen
		video_console_clear();
		break;
	case VB_SCREEN_DEVELOPER_WARNING:
		video_console_clear();
		video_console_set_cursor(0, 0);

		if (vnc != NULL) {
			VbNvGet(vnc, VBNV_DEV_BOOT_SIGNED_ONLY,
				&boot_signed_only);

			VbNvGet(vnc, VBNV_DEV_BOOT_USB, &boot_usb);
			VbNvGet(vnc, VBNV_DEV_BOOT_LEGACY, &boot_legacy);

			VbNvGet(vnc, VBNV_DEV_DEFAULT_BOOT, &default_boot);
		}

		print_string(
			"Welcome to developer mode!\n\n"
			"Useful key combinations:\n"
			"- Ctrl + H: Hold developer mode\n"
			"- Ctrl + D: Boot from internal storage\n");

		if (boot_usb)
			print_string("- Ctrl + U: Boot from external media\n");

		if (boot_legacy)
			print_string("- Ctrl + L: Boot from legacy payload\n");

		print_string(
			"- Ctrl + I: Show device information\n"
			"- Space: Disable developer mode\n\n"
			"This screen is shown for 3 seconds (if not held)."
			"\n\n");

		if (vnc != NULL) {
			if (!boot_signed_only)
				print_string(
					"Warning: this device will boot kernels"
					" without verifying their signature!"
					"\n");

			if (boot_usb)
				print_string(
					"Warning: this device will boot from "
					"external media!\n");

			if (boot_legacy)
				print_string(
					"Warning: this device will boot legacy "
					"payloads!\n");

			if (!boot_signed_only || boot_usb)
				print_string("\n");

			print_string("Default boot medium: ");

			switch (default_boot) {
				case VBNV_DEV_DEFAULT_BOOT_DISK:
					print_string("internal storage");
					break;
				case VBNV_DEV_DEFAULT_BOOT_USB:
					print_string("external media");
					break;
				case VBNV_DEV_DEFAULT_BOOT_LEGACY:
					print_string("legacy payload");
					break;
				default:
					print_string("unknown");
					break;
			}

			print_string("\n");
		}

		find_common_params(&blob, &size);

		if (blob != NULL) {
			VbSharedDataHeader *vdat = (VbSharedDataHeader *) blob;
			fw_index = get_active_fw_index(vdat);
			fw_id = get_fw_id(fw_index);

			if (fw_id == NULL)
				fw_id = "NOT FOUND";

			print_string("Active firmware id: ");
			print_string(fw_id);

			switch (fw_index) {
				case VDAT_RW_A:
					print_string(" (RW A)\n");
					break;
				case VDAT_RW_B:
					print_string(" (RW A)\n");
					break;
				case VDAT_RO:
					print_string(" (RO)\n");
					break;
				default:
					print_string(" (UNKNOWN)\n");
					break;
			}
		}
		break;
	case VB_SCREEN_DEVELOPER_EGG:
		video_console_clear();
		print_on_center("Free as in Freedom!");
		break;
	case VB_SCREEN_RECOVERY_REMOVE:
		video_console_clear();
		print_on_center(
			"Please remove any external media before accessing "
			"recovery screen.");
		break;
	case VB_SCREEN_RECOVERY_INSERT:
	case VB_SCREEN_RECOVERY_NO_GOOD:
		video_console_clear();
		print_string(
			"Welcome to recovery mode!\n\n"
			"Useful key combinations:\n"
			"- Ctrl + D: Enable developer mode (if possible)\n\n");

		if (screen_type == VB_SCREEN_RECOVERY_NO_GOOD)
			print_on_center(
				"Invalid recovery media, please instert a "
				"valid one.");
		else
			print_on_center(
				"Please insert an external recovery media.");
		break;
	case VB_SCREEN_RECOVERY_TO_DEV:
		video_console_clear();
		video_get_rows_cols(&rows, &cols);

		video_console_set_cursor(0, 0);

		print_string(
			"Enabling developer mode will allow booting unsigned "
			"kernels and booting from external media (when enabled "
			"with crossystem).\n\n"
			"Developer mode can be disabled via the developer mode "
			"screen.");

		msg = "Developer mode will be enabled.";
		video_console_set_cursor((cols - strlen(msg)) / 2, rows / 2);
		print_string(msg);

		msg = "Press enter to confirm or escape to go back.";
		video_console_set_cursor((cols - strlen(msg)) / 2,
					 rows / 2 + 2);
		print_string(msg);
		break;
	case VB_SCREEN_DEVELOPER_TO_NORM:
		video_console_clear();
		video_get_rows_cols(&rows, &cols);

		video_console_set_cursor(0, 0);

		print_string(
			"Disabling developer mode will restrict boot to signed "
			"kernels stored on internal memory only.\n\n"
			"Developer mode can be enabled again via the recovery "
			"mode screen.");

		msg = "Developer mode will be disabled.";
		video_console_set_cursor((cols - strlen(msg)) / 2, rows / 2);
		print_string(msg);

		msg = "Press enter to confirm or escape to go back.";
		video_console_set_cursor((cols - strlen(msg)) / 2,
					 rows / 2 + 2);
		print_string(msg);
		break;
	case VB_SCREEN_WAIT:
		video_console_clear();
		print_on_center("Waiting for EC update...");
		break;
	case VB_SCREEN_TO_NORM_CONFIRMED:
		video_console_clear();
		print_on_center("Disabling developer mode.");
		break;
	case VB_SCREEN_OS_BROKEN:
		video_console_clear();
		print_on_center(
			"Something went wrong and the device cannot boot.\n"
			"Press Escape + Refresh + Power to access recovery.");
		break;
	default:
		printf("Not a valid screen type: %d.\n", screen_type);
		return VBERROR_INVALID_SCREEN_INDEX;
	}

	return VBERROR_SUCCESS;
}

VbError_t VbExDisplayImage(uint32_t x, uint32_t y,
			   void *buffer, uint32_t buffersize)
{
	if (dc_corebootfb_draw_bitmap(x, y, buffer))
		return VBERROR_UNKNOWN;

	return VBERROR_SUCCESS;
}

VbError_t VbExDisplaySetDimension(uint32_t width, uint32_t height)
{
	// TODO(hungte) Shift or scale images if width/height is not equal to
	// lib_sysinfo.framebuffer->{x,y}_resolution.
	return VBERROR_SUCCESS;
}

VbError_t VbExDisplayDebugInfo(const char *info_str)
{
	video_console_set_cursor(0, 0);
	print_string(info_str);

	print_string("read-only firmware id: ");
	print_string_newline(get_ro_fw_id());

	print_string("active firmware id: ");
	const char *id = get_active_fw_id();
	if (id == NULL)
		id = "NOT FOUND";
	print_string_newline(id);

	return VBERROR_SUCCESS;
}

VbError_t VbExGetLocalizationCount(uint32_t *count)
{
	*count = vboot_get_locale_count();
	return VBERROR_SUCCESS;
}
