/*
 * (C) Copyright 2001-2014
 * DENX Software Engineering -- wd@denx.de
 * Compulab Ltd - http://compulab.co.il/
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <lcd.h>
#include <video_font.h>		/* Get font data, width and height */

#define CONSOLE_ROW_SIZE	(VIDEO_FONT_HEIGHT * lcd_line_length)
#define CONSOLE_ROW_FIRST	lcd_console_address
#define CONSOLE_SIZE		(CONSOLE_ROW_SIZE * console_rows)

static short console_curr_col;
static short console_curr_row;
static short console_cols;
static short console_rows;
static void *lcd_console_address;

void lcd_init_console(void *address, int rows, int cols)
{
	console_curr_col = 0;
	console_curr_row = 0;
	console_cols = cols;
	console_rows = rows;
	lcd_console_address = address;
}

void lcd_set_col(short col)
{
	console_curr_col = col;
}

void lcd_set_row(short row)
{
	console_curr_row = row;
}

void lcd_position_cursor(unsigned col, unsigned row)
{
	console_curr_col = min_t(short, col, console_cols - 1);
	console_curr_row = min_t(short, row, console_rows - 1);
}

int lcd_get_screen_rows(void)
{
	return console_rows;
}

int lcd_get_screen_columns(void)
{
	return console_cols;
}

static void lcd_drawchars(ushort x, ushort y, uchar *str, int count)
{
	uchar *dest;
	ushort row;
	int fg_color, bg_color;

#if LCD_BPP == LCD_MONOCHROME
	ushort off  = x * (1 << LCD_BPP) % 8;
#endif

	dest = (uchar *)(lcd_console_address +
			 y * lcd_line_length + x * NBITS(LCD_BPP) / 8);

	for (row = 0; row < VIDEO_FONT_HEIGHT; ++row, dest += lcd_line_length) {
		uchar *s = str;
		int i;
#if LCD_BPP == LCD_COLOR16
		ushort *d = (ushort *)dest;
#elif LCD_BPP == LCD_COLOR32
		u32 *d = (u32 *)dest;
#else
		uchar *d = dest;
#endif

		fg_color = lcd_getfgcolor();
		bg_color = lcd_getbgcolor();

#if LCD_BPP == LCD_MONOCHROME
		uchar rest = *d & -(1 << (8 - off));
		uchar sym;
#endif
		for (i = 0; i < count; ++i) {
			uchar c, bits;

			c = *s++;
			bits = video_fontdata[c * VIDEO_FONT_HEIGHT + row];

#if LCD_BPP == LCD_MONOCHROME
			sym  = (COLOR_MASK(fg_color) & bits) |
				(COLOR_MASK(bg_color) & ~bits);

			*d++ = rest | (sym >> off);
			rest = sym << (8-off);
#else /* LCD_BPP == LCD_COLOR8 or LCD_COLOR16 or LCD_COLOR32 */
			for (c = 0; c < 8; ++c) {
				*d++ = (bits & 0x80) ? fg_color : bg_color;
				bits <<= 1;
			}
#endif
		}
#if LCD_BPP == LCD_MONOCHROME
		*d  = rest | (*d & ((1 << (8 - off)) - 1));
#endif
	}
}

static inline void lcd_putc_xy(ushort x, ushort y, uchar c)
{
	lcd_drawchars(x, y, &c, 1);
}

static void console_scrollup(void)
{
	const int rows = CONFIG_CONSOLE_SCROLL_LINES;
	int bg_color = lcd_getbgcolor();

	/* Copy up rows ignoring those that will be overwritten */
	memcpy(CONSOLE_ROW_FIRST,
	       lcd_console_address + CONSOLE_ROW_SIZE * rows,
	       CONSOLE_SIZE - CONSOLE_ROW_SIZE * rows);

	/* Clear the last rows */
#if (LCD_BPP != LCD_COLOR32)
	memset(lcd_console_address + CONSOLE_SIZE - CONSOLE_ROW_SIZE * rows,
	       COLOR_MASK(bg_color), CONSOLE_ROW_SIZE * rows);
#else
	u32 *ppix = lcd_console_address +
		    CONSOLE_SIZE - CONSOLE_ROW_SIZE * rows;
	u32 i;
	for (i = 0;
	    i < (CONSOLE_ROW_SIZE * rows) / NBYTES(panel_info.vl_bpix);
	    i++) {
		*ppix++ = COLOR_MASK(bg_color);
	}
#endif
	lcd_sync();
	console_curr_row -= rows;
}

static inline void console_back(void)
{
	if (--console_curr_col < 0) {
		console_curr_col = console_cols - 1;
		if (--console_curr_row < 0)
			console_curr_row = 0;
	}

	lcd_putc_xy(console_curr_col * VIDEO_FONT_WIDTH,
		    console_curr_row * VIDEO_FONT_HEIGHT, ' ');
}

static inline void console_newline(void)
{
	console_curr_col = 0;

	/* Check if we need to scroll the terminal */
	if (++console_curr_row >= console_rows)
		console_scrollup();
	else
		lcd_sync();
}

void lcd_putc(const char c)
{
	if (!lcd_is_enabled) {
		serial_putc(c);

		return;
	}

	switch (c) {
	case '\r':
		console_curr_col = 0;

		return;
	case '\n':
		console_newline();

		return;
	case '\t':	/* Tab (8 chars alignment) */
		console_curr_col +=  8;
		console_curr_col &= ~7;

		if (console_curr_col >= console_cols)
			console_newline();

		return;
	case '\b':
		console_back();

		return;
	default:
		lcd_putc_xy(console_curr_col * VIDEO_FONT_WIDTH,
			    console_curr_row * VIDEO_FONT_HEIGHT, c);
		if (++console_curr_col >= console_cols)
			console_newline();
	}
}

void lcd_puts(const char *s)
{
	if (!lcd_is_enabled) {
		serial_puts(s);

		return;
	}

	while (*s)
		lcd_putc(*s++);

	lcd_sync();
}

void lcd_printf(const char *fmt, ...)
{
	va_list args;
	char buf[CONFIG_SYS_PBSIZE];

	va_start(args, fmt);
	vsprintf(buf, fmt, args);
	va_end(args);

	lcd_puts(buf);
}

static int do_lcd_setcursor(cmd_tbl_t *cmdtp, int flag, int argc,
			    char *const argv[])
{
	unsigned int col, row;

	if (argc != 3)
		return CMD_RET_USAGE;

	col = simple_strtoul(argv[1], NULL, 10);
	row = simple_strtoul(argv[2], NULL, 10);
	lcd_position_cursor(col, row);

	return 0;
}

static int do_lcd_puts(cmd_tbl_t *cmdtp, int flag, int argc,
		       char *const argv[])
{
	if (argc != 2)
		return CMD_RET_USAGE;

	lcd_puts(argv[1]);

	return 0;
}

U_BOOT_CMD(
	setcurs, 3,	1,	do_lcd_setcursor,
	"set cursor position within screen",
	"    <col> <row> in character"
);

U_BOOT_CMD(
	lcdputs, 2,	1,	do_lcd_puts,
	"print string on lcd-framebuffer",
	"    <string>"
);