// File: UAPC1021.cpp
// Author: Edward Ly (m5222120)
// Date: 9 January 2019
// Draft of Solution for University of Aizu Programming Contest
// Using C++ String and Vector Classes
// Problem 1021: Emacs-like Editor

#include <iostream> // cin, cout, endl
#include <cstdio>   // getline
#include <cstdlib>  // exit, EXIT_FAILURE, EXIT_SUCCESS
#include <string>   // string (class)
#include <vector>   // vector (class)
using namespace std;

// Define preprocessor macro for debugging.
// #define DEBUG

#if defined( DEBUG )
#define debug( code_fragment ) { code_fragment }
#else
#define debug( code_fragment )
#endif

// Declare functions used in this program.
int numLines ( vector<string> text );
void checkAndOutputError();
void printText ( vector<string> text, int numLines );

/* ==================================================================
	int main ()

	Main function routine.

	Input: multi-line string of text (from stdin).
	Output: prints the modified text according to a list of Emacs
		commands to stdout.
================================================================== */

int main () {
	// Program constants and parameters.
	const int MAX_COMMANDS = 300,
			  MAX_INPUT_LINES = 10,
			  MAX_INPUT_COLUMNS = 20,
			  MAX_OUTPUT_LINES = 100,
			  MAX_OUTPUT_COLUMNS = 1000;
	const string END_OF_TEXT_INDICATOR("END_OF_TEXT");
	const char END_OF_COMMANDS_INDICATOR = '-';

	// Initialize commands, buffers, and text strings.
	string commands( MAX_COMMANDS, ' ' ); commands.clear();
	string buffer( MAX_OUTPUT_COLUMNS, ' ' ); buffer.clear();
	string inputLine( MAX_INPUT_COLUMNS, ' ' ); inputLine.clear();

	string initialString( MAX_OUTPUT_COLUMNS, ' ' );
	vector<string> text( MAX_OUTPUT_LINES, initialString );
	initialString.clear(); text.clear();

	debug( cout << "Initialized buffers..." << endl; );

	// Read input and copy input text, one line at a time.
	getline( cin, inputLine );

	checkAndOutputError();

	if ( inputLine.compare(END_OF_TEXT_INDICATOR) == 0 ) {
		// Input text is empty, so there is no text to modify. Notify user and exit.
		cout << "Notice: no input text has been read, exiting..." << endl;
		exit(EXIT_SUCCESS);		
	}

	do {
		if ( numLines(text) < MAX_INPUT_LINES ) {
			if ( inputLine.length() > MAX_INPUT_COLUMNS ) {
				// Truncate excess characters before copying input line.
				inputLine.erase(MAX_INPUT_COLUMNS);
				// Notify that at least one input line exceeds the maximum number of characters allowed.
				cout << "Warning: maximum number of characters per line exceeded! Only the first "
				     << MAX_INPUT_COLUMNS
					 << " characters on line "
					 << ( numLines(text) + 1 )
					 << " will be kept..."
					 << endl;
			}
			text.push_back( inputLine.append("\n") );
		}
		getline(cin, inputLine);
	} while ( cin.good() and inputLine.compare(END_OF_TEXT_INDICATOR) != 0 );

	checkAndOutputError();
	// else: input text has been copied successfully.

	if ( numLines(text) > MAX_INPUT_LINES ) {
		text.erase( text.begin() + MAX_INPUT_LINES, text.end() );
		// Notify that excess input lines have been omitted.
		cout << "Warning: maximum number of input lines exceeded! Continuing with first "
		     << MAX_INPUT_LINES
			 << " lines..."
			 << endl;
	}

	debug(
		cout << "Successfully read input text..." << endl;
		printText( text, numLines(text) );
	);

	// Now copy commands list.
	getline( cin, inputLine );

	checkAndOutputError();

	// If there are multiple characters on one line,
	// just take the first character and ignore the rest.
	if ( !inputLine.empty() and inputLine[0] == END_OF_COMMANDS_INDICATOR ) {
		// No commands to modify input, so just print text and exit.
		printText( text, numLines(text) );
		exit(EXIT_SUCCESS);
	}

	do {
		if ( !inputLine.empty() ) commands.append( &inputLine[0] );
		do { getline(cin, inputLine); } while ( inputLine.empty() );
	} while ( cin.good() and inputLine[0] != END_OF_COMMANDS_INDICATOR );

	checkAndOutputError();
	// else: commands list has been copied successfully.

	if ( commands.length() > MAX_COMMANDS ) {
		// Truncate excess commands and notify that excess commands have been omitted.
		commands.erase(MAX_COMMANDS);
		cout << "Warning: maximum number of commands exceeded! Continuing with first "
		     << MAX_COMMANDS
			 << " commands..."
			 << endl;
	}

	debug(
		cout << "Successfully read commands list..." << endl;
		cout << commands << endl;
	);

	// Read commands and modify text.
	int currentLine = 0, currentColumn = 0; // serves as "pointer" to text.
	int currentCommand = 0;

	do {
		switch ( commands[currentCommand] ) {
			case 'a':
				// Move the cursor to the first character of the current line.
				currentColumn = 0;
				break;
			case 'e':
				// Move the cursor to the end-of-line of the current line.
				currentColumn = text[currentLine].find("\n");
				break;
			case 'p':
				// Move the cursor to the first character of the next upper line, if it exists.
				// If there is no line above the current line, move the cursor to the first character of the current line.
				if ( currentLine > 0 ) currentLine--;
				currentColumn = 0;
				break;
			case 'n':
				// Move the cursor to the first character of the next lower line, if it exists.
				// If there is no line below the current line, move the cursor to the first character of the current line.
				if ( currentLine < numLines(text) - 1 ) currentLine++;
				currentColumn = 0;
				break;
			case 'f':
				// Move the cursor by one character to the right, unless the cursor points out the end-of-line.
				// If the cursor points out the end-of-line and there is a line below the current line,
				// move the cursor to the first character of the next lower line. Otherwise, do nothing.
				if ( text[currentLine][currentColumn] != '\n' ) {
					currentColumn++;
					break;
				}
				// else: cursor points to end-of-line.
				if ( currentLine < numLines(text) - 1 ) {
					currentLine++;
					currentColumn = 0;
				}
				// else: end of text reached, so do nothing.
				break;
			case 'b':
				// Move the cursor by one character to the left, unless the cursor points out the first character.
				// If the cursor points out the first character and there is a line above the current line,
				// move the cursor to the end-of-line of the next upper line. Otherwise, do nothing.
				if ( currentColumn > 0 ) {
					currentColumn--;
					break;
				}
				// else: cursor points to beginning of line.
				if ( currentLine > 0 ) {
					currentLine--;
					currentColumn = text[currentLine].find("\n");
				}
				// else: beginning of text reached, so do nothing.
				break;
			case 'd':
				// If the cursor points out a character, delete the character
				// (Characters and end-of-line next to the deleted character are shifted to the left).
				// If the cursor points out the end-of-line and there is a line below, the next lower line is appended
				// to the end-of-line of the current line (Lines below the current line are shifted to the upper).
				// Otherwise, do nothing.
				if ( text[currentLine][currentColumn] != '\n' ) {
					text[currentLine].erase( text[currentLine].begin() + currentColumn );
					break;
				}
				// else: cursor points to end-of-line.
				if ( currentLine < numLines(text) - 1 ) {
					// There is a line below, so append next line to end of current line.
					int currentLineForReplace = currentLine + 1;
					text[currentLine].replace( currentColumn, 1, text[currentLineForReplace] );

					// Erase line.
					text.erase( text.begin() + currentLineForReplace );
				}
				// else: end of text reached, so do nothing.
				break;
			case 'k':
				// If the cursor points out the end-of-line and there is a line below the current line,
				// perform the command d mentioned above, and record a linefeed on the buffer.
				// If the cursor does not point out the end-of-line, cut characters between the cursor (inclusive)
				// and the end-of-line, and record them on the buffer.
				// After this operation, the cursor indicates the end-of-line of the current line.
				if ( text[currentLine][currentColumn] == '\n' and currentLine < numLines(text) - 1 ) {
					// Perform "d".
					// Append next line to end of current line.
					int currentLineForReplace = currentLine + 1;
					text[currentLine].replace( currentColumn, 1, text[currentLineForReplace] );

					// Erase line.
					text.erase( text.begin() + currentLineForReplace );

					// Record a linefeed to the buffer.
					buffer = "\n";
					break;
				}
				if ( text[currentLine][currentColumn] != '\n' ) {
					string stringToBuffer = text[currentLine].substr(currentColumn);
					stringToBuffer.erase( stringToBuffer.end() - 1 ); // remove '\n'
					text[currentLine].erase( currentColumn, stringToBuffer.length() );
					buffer = stringToBuffer;
				}
				// else: end of text reached, so do nothing.
				break;
			case 'y':
				// If the buffer is empty, do nothing.
				// If the buffer is holding a linefeed, insert the linefeed at the cursor.
				// The cursor moves to the first character of the new line.
				// If the buffer is holding characters, insert the characters at the cursor.
				// The cursor moves to the character or end-of-line which is originally pointed by the cursor.
				if ( buffer.empty() ) break;
				if ( buffer.compare("\n") == 0 ) {
					string stringToMove = text[currentLine].substr(currentColumn);
					stringToMove.erase( stringToMove.end() - 1 ); // remove '\n'
					text[currentLine].erase( currentColumn, stringToMove.length() );

					// Insert new line.
					text.insert( text.begin() + currentLine + 1, stringToMove.append("\n") );

					// Move cursor to new line.
					currentLine++;
					currentColumn = 0;

					break;
				}
				// else: the buffer is holding some characters.

				// Insert buffer at current position and update position.
				text[currentLine].insert( currentColumn, buffer );
				currentColumn += buffer.length();
			default:
				break;
		}
		currentCommand++;
	} while ( currentCommand < commands.length() );

	debug( cout << "Successfully modified text, printing..." << endl; );

	// Print modified text.
	printText( text, numLines(text) );

	exit(EXIT_SUCCESS);
}

/* ==================================================================
	int numLines ( vector<string> text )

	Returns the number of lines in the text.
================================================================== */

int numLines ( vector<string> text ) {
	return text.size();
}

/* ==================================================================
	void checkAndOutputError ()

	Prints an error message to terminal and ends program if an
	error has occurred.
================================================================== */

void checkAndOutputError () {
	if ( cin.eof() ) {
		// Unexpected end of input reached, exit program.
		cout << "Unexpected end of input reached, exiting..." << endl;
		exit(EXIT_FAILURE);
	}

	if ( cin.fail() ) {
		// Other error occurred, exit program.
		cout << "An unknown error occurred while reading input, exiting..." << endl;
		exit(EXIT_FAILURE);
	}
}

/* ==================================================================
	void printText ( vector<string> text, int numLines )

	Prints the first *numLines* lines of text to the terminal.
================================================================== */

void printText ( vector<string> text, int numLines ) {
	if ( numLines < 1 ) return;
	int currentLine = 0;

	do {
		cout << text[currentLine++];
	} while ( currentLine < numLines );
}



Measured memory_bytes: 3216