/*
 * Copyright (c) 2013, 2014, 2016 Jonas 'Sortie' Termansen.
 * Copyright (c) 2021 Juhani 'nortti' Krekelä.
 *
 * 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.
 *
 * command.c
 * Editor commands.
 */

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <wctype.h>

#include "command.h"
#include "cursor.h"
#include "display.h"
#include "editor.h"
#include "multibyte.h"
#include "terminal.h"

void editor_type_newline(struct editor* editor)
{
	editor->dirty = true;

	if ( editor->lines_used == editor->lines_length )
	{
		size_t new_length = editor->lines_length ? 2 * editor->lines_length : 8;
		struct line* new_lines = (struct line*)
			malloc(sizeof(struct line) * new_length);
		for ( size_t i = 0; i < editor->lines_used; i++ )
			new_lines[i] = editor->lines[i];
		free(editor->lines);
		editor->lines = new_lines;
		editor->lines_length = new_length;
	}

	for ( size_t i = editor->lines_used-1; editor->cursor_row < i; i-- )
		editor->lines[i+1] = editor->lines[i];
	editor->lines_used++;

	struct line old_line = editor->lines[editor->cursor_row];

	size_t keep_length = editor->cursor_column;
	size_t move_length = old_line.used - keep_length;

	struct line* keep_line = &editor->lines[editor->cursor_row];
	struct line* move_line = &editor->lines[editor->cursor_row+1];

	keep_line->data = (wchar_t*) malloc(sizeof(wchar_t) * keep_length);
	keep_line->used = keep_length;
	keep_line->length = keep_length;
	if ( keep_length )
		memcpy(keep_line->data, old_line.data + 0,
		       sizeof(wchar_t) * keep_length);

	move_line->data = (wchar_t*) malloc(sizeof(wchar_t) * move_length);
	move_line->used = move_length;
	move_line->length = move_length;
	if ( move_length )
		memcpy(move_line->data, old_line.data + keep_length,
		       sizeof(wchar_t) * move_length);

	editor_cursor_set(editor, editor->cursor_row+1, 0);

	free(old_line.data);
}

void editor_type_delete_selection(struct editor* editor);

void editor_type_combine_with_last(struct editor* editor)
{
	if ( !editor->cursor_row )
		return;

	editor->dirty = true;

	struct line* keep_line = &editor->lines[editor->cursor_row-1];
	struct line* gone_line = &editor->lines[editor->cursor_row];

	wchar_t* keep_line_data = keep_line->data;
	wchar_t* gone_line_data = gone_line->data;

	size_t new_length = keep_line->used + gone_line->used;
	wchar_t* new_data = (wchar_t*) malloc(sizeof(wchar_t) * new_length);

	memcpy(new_data, keep_line_data, sizeof(wchar_t) * keep_line->used);
	memcpy(new_data + keep_line->used, gone_line_data, sizeof(wchar_t) * gone_line->used);

	editor_cursor_set(editor, editor->cursor_row-1, keep_line->used);

	keep_line->data = new_data;
	keep_line->used = new_length;
	keep_line->length = new_length;

	editor->lines_used--;
	for ( size_t i = editor->cursor_row + 1; i < editor->lines_used; i++ )
		editor->lines[i] = editor->lines[i+1];

	free(keep_line_data);
	free(gone_line_data);
}

void editor_type_backspace(struct editor* editor)
{
	if ( !(editor->select_row == editor->cursor_row &&
	       editor->select_column == editor->cursor_column) )
	{
		editor_type_delete_selection(editor);
		return;
	}

	struct line* current_line = &editor->lines[editor->cursor_row];

	if ( !editor->cursor_column )
	{
		editor_type_combine_with_last(editor);
		return;
	}

	editor->dirty = true;

	current_line->used--;
	for ( size_t i = editor->cursor_column - 1; i < current_line->used; i++ )
		current_line->data[i] = current_line->data[i+1];
	editor_cursor_set(editor, editor->cursor_row, editor->cursor_column - 1);
}

void editor_type_combine_with_next(struct editor* editor)
{
	if ( editor->cursor_row + 1 == editor->lines_used )
		return;

	editor->dirty = true;

	struct line* keep_line = &editor->lines[editor->cursor_row];
	struct line* gone_line = &editor->lines[editor->cursor_row+1];

	wchar_t* keep_line_data = keep_line->data;
	wchar_t* gone_line_data = gone_line->data;

	size_t new_length = keep_line->used + gone_line->used;
	wchar_t* new_data = (wchar_t*) malloc(sizeof(wchar_t) * new_length);

	memcpy(new_data, keep_line_data, sizeof(wchar_t) * keep_line->used);
	memcpy(new_data + keep_line->used, gone_line_data, sizeof(wchar_t) * gone_line->used);

	editor_cursor_set(editor, editor->cursor_row, keep_line->used);

	keep_line->data = new_data;
	keep_line->used = new_length;
	keep_line->length = new_length;

	editor->lines_used--;
	for ( size_t i = editor->cursor_row + 1; i < editor->lines_used; i++ )
		editor->lines[i] = editor->lines[i+1];

	free(keep_line_data);
	free(gone_line_data);
}

void editor_type_delete(struct editor* editor)
{
	if ( !(editor->select_row == editor->cursor_row &&
	       editor->select_column == editor->cursor_column) )
	{
		editor_type_delete_selection(editor);
		return;
	}

	struct line* current_line = &editor->lines[editor->cursor_row];

	if ( editor->cursor_column == current_line->used )
	{
		editor_type_combine_with_next(editor);
		return;
	}

	editor->dirty = true;

	current_line->used--;
	for ( size_t i = editor->cursor_column; i < current_line->used; i++ )
		current_line->data[i] = current_line->data[i+1];
}

void editor_type_delete_selection(struct editor* editor)
{
	if ( is_row_column_lt(editor->select_row, editor->select_column,
	                      editor->cursor_row, editor->cursor_column) )
	{
		size_t tmp;
		tmp = editor->select_row;
		editor->select_row = editor->cursor_row;
		editor->cursor_row = tmp;
		tmp = editor->select_column;
		editor->select_column = editor->cursor_column;
		editor->cursor_column = tmp;
	}

	size_t desired_row = editor->cursor_row;
	size_t desired_column = editor->cursor_column;

	editor->cursor_row = editor->select_row;
	editor->cursor_column = editor->select_column;

	while ( !(editor->cursor_row == desired_row &&
	          editor->cursor_column == desired_column) )
		editor_type_backspace(editor);
}

void editor_type_exit_select_left(struct editor* editor)
{
	if ( editor_has_selection(editor) )
	{
		size_t column, row;
		row_column_smallest(editor->cursor_row, editor->cursor_column,
		                    editor->select_row, editor->select_column,
		                    &row, &column);
		editor_cursor_set(editor, row, column);
		return;
	}
}

void editor_type_exit_select_right(struct editor* editor)
{
	if ( editor_has_selection(editor) )
	{
		size_t column, row;
		row_column_biggest(editor->cursor_row, editor->cursor_column,
		                   editor->select_row, editor->select_column,
		                   &row, &column);
		editor_cursor_set(editor, row, column);
		return;
	}
}

void editor_type_left(struct editor* editor)
{
	editor_type_exit_select_left(editor);
	if ( editor->cursor_column )
		editor_cursor_set(editor, editor->cursor_row,
		                  editor->cursor_column - 1);
	else if ( editor->cursor_row )
		editor_cursor_set(editor, editor->cursor_row - 1,
		                  editor->lines[editor->cursor_row - 1].used);
}

void editor_type_select_left(struct editor* editor)
{
	if ( editor->select_column )
		editor_select_set(editor, editor->select_row,
		                  editor->select_column - 1);
	else if ( editor->select_row )
		editor_select_set(editor, editor->select_row - 1,
		                  editor->lines[editor->select_row - 1].used);
}

void editor_type_control_left(struct editor* editor)
{
	editor_type_exit_select_left(editor);
	if ( editor->cursor_column || editor->cursor_row )
		editor_type_left(editor);
	int state = 0;
	while ( editor->cursor_column || editor->cursor_row )
	{
		editor_type_left(editor);
		struct line* line = &editor->lines[editor->cursor_row];
		wchar_t wc = line->data[editor->cursor_column];
		if ( (state == 0 && !iswspace(wc)) || (state == 1 && iswspace(wc)) )
		{
			editor_type_right(editor);
			if ( ++state == 2 )
				break;
		}
	}
}

void editor_type_control_select_left(struct editor* editor)
{
	if ( editor->select_column || editor->select_row )
		editor_type_select_left(editor);
	int state = 0;
	while ( editor->select_column || editor->select_row )
	{
		editor_type_select_left(editor);
		struct line* line = &editor->lines[editor->select_row];
		wchar_t wc = line->data[editor->select_column];
		if ( (state == 0 && !iswspace(wc)) || (state == 1 && iswspace(wc)) )
		{
			editor_type_select_right(editor);
			if ( ++state == 2 )
				break;
		}
	}
}

void editor_type_right(struct editor* editor)
{
	editor_type_exit_select_right(editor);
	struct line* current_line = &editor->lines[editor->cursor_row];
	if ( editor->cursor_column != current_line->used )
		editor_cursor_set(editor, editor->cursor_row,
		                  editor->cursor_column + 1);
	else if ( editor->cursor_row + 1 != editor->lines_used )
		editor_cursor_set(editor, editor->cursor_row + 1, 0);
}

void editor_type_select_right(struct editor* editor)
{
	struct line* current_line = &editor->lines[editor->select_row];
	if ( editor->select_column != current_line->used )
		editor_select_set(editor, editor->select_row,
		                  editor->select_column + 1);
	else if ( editor->select_row + 1 != editor->lines_used )
		editor_select_set(editor, editor->select_row + 1, 0);
}

void editor_type_control_right(struct editor* editor)
{
	editor_type_exit_select_right(editor);
	int state = 0;
	while ( editor->cursor_column != editor->lines[editor->cursor_row].used ||
	        editor->cursor_row != editor->lines_used )
	{
		struct line* line = &editor->lines[editor->cursor_row];
		wchar_t wc = editor->cursor_column != line->used ?
		             line->data[editor->cursor_column] : L' ';
		if ( (state == 0 && !iswspace(wc)) || (state == 1 && iswspace(wc)) )
		{
			if ( ++state == 2 )
				break;
		}
		editor_type_right(editor);
	}
}

void editor_type_control_select_right(struct editor* editor)
{
	int state = 0;
	while ( editor->select_column != editor->lines[editor->select_row].used ||
	        editor->select_row != editor->lines_used )
	{
		struct line* line = &editor->lines[editor->select_row];
		wchar_t wc = editor->select_column != line->used ?
		             line->data[editor->select_column] : L' ';
		if ( (state == 0 && !iswspace(wc)) || (state == 1 && iswspace(wc)) )
		{
			if ( ++state == 2 )
				break;
		}
		editor_type_select_right(editor);
	}
}

void editor_type_up(struct editor* editor)
{
	editor_type_exit_select_left(editor);
	if ( !editor->cursor_row )
	{
		editor_cursor_set(editor, 0, 0);
		return;
	}
	struct line* old_line = &editor->lines[editor->cursor_row];
	struct line* new_line = &editor->lines[editor->cursor_row - 1];
	size_t old_column = editor_display_column_of_line_offset(editor, old_line, editor->cursor_column);
	size_t new_offset = editor_line_offset_of_display_column(editor, new_line, old_column);
	editor_cursor_set(editor, editor->cursor_row - 1, new_offset);
}

void editor_type_select_up(struct editor* editor)
{
	if ( !editor->select_row )
	{
		editor_select_set(editor, 0, 0);
		return;
	}
	struct line* old_line = &editor->lines[editor->select_row];
	struct line* new_line = &editor->lines[editor->select_row - 1];
	size_t old_column = editor_display_column_of_line_offset(editor, old_line, editor->select_column);
	size_t new_offset = editor_line_offset_of_display_column(editor, new_line, old_column);
	editor_select_set(editor, editor->select_row - 1, new_offset);
}

void editor_type_control_up(struct editor* editor)
{
	editor_type_exit_select_left(editor);
	if ( editor->cursor_row )
		editor_cursor_set(editor, editor->cursor_row - 1, 0);
	else
		editor_cursor_set(editor, editor->cursor_row, 0);
}

void editor_type_control_select_up(struct editor* editor)
{
	if ( editor->select_row )
		editor_select_set(editor, editor->select_row - 1, 0);
	else
		editor_select_set(editor, editor->select_row, 0);
}

void editor_type_down(struct editor* editor)
{
	editor_type_exit_select_right(editor);
	if ( editor->cursor_row + 1 == editor->lines_used )
	{
		editor_cursor_set(editor, editor->cursor_row,
		                  editor->lines[editor->cursor_row].used);
		return;
	}
	struct line* old_line = &editor->lines[editor->cursor_row];
	struct line* new_line = &editor->lines[editor->cursor_row + 1];
	size_t old_column = editor_display_column_of_line_offset(editor, old_line, editor->cursor_column);
	size_t new_offset = editor_line_offset_of_display_column(editor, new_line, old_column);
	editor_cursor_set(editor, editor->cursor_row + 1, new_offset);
}

void editor_type_select_down(struct editor* editor)
{
	if ( editor->select_row+1 == editor->lines_used )
	{
		editor_select_set(editor, editor->select_row,
		                  editor->lines[editor->select_row].used);
		return;
	}
	struct line* old_line = &editor->lines[editor->select_row];
	struct line* new_line = &editor->lines[editor->select_row + 1];
	size_t old_column = editor_display_column_of_line_offset(editor, old_line, editor->select_column);
	size_t new_offset = editor_line_offset_of_display_column(editor, new_line, old_column);
	editor_select_set(editor, editor->select_row + 1, new_offset);
}

void editor_type_control_down(struct editor* editor)
{
	editor_type_exit_select_right(editor);
	if ( editor->cursor_row + 1 < editor->lines_used )
		editor_cursor_set(editor, editor->cursor_row + 1,
		                  editor->lines[editor->cursor_row + 1].used);
	else
		editor_cursor_set(editor, editor->cursor_row,
		                  editor->lines[editor->cursor_row].used);
}

void editor_type_control_select_down(struct editor* editor)
{
	if ( editor->select_row + 1 < editor->lines_used )
		editor_select_set(editor, editor->select_row + 1,
		                  editor->lines[editor->select_row + 1].used);
	else
		editor_select_set(editor, editor->select_row,
		                  editor->lines[editor->select_row].used);
}

void editor_skip_leading(struct editor* editor)
{
	struct line* current_line = &editor->lines[editor->cursor_row];
	for ( editor_cursor_set(editor, editor->cursor_row, 0);
	      editor->cursor_column < current_line->used;
	      editor_cursor_set(editor, editor->cursor_row,
	                               editor->cursor_column + 1) )
		if ( !iswspace(current_line->data[editor->cursor_column]) )
			break;
}

void editor_select_skip_leading(struct editor* editor)
{
	struct line* current_line = &editor->lines[editor->select_row];
	for ( editor_select_set(editor, editor->select_row, 0);
	      editor->select_column < current_line->used;
	      editor_select_set(editor, editor->select_row,
	                               editor->select_column + 1) )
		if ( !iswspace(current_line->data[editor->select_column]) )
			break;
}

void editor_type_home(struct editor* editor)
{
	if ( editor_has_selection(editor) )
	{
		size_t column, row;
		row_column_smallest(editor->cursor_row, editor->cursor_column,
		                    editor->select_row, editor->select_column,
		                    &row, &column);
		editor_cursor_set(editor, row, column);
	}
	if ( !editor->cursor_column )
	{
		editor_skip_leading(editor);
		return;
	}
	editor_cursor_set(editor, editor->cursor_row, 0);
}

void editor_type_select_home(struct editor* editor)
{
	if ( !editor->select_column )
	{
		editor_select_skip_leading(editor);
		return;
	}
	editor_select_set(editor, editor->select_row, 0);
}

void editor_skip_ending(struct editor* editor)
{
	struct line* current_line = &editor->lines[editor->cursor_row];
	for ( editor_cursor_set(editor, editor->cursor_row, current_line->used);
	      editor->cursor_column;
	      editor_cursor_set(editor, editor->cursor_row,
	                        editor->cursor_column - 1) )
		if ( !iswspace(current_line->data[editor->cursor_column-1]) )
			break;
}

void editor_select_skip_ending(struct editor* editor)
{
	struct line* current_line = &editor->lines[editor->select_row];
	for ( editor_select_set(editor, editor->select_row, current_line->used);
	      editor->select_column;
	      editor_select_set(editor, editor->select_row,
	                        editor->select_column - 1) )
		if ( !iswspace(current_line->data[editor->select_column-1]) )
			break;
}

void editor_type_end(struct editor* editor)
{
	if ( editor_has_selection(editor) )
	{
		size_t column, row;
		row_column_biggest(editor->cursor_row, editor->cursor_column,
		                   editor->select_row, editor->select_column,
		                   &row, &column);
		editor_cursor_set(editor, row, column);
	}
	struct line* current_line = &editor->lines[editor->cursor_row];
	if ( editor->cursor_column == current_line->used )
	{
		editor_skip_ending(editor);
		return;
	}
	editor_cursor_set(editor, editor->cursor_row, current_line->used);
}

void editor_type_select_end(struct editor* editor)
{
	struct line* current_line = &editor->lines[editor->select_row];
	if ( editor->select_column == current_line->used )
	{
		editor_select_skip_ending(editor);
		return;
	}
	editor_select_set(editor, editor->select_row, current_line->used);
}

void editor_type_page_up(struct editor* editor)
{
	if ( editor_has_selection(editor) )
	{
		size_t column, row;
		row_column_smallest(editor->cursor_row, editor->cursor_column,
		                    editor->select_row, editor->select_column,
		                    &row, &column);
		editor_cursor_set(editor, row, column);
	}
	if ( editor->cursor_row < editor->viewport_height )
	{
		editor_cursor_set(editor, 0, 0);
		return;
	}
	size_t new_line = editor->cursor_row - editor->viewport_height;
	size_t new_line_len = editor->lines[new_line].used;
	if ( new_line_len < editor->cursor_column )
		editor_cursor_set(editor, new_line, new_line_len);
	else
		editor_cursor_set(editor, new_line, editor->cursor_column);
}

void editor_type_select_page_up(struct editor* editor)
{
	if ( editor->select_row < editor->viewport_height )
	{
		editor_select_set(editor, 0, 0);
		return;
	}
	size_t new_line = editor->select_row - editor->viewport_height;
	size_t new_line_len = editor->lines[new_line].used;
	if ( new_line_len < editor->select_column )
		editor_select_set(editor, new_line, new_line_len);
	else
		editor_select_set(editor, new_line, editor->select_column);
}

void editor_type_page_down(struct editor* editor)
{
	if ( editor_has_selection(editor) )
	{
		size_t column, row;
		row_column_biggest(editor->cursor_row, editor->cursor_column,
		                   editor->select_row, editor->select_column,
		                   &row, &column);
		editor_cursor_set(editor, row, column);
	}
	size_t new_line = editor->cursor_row + editor->viewport_height;
	if ( editor->lines_used <= new_line )
	{
		editor_cursor_set(editor, editor->lines_used - 1,
		                  editor->lines[editor->lines_used - 1].used);
		return;
	}
	size_t new_line_len = editor->lines[new_line].used;
	if ( new_line_len < editor->cursor_column )
		editor_cursor_set(editor, new_line, new_line_len);
	else
		editor_cursor_set(editor, new_line, editor->cursor_column);
}

void editor_type_select_page_down(struct editor* editor)
{
	size_t new_line = editor->select_row + editor->viewport_height;
	if ( editor->lines_used <= new_line )
	{
		editor_select_set(editor, editor->lines_used - 1,
		                  editor->lines[editor->lines_used - 1].used);
		return;
	}
	size_t new_line_len = editor->lines[new_line].used;
	if ( new_line_len < editor->select_column )
		editor_select_set(editor, new_line, new_line_len);
	else
		editor_select_set(editor, new_line, editor->select_column);
}

void editor_type_edit(struct editor* editor)
{
	editor->mode = MODE_EDIT;
}

void editor_type_search(struct editor* editor)
{
	editor->mode = MODE_SEARCH;
	editor->modal_used = 0;
	editor->modal_cursor = 0;
	editor->modal_error = false;
}

void editor_type_goto_line(struct editor* editor)
{
	editor->mode = MODE_GOTO_LINE;
	editor->modal_used = 0;
	editor->modal_cursor = 0;
	editor->modal_error = false;
}

void editor_type_save(struct editor* editor)
{
	editor->mode = MODE_SAVE;

	free(editor->modal);
	editor->modal = convert_mbs_to_wcs(editor->current_file_name);
	editor->modal_used = wcslen(editor->modal);
	editor->modal_length = editor->modal_used+1;
	editor->modal_cursor = editor->modal_used;
	editor->modal_error = false;
}

void editor_type_save_as(struct editor* editor)
{
	editor->mode = MODE_SAVE;
	editor->modal_used = 0;
	editor->modal_cursor = 0;
	editor->modal_error = false;
}

void editor_type_open(struct editor* editor)
{
	editor->mode = editor->dirty ? MODE_ASK_LOAD : MODE_LOAD;
	editor->modal_used = 0;
	editor->modal_cursor = 0;
	editor->modal_error = false;
}

void editor_type_open_as(struct editor* editor)
{
	if ( editor->dirty )
		return editor_type_open(editor);

	editor->mode = MODE_LOAD;

	free(editor->modal);
	editor->modal = convert_mbs_to_wcs(editor->current_file_name);
	editor->modal_used = wcslen(editor->modal);
	editor->modal_length = editor->modal_used+1;
	editor->modal_cursor = editor->modal_used;
	editor->modal_error = false;
}

void editor_type_quit(struct editor* editor)
{
	editor->mode = editor->dirty ? MODE_ASK_QUIT : MODE_QUIT;
	editor->modal_cursor = 0;
	editor->modal_used = 0;
	editor->modal_error = false;
}

void editor_type_command(struct editor* editor)
{
	editor->mode = MODE_COMMAND;
	editor->modal_cursor = 0;
	editor->modal_used = 0;
	editor->modal_error = false;
}

void editor_type_raw_character(struct editor* editor, wchar_t c)
{
	struct line* current_line = &editor->lines[editor->cursor_row];

	if ( current_line->used == current_line->length )
	{
		size_t new_length = current_line->length ? 2 * current_line->length : 8;
		wchar_t* new_data = (wchar_t*) malloc(sizeof(wchar_t) * new_length);
		for ( size_t i = 0; i < current_line->used; i++ )
			new_data[i] = current_line->data[i];
		free(current_line->data);
		current_line->data = new_data;
		current_line->length = new_length;
	}

	editor->dirty = true;

	for ( size_t i = current_line->used; editor->cursor_column < i; i-- )
		current_line->data[i] = current_line->data[i-1];
	current_line->used++;
	current_line->data[editor->cursor_column] = c;
	editor_cursor_set(editor, editor->cursor_row, editor->cursor_column + 1);
}

void editor_type_copy(struct editor* editor)
{
	if ( editor->cursor_row == editor->select_row &&
	     editor->cursor_column == editor->select_column )
		return;

	free(editor->clipboard);

	size_t start_row;
	size_t start_column;
	size_t end_row;
	size_t end_column;
	if ( is_row_column_lt(editor->select_row, editor->select_column,
	                      editor->cursor_row, editor->cursor_column) )
	{
		start_row = editor->select_row;
		start_column = editor->select_column;
		end_row = editor->cursor_row;
		end_column = editor->cursor_column;
	}
	else
	{
		start_row = editor->cursor_row;
		start_column = editor->cursor_column;
		end_row = editor->select_row;
		end_column = editor->select_column;
	}

	size_t length = 0;
	for ( size_t row = start_row, column = start_column;
	      is_row_column_lt(row, column, end_row, end_column); )
	{
		struct line* line = &editor->lines[row];
		if ( row == end_row )
		{
			length += end_column - column;
			column = end_column;
		}
		else
		{
			length += (line->used - column) + 1 /*newline*/;
			column = 0;
			row++;
		}
	}

	editor->clipboard = (wchar_t*) malloc(sizeof(wchar_t) * (length + 1));
	size_t offset = 0;
	for ( size_t row = start_row, column = start_column;
	      is_row_column_lt(row, column, end_row, end_column); )
	{
		struct line* line = &editor->lines[row];
		if ( row == end_row )
		{
			memcpy(editor->clipboard + offset, line->data + column,
			       sizeof(wchar_t) * (end_column - column));
			offset += end_column - column;
			column = end_column;
		}
		else
		{
			memcpy(editor->clipboard + offset, line->data + column,
			       sizeof(wchar_t) * (line->used - column));
			editor->clipboard[offset + (line->used - column)] = L'\n';
			offset += (line->used - column) + 1 /*newline*/;
			column = 0;
			row++;
		}
	}
	editor->clipboard[length] = L'\0';
}

void editor_type_cut(struct editor* editor)
{
	if ( editor->cursor_row == editor->select_row &&
	     editor->cursor_column == editor->select_column )
		return;

	editor_type_copy(editor);
	editor_type_delete_selection(editor);
}

void editor_type_paste(struct editor* editor)
{
	if ( !(editor->cursor_row == editor->select_row &&
	       editor->cursor_column == editor->select_column) )
		editor_type_delete_selection(editor);

	for ( size_t i = 0; editor->clipboard && editor->clipboard[i]; i++ )
	{
		if ( editor->clipboard[i] == L'\n' )
			editor_type_newline(editor);
		else
			editor_type_raw_character(editor, editor->clipboard[i]);
	}
}

void editor_type_suspend(struct editor* editor)
{
	editor->suspend_requested = true;
}

void editor_type_character(struct editor* editor, wchar_t c)
{
	if ( editor->control )
	{
		switch ( towlower(c) )
		{
		case L'c': editor_type_copy(editor); break;
		case L'f': editor_type_search(editor); break;
		case L'g': editor_type_goto_line(editor); break;
		case L'k': editor_type_cut(editor); break;
		case L'o': editor->shift ?
		           editor_type_open_as(editor) :
		           editor_type_open(editor); break;
		case L'q': editor_type_quit(editor); break;
		case L's': editor->shift ?
		           editor_type_save_as(editor) :
		           editor_type_save(editor); break;
		case L'v': editor_type_paste(editor); break;
		case L'x': editor_type_cut(editor); break;
		case L'z': editor_type_suspend(editor); break;
		}
		return;
	}

	if ( editor_has_selection(editor) )
		editor_type_delete_selection(editor);

	if ( c == L'\n' ) { editor_type_newline(editor); return; }

	editor_type_raw_character(editor, c);
}
