/*
 * Copyright (c) 2012, 2013, 2014, 2015, 2016, 2021 Jonas 'Sortie' Termansen.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * lfbtextbuffer.cpp
 * An indexable text buffer rendered to a graphical linear frame buffer.
 */

#include <string.h>

#include <sortix/vga.h>

#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/textbuffer.h>
#include <sortix/kernel/thread.h>

#include "vga.h"
#include "lfbtextbuffer.h"

namespace Sortix {

static uint32_t boldify(uint32_t color)
{
	int b = color >>  0 & 0xFF;
	int g = color >>  8 & 0xFF;
	int r = color >> 16 & 0xFF;
	b += 63;
	if ( 255 < b )
		b = 255;
	g += 63;
	if ( 255 < g )
		g = 255;
	r += 63;
	if ( 255 < r )
		r = 255;
	return (uint32_t) b << 0 | (uint32_t) g << 8 | (uint32_t) r << 16;
}

static void LFBTextBuffer__RenderThread(void* user)
{
	((LFBTextBuffer*) user)->RenderThread();
}

LFBTextBuffer* CreateLFBTextBuffer(uint8_t* lfb, uint32_t lfbformat,
                                   size_t xres, size_t yres, size_t scansize)
{
	const size_t QUEUE_LENGTH = 1024;
	size_t columns = xres / (VGA_FONT_WIDTH + 1);
	size_t rows = yres / VGA_FONT_HEIGHT;
	size_t fontsize = VGA_FONT_CHARSIZE * VGA_FONT_NUMCHARS;
	uint8_t* backbuf;
	uint8_t* font;
	TextChar* chars;
	TextBufferCmd* queue;
	LFBTextBuffer* ret;

	Process* kernel_process = Scheduler::GetKernelProcess();

	if ( !(backbuf = new uint8_t[yres * scansize]) )
		goto cleanup_done;
	if ( !(font = new uint8_t[fontsize]) )
		goto cleanup_backbuf;
	if ( !(chars = new TextChar[columns * rows]) )
		goto cleanup_font;
	if ( !(queue = new TextBufferCmd[QUEUE_LENGTH]) )
		goto cleanup_chars;
	if ( !(ret = new LFBTextBuffer) )
		goto cleanup_queue;

	memcpy(font, VGA::GetFont(), fontsize);
	ret->execute_lock = KTHREAD_MUTEX_INITIALIZER;
	ret->queue_lock = KTHREAD_MUTEX_INITIALIZER;
	ret->queue_not_full = KTHREAD_COND_INITIALIZER;
	ret->queue_not_empty = KTHREAD_COND_INITIALIZER;
	ret->queue_exit = KTHREAD_COND_INITIALIZER;
	ret->queue_sync = KTHREAD_COND_INITIALIZER;
	ret->queue_paused = KTHREAD_COND_INITIALIZER;
	ret->queue_resume = KTHREAD_COND_INITIALIZER;
	ret->queue = queue;
	ret->queue_length = QUEUE_LENGTH;
	ret->queue_offset = 0;
	ret->queue_used = 0;
	ret->queue_is_paused = false;
	ret->queue_thread = false;
	ret->lfb = lfb;
	ret->backbuf = backbuf;
	ret->lfbformat = lfbformat;
	ret->bytes_per_pixel = (lfbformat + 7) / 8;
	ret->pixelsx = xres;
	ret->pixelsy = yres;
	ret->scansize = scansize;
	ret->columns = columns;
	ret->rows = rows;
	ret->font = font;
	memset(chars, 0, sizeof(chars[0]) * columns * rows);
	ret->chars = chars;
	ret->cursorenabled = true;
	ret->cursorpos = TextPos(0, 0);
	ret->emergency_state = false;
	ret->invalidated = false;
	ret->need_clear = true;
	ret->exit_after_pause = false;

	if ( !kernel_process )
		return ret;

	ret->queue_thread = true; // Visible to new thread.
	if ( !RunKernelThread(kernel_process, LFBTextBuffer__RenderThread, ret,
	                      "console") )
		ret->queue_thread = false;

	return ret;

cleanup_queue:
	delete[] queue;
cleanup_chars:
	delete[] chars;
cleanup_font:
	delete[] font;
cleanup_backbuf:
	delete[] backbuf;
cleanup_done:
	return NULL;
}

void LFBTextBuffer::SpawnThreads()
{
	if ( queue_thread )
		return;
	Process* kernel_process = Scheduler::GetKernelProcess();
	queue_thread = true; // Visible to new thread.
	if ( !RunKernelThread(kernel_process, LFBTextBuffer__RenderThread, this,
	                      "console") )
		queue_thread = false;
}

LFBTextBuffer::LFBTextBuffer()
{
}

LFBTextBuffer::~LFBTextBuffer()
{
	if ( queue_thread )
	{
		kthread_mutex_lock(&queue_lock);
		if ( queue_is_paused )
		{
			queue_is_paused = false;
			exit_after_pause = true;
			kthread_cond_signal(&queue_resume);
		}
		else
		{
			TextBufferCmd cmd;
			cmd.type = TEXTBUFCMD_EXIT;
			kthread_mutex_unlock(&queue_lock);
			IssueCommand(&cmd);
			kthread_mutex_lock(&queue_lock);
		}
		while ( queue_thread )
			kthread_cond_wait(&queue_exit, &queue_lock);
		kthread_mutex_unlock(&queue_lock);
	}
	delete[] backbuf;
	delete[] font;
	delete[] chars;
	delete[] queue;
}

size_t LFBTextBuffer::Width()
{
	return columns;
}

size_t LFBTextBuffer::Height()
{
	return rows;
}

bool LFBTextBuffer::UsablePosition(TextPos pos) const
{
	return pos.x < columns && pos.y < rows;
}

TextPos LFBTextBuffer::CropPosition(TextPos pos) const
{
	if ( columns <= pos.x )
		pos.x = columns - 1;
	if ( rows <= pos.y )
		pos.y = rows -1;
	return pos;
}

TextPos LFBTextBuffer::AddToPosition(TextPos pos, size_t count)
{
	size_t index = pos.y * columns + pos.x + count;
	TextPos ret(index % columns, index / columns);
	return CropPosition(ret);
}

void LFBTextBuffer::RenderChar(TextChar textchar, size_t posx, size_t posy)
{
	if ( columns <= posx || rows <= posy )
		return;
	// TODO: Support other resolutions.
	if ( lfbformat != 24 && lfbformat != 32 )
		return;
	// TODO: Support other font sizes.
	if ( VGA_FONT_WIDTH != 8UL )
		return;
	bool drawcursor = cursorenabled && posx == cursorpos.x && posy == cursorpos.y;
	uint32_t fgcolor = textchar.fg;
	uint32_t bgcolor = textchar.bg;
	if ( textchar.attr & ATTR_BOLD )
		fgcolor = boldify(fgcolor);
	int remap = VGA::MapWideToVGAFont(textchar.c);
	const uint8_t* charfont = VGA::GetCharacterFont(font, remap);
	size_t pixelyoff = rows * VGA_FONT_HEIGHT;
	size_t pixelxoff = posx * (VGA_FONT_WIDTH+1);
	for ( size_t y = 0; y < VGA_FONT_HEIGHT; y++ )
	{
		size_t pixely = posy * VGA_FONT_HEIGHT + y;
		uint8_t linebitmap = charfont[y];
		uint8_t* line = (uint8_t*) (lfb + pixely * scansize);
		size_t bytesxoff = bytes_per_pixel * pixelxoff;
		for ( size_t x = 0; x < VGA_FONT_WIDTH; x++ )
		{
			uint32_t color = linebitmap & 1 << (7-x) ? fgcolor : bgcolor;
			line[bytesxoff++] = color >> 0;
			line[bytesxoff++] = color >> 8;
			line[bytesxoff++] = color >> 16;
			if ( lfbformat == 32 )
				line[bytesxoff++] = color >> 24;
		}
		uint32_t lastcolor = bgcolor;
		if ( 0xB0 <= remap && remap <= 0xDF && (linebitmap & 1) )
			lastcolor = fgcolor;
		line[bytesxoff++] = lastcolor >> 0;
		line[bytesxoff++] = lastcolor >> 8;
		line[bytesxoff++] = lastcolor >> 16;
		if ( lfbformat == 32 )
			line[bytesxoff++] = lastcolor >> 24;
		if ( unlikely(posx + 1 == columns) )
		{
			for ( size_t x = pixelxoff + VGA_FONT_WIDTH + 1; x < pixelsx; x++ )
			{
				line[bytesxoff++] = bgcolor >> 0;
				line[bytesxoff++] = bgcolor >> 8;
				line[bytesxoff++] = bgcolor >> 16;
				if ( lfbformat == 32 )
					line[bytesxoff++] = bgcolor >> 24;
			}
		}
	}
	if ( unlikely(posy + 1 == rows) )
	{
		size_t width = VGA_FONT_WIDTH + 1;
		if ( unlikely(posx + 1 == columns) )
			width = pixelsx - pixelxoff;
		for ( size_t y = pixelyoff; y < pixelsy; y++ )
		{
			size_t bytesxoff = bytes_per_pixel * pixelxoff;
			uint8_t* line = (uint8_t*) (lfb + y * scansize);
			for ( size_t x = 0; x < width; x++ )
			{
				line[bytesxoff++] = bgcolor >> 0;
				line[bytesxoff++] = bgcolor >> 8;
				line[bytesxoff++] = bgcolor >> 16;
				if ( lfbformat == 32 )
					line[bytesxoff++] = bgcolor >> 24;
			}
		}
	}
	if ( likely(!drawcursor) && !(textchar.attr & ATTR_UNDERLINE) )
		return;
	size_t underlines = VGA_FONT_HEIGHT - (!drawcursor ? 1 : 0);
	for ( size_t y = VGA_FONT_HEIGHT - 2; y < underlines; y++ )
	{
		size_t pixely = posy * VGA_FONT_HEIGHT + y;
		uint8_t* line = (uint8_t*) (lfb + pixely * scansize);
		size_t bytesxoff = bytes_per_pixel * pixelxoff;
		for ( size_t x = 0; x < VGA_FONT_WIDTH+1; x++ )
		{
			line[bytesxoff++] = fgcolor >> 0;
			line[bytesxoff++] = fgcolor >> 8;
			line[bytesxoff++] = fgcolor >> 16;
			if ( lfbformat == 32 )
				line[bytesxoff++] = fgcolor >> 24;
		}
	}
}

void LFBTextBuffer::RenderCharAt(TextPos pos)
{
	RenderChar(chars[pos.y * columns + pos.x], pos.x, pos.y);
}

void LFBTextBuffer::RenderRegion(size_t c1, size_t r1, size_t c2, size_t r2)
{
	for ( size_t y = r1; y <= r2; y++ )
		for ( size_t x = c1; x <= c2; x++ )
			RenderChar(chars[y * columns + x], x, y);
}

void LFBTextBuffer::RenderRange(TextPos from, TextPos to)
{
	from = CropPosition(from);
	to = CropPosition(to);
	uint8_t* orig_lfb = lfb;
	bool backbuffered = from.y != to.y;
	if ( backbuffered )
	{
		lfb = backbuf;
		from.x = 0;
		to.x = columns - 1;
	}
	TextPos i = from;
	RenderChar(chars[i.y * columns + i.x], i.x, i.y);
	while ( !(i.x==to.x && i.y==to.y) )
	{
		i = AddToPosition(i, 1);
		RenderChar(chars[i.y * columns + i.x], i.x, i.y);
	}
	if ( backbuffered )
	{
		lfb = orig_lfb;
		size_t scanline_start = from.y * VGA_FONT_HEIGHT;
		size_t scanline_end = ((to.y+1) * VGA_FONT_HEIGHT) - 1;
		if ( to.y + 1 == rows )
			scanline_end = pixelsy - 1;
		for ( size_t sc = scanline_start; sc <= scanline_end; sc++ )
		{
			size_t offset = sc * scansize;
			memcpy(lfb + offset, backbuf + offset, pixelsx * bytes_per_pixel);
		}
	}
}

void LFBTextBuffer::IssueCommand(TextBufferCmd* cmd, bool already_locked)
{
	if ( invalidated )
	{
		invalidated = false;
		TextBufferCmd newcmd;
		newcmd.type = TEXTBUFCMD_REDRAW;
		IssueCommand(&newcmd, already_locked);
	}
	if ( !queue_thread || emergency_state )
	{
		bool exit_requested = false;
		bool sync_requested = false;
		bool pause_requested = false;
		TextPos render_from(columns - 1, rows - 1);
		TextPos render_to(0, 0);
		ExecuteCommand(cmd, exit_requested, sync_requested, pause_requested, render_from, render_to);
		if ( !IsTextPosBeforeTextPos(render_to, render_from) )
			RenderRange(render_from, render_to);
		return;
	}
	if ( !already_locked )
		kthread_mutex_lock(&queue_lock);
	while ( queue_used == queue_length )
		kthread_cond_wait(&queue_not_full, &queue_lock);
	if ( !queue_used )
		kthread_cond_signal(&queue_not_empty);
	queue[(queue_offset + queue_used++) % queue_length] = *cmd;
	if ( !already_locked )
		kthread_mutex_unlock(&queue_lock);
}

bool LFBTextBuffer::StopRendering()
{
	if ( !queue_thread || emergency_state )
		return false;
	TextBufferCmd cmd;
	cmd.type = TEXTBUFCMD_PAUSE;
	ScopedLock lock(&queue_lock);
	if ( queue_is_paused )
		return false;
	IssueCommand(&cmd, true);
	while ( !queue_is_paused )
		kthread_cond_wait(&queue_paused, &queue_lock);
	return true;
}

void LFBTextBuffer::ResumeRendering()
{
	if ( !queue_thread || emergency_state )
		return;
	ScopedLock lock(&queue_lock);
	if ( !queue_is_paused )
		return;
	queue_is_paused = false;
	kthread_cond_signal(&queue_resume);
}

TextChar LFBTextBuffer::GetChar(TextPos pos)
{
	if ( UsablePosition(pos) )
	{
		bool was_rendering = StopRendering();
		TextChar ret = chars[pos.y * columns + pos.x];
		if ( was_rendering )
			ResumeRendering();
		return ret;
	}
	return {0, 0, 0, 0, 0};
}

void LFBTextBuffer::SetChar(TextPos pos, TextChar c)
{
	if ( !UsablePosition(pos) )
		return;
	TextBufferCmd cmd;
	cmd.type = TEXTBUFCMD_CHAR;
	cmd.x = pos.x;
	cmd.y = pos.y;
	cmd.c = c;
	IssueCommand(&cmd);
}

bool LFBTextBuffer::GetCursorEnabled()
{
	bool was_rendering = StopRendering();
	bool ret = cursorenabled;
	if ( was_rendering )
		ResumeRendering();
	return ret;
}

void LFBTextBuffer::SetCursorEnabled(bool enablecursor)
{
	TextBufferCmd cmd;
	cmd.type = TEXTBUFCMD_CURSOR_SET_ENABLED;
	cmd.b = enablecursor;
	IssueCommand(&cmd);
}

TextPos LFBTextBuffer::GetCursorPos()
{
	bool was_rendering = StopRendering();
	TextPos ret = cursorpos;
	if ( was_rendering )
		ResumeRendering();
	return ret;
}

void LFBTextBuffer::SetCursorPos(TextPos newcursorpos)
{
	TextBufferCmd cmd;
	cmd.type = TEXTBUFCMD_CURSOR_MOVE;
	cmd.x = newcursorpos.x;
	cmd.y = newcursorpos.y;
	IssueCommand(&cmd);
}

void LFBTextBuffer::Invalidate()
{
	invalidated = true;
}

size_t LFBTextBuffer::OffsetOfPos(TextPos pos) const
{
	return pos.y * columns + pos.x;
}

void LFBTextBuffer::Scroll(ssize_t off, TextChar fillwith)
{
	if ( !off )
		return;
	TextBufferCmd cmd;
	cmd.type = TEXTBUFCMD_SCROLL;
	cmd.scroll_offset = off;
	cmd.c = fillwith;
	IssueCommand(&cmd);
}

void LFBTextBuffer::Move(TextPos to, TextPos from, size_t numchars)
{
	to = CropPosition(to);
	from = CropPosition(from);
	TextBufferCmd cmd;
	cmd.type = TEXTBUFCMD_MOVE;
	cmd.to_x = to.x;
	cmd.to_y = to.y;
	cmd.from_x = from.x;
	cmd.from_y = from.y;
	cmd.val = numchars;
	IssueCommand(&cmd);
}

void LFBTextBuffer::Fill(TextPos from, TextPos to, TextChar fillwith)
{
	from = CropPosition(from);
	to = CropPosition(to);
	TextBufferCmd cmd;
	cmd.type = TEXTBUFCMD_FILL;
	cmd.from_x = from.x;
	cmd.from_y = from.y;
	cmd.to_x = to.x;
	cmd.to_y = to.y;
	cmd.c = fillwith;
	IssueCommand(&cmd);
}

void LFBTextBuffer::DoScroll(ssize_t off, TextChar entry)
{
	bool neg = 0 < off;
	size_t absoff = off < 0 ? -off : off;
	if ( rows < absoff )
		absoff = rows;
	TextPos scrollfrom = neg ? TextPos{0, absoff} : TextPos{0, 0};
	TextPos scrollto = neg ? TextPos{0, 0} : TextPos{0, absoff};
	TextPos fillfrom = neg ? TextPos{0, rows-absoff} : TextPos{0, 0};
	TextPos fillto = neg ? TextPos{columns-1, rows-1} : TextPos{columns-1, absoff-1};
	size_t scrollchars = columns * (rows-absoff);
	DoMove(scrollto, scrollfrom, scrollchars);
	DoFill(fillfrom, fillto, entry);
}

void LFBTextBuffer::DoMove(TextPos to, TextPos from, size_t numchars)
{
	size_t dest = OffsetOfPos(to);
	size_t src = OffsetOfPos(from);
	if ( dest < src )
		for ( size_t i = 0; i < numchars; i++ )
			chars[dest + i] = chars[src + i];
	else if ( src < dest )
		for ( size_t i = 0; i < numchars; i++ )
			chars[dest + numchars-1 - i] = chars[src + numchars-1 - i];
}

void LFBTextBuffer::DoFill(TextPos from, TextPos to, TextChar fillwith)
{
	size_t start = OffsetOfPos(from);
	size_t end = OffsetOfPos(to);
	for ( size_t i = start; i <= end; i++ )
		chars[i] = fillwith;
}

bool LFBTextBuffer::IsCommandIdempotent(const TextBufferCmd* cmd) const
{
	switch ( cmd->type )
	{
		case TEXTBUFCMD_EXIT: return true;
		case TEXTBUFCMD_SYNC: return true;
		case TEXTBUFCMD_PAUSE: return true;
		case TEXTBUFCMD_CHAR: return true;
		case TEXTBUFCMD_CURSOR_SET_ENABLED: return true;
		case TEXTBUFCMD_CURSOR_MOVE: return true;
		case TEXTBUFCMD_MOVE: return false;
		case TEXTBUFCMD_FILL: return true;
		case TEXTBUFCMD_SCROLL: return false;
		case TEXTBUFCMD_REDRAW: return true;
		default: return false;
	}
}

void LFBTextBuffer::ExecuteCommand(TextBufferCmd* cmd,
                                   bool& exit_requested,
                                   bool& sync_requested,
                                   bool& pause_requested,
                                   TextPos& render_from,
                                   TextPos& render_to)
{
	switch ( cmd->type )
	{
		case TEXTBUFCMD_EXIT:
			exit_requested = true;
			break;
		case TEXTBUFCMD_SYNC:
			sync_requested = true;
			break;
		case TEXTBUFCMD_PAUSE:
			pause_requested = true;
			break;
		case TEXTBUFCMD_CHAR:
		{
			TextPos pos(cmd->x, cmd->y);
			chars[pos.y * columns + pos.x] = cmd->c;
			if ( IsTextPosBeforeTextPos(pos, render_from) )
				render_from = pos;
			if ( IsTextPosAfterTextPos(pos, render_to) )
				render_to = pos;
		} break;
		case TEXTBUFCMD_CURSOR_SET_ENABLED:
			if ( cmd->b != cursorenabled )
			{
				cursorenabled = cmd->b;
				if ( IsTextPosBeforeTextPos(cursorpos, render_from) )
					render_from = cursorpos;
				if ( IsTextPosAfterTextPos(cursorpos, render_to) )
					render_to = cursorpos;
			}
			break;
		case TEXTBUFCMD_CURSOR_MOVE:
		{
			TextPos pos(cmd->x, cmd->y);
			if ( cursorpos.x != pos.x || cursorpos.y != pos.y )
			{
				if ( IsTextPosBeforeTextPos(cursorpos, render_from) )
					render_from = cursorpos;
				if ( IsTextPosAfterTextPos(cursorpos, render_to) )
					render_to = cursorpos;
				cursorpos = pos;
				if ( IsTextPosBeforeTextPos(cursorpos, render_from) )
					render_from = cursorpos;
				if ( IsTextPosAfterTextPos(cursorpos, render_to) )
					render_to = cursorpos;
			}
		} break;
		case TEXTBUFCMD_MOVE:
		{
			TextPos to(cmd->to_x, cmd->to_y);
			TextPos from(cmd->from_x, cmd->from_y);
			size_t numchars = cmd->val;
			DoMove(to, from, numchars);
			TextPos toend = AddToPosition(to, numchars);
			if ( IsTextPosBeforeTextPos(to, render_from) )
				render_from = to;
			if ( IsTextPosAfterTextPos(toend, render_to) )
				render_to = toend;
		} break;
		case TEXTBUFCMD_FILL:
		{
			TextPos from(cmd->from_x, cmd->from_y);
			TextPos to(cmd->to_x, cmd->to_y);
			DoFill(from, to, cmd->c);
			if ( IsTextPosBeforeTextPos(from, render_from) )
				render_from = from;
			if ( IsTextPosAfterTextPos(to, render_to) )
				render_to = to;
		} break;
		case TEXTBUFCMD_SCROLL:
		{
			ssize_t off = cmd->scroll_offset;
			DoScroll(off, cmd->c);
			render_from = {0, 0};
			render_to = {columns-1, rows-1};
		} break;
		case TEXTBUFCMD_REDRAW:
		{
			render_from = {0, 0};
			render_to = {columns-1, rows-1};
		} break;
	}
}

void LFBTextBuffer::RenderThread()
{
	queue_is_paused = false;
	size_t offset = 0;
	size_t amount = 0;
	bool exit_requested = false;
	bool sync_requested = false;
	bool pause_requested = false;

	while ( true )
	{
		kthread_mutex_lock(&queue_lock);

		if ( queue_used == queue_length && amount )
			kthread_cond_signal(&queue_not_full);

		queue_used -= amount;
		queue_offset = (queue_offset + amount) % queue_length;

		if ( !queue_used )
		{
			if ( exit_requested )
			{
				queue_thread = false;
				kthread_cond_signal(&queue_exit);
				kthread_mutex_unlock(&queue_lock);
				return;
			}

			if ( sync_requested )
			{
				kthread_cond_signal(&queue_sync);
				sync_requested = false;
			}

			if ( pause_requested )
			{
				queue_is_paused = true;
				kthread_cond_signal(&queue_paused);
				while ( queue_is_paused )
					kthread_cond_wait(&queue_resume, &queue_lock);
				pause_requested = false;
				if ( exit_after_pause )
				{
					queue_thread = false;
					kthread_cond_signal(&queue_exit);
					kthread_mutex_unlock(&queue_lock);
					return;
				}
			}
		}

		while ( !queue_used )
			kthread_cond_wait(&queue_not_empty, &queue_lock);

		amount = queue_used;
		offset = queue_offset;

		kthread_mutex_unlock(&queue_lock);

		execute_amount = amount;

		kthread_mutex_lock(&execute_lock);

		TextPos render_from(columns - 1, rows - 1);
		TextPos render_to(0, 0);

		for ( size_t i = 0; i < amount; i++ )
		{
			TextBufferCmd* cmd = &queue[(offset + i) % queue_length];
			ExecuteCommand(cmd, exit_requested, sync_requested, pause_requested, render_from, render_to);
		}

		kthread_mutex_unlock(&execute_lock);

		if ( !IsTextPosBeforeTextPos(render_to, render_from) )
			RenderRange(render_from, render_to);
	}
}

bool LFBTextBuffer::EmergencyIsImpaired()
{
	return !emergency_state;
}

bool LFBTextBuffer::EmergencyRecoup()
{
	if ( !emergency_state )
		emergency_state = true;

	if ( !kthread_mutex_trylock(&queue_lock) )
		return false;
	kthread_mutex_unlock(&queue_lock);

	if ( !kthread_mutex_trylock(&execute_lock) )
	{
		for ( size_t i = 0; i < execute_amount; i++ )
		{
			TextBufferCmd* cmd = &queue[(queue_offset + i) % queue_length];
			if ( !IsCommandIdempotent(cmd) )
				return false;
		}
	}
	else
		kthread_mutex_unlock(&execute_lock);

	TextPos render_from(0, 0);
	TextPos render_to(columns - 1, rows - 1);

	for ( size_t i = 0; i < queue_used; i++ )
	{
		bool exit_requested = false;
		bool sync_requested = false;
		bool pause_requested = false;
		TextBufferCmd* cmd = &queue[(queue_offset + i) % queue_length];
		ExecuteCommand(cmd, exit_requested, sync_requested, pause_requested,
		               render_from, render_to);
	}

	queue_used = 0;
	queue_offset = 0;

	RenderRange(render_from, render_to);

	return true;
}

void LFBTextBuffer::EmergencyReset()
{
	// TODO: Reset everything here!

	Fill(TextPos{0, 0}, TextPos{columns-1, rows-1}, TextChar{0, 0, 0, 0, 0});
	SetCursorPos(TextPos{0, 0});
}

void LFBTextBuffer::Resume()
{
	if ( need_clear )
	{
		for ( size_t y = 0; y < pixelsy; y++ )
			memset(lfb + scansize * y, 0, bytes_per_pixel * pixelsx);
		need_clear = false;
	}
	ResumeRendering();
}

void LFBTextBuffer::Pause()
{
	StopRendering();
}

} // namespace Sortix
