#include <algorithm>
#include <chrono>
#include <string>
#include <regex>
#include <cmath>

#include "text_editor.h"

#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui.h" // for imGui::GetCurrentWindow()

using namespace std::chrono_literals;

// https://en.wikipedia.org/wiki/UTF-8
// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code)
static int UTF8CharLength(TextEditor::Char c) {
	if ((c & 0xFE) == 0xFC)
		return 6;
	if ((c & 0xFC) == 0xF8)
		return 5;
	if ((c & 0xF8) == 0xF0)
		return 4;
	else if ((c & 0xF0) == 0xE0)
		return 3;
	else if ((c & 0xE0) == 0xC0)
		return 2;
	return 1;
}

// "Borrowed" from ImGui source
static inline int ImTextCharToUtf8(char *buf, int buf_size, unsigned int c) {
	if (c < 0x80) {
		buf[0] = (char)c;
		return 1;
	}
	if (c < 0x800) {
		if (buf_size < 2) return 0;
		buf[0] = (char)(0xc0 + (c >> 6));
		buf[1] = (char)(0x80 + (c & 0x3f));
		return 2;
	}
	if (c >= 0xdc00 && c < 0xe000) {
		return 0;
	}
	if (c >= 0xd800 && c < 0xdc00) {
		if (buf_size < 4) return 0;
		buf[0] = (char)(0xf0 + (c >> 18));
		buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
		buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
		buf[3] = (char)(0x80 + ((c) & 0x3f));
		return 4;
	}
	//else if (c < 0x10000)
	{
		if (buf_size < 3) return 0;
		buf[0] = (char)(0xe0 + (c >> 12));
		buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
		buf[2] = (char)(0x80 + ((c) & 0x3f));
		return 3;
	}
}

unsigned TextEditor::Line::num_chars() const noexcept {
	if (num_char>=0) {
		_ASSERT(num_char == (int)calculate_num_chars());
	} else {
		num_char = calculate_num_chars();
	}
	return num_char;
}

unsigned TextEditor::Line::calculate_num_chars() const noexcept {
	int col = 0;
	for (unsigned i = 0; i < this->size(); i += UTF8CharLength(this->at(i).mChar))
		col++;
	return col;
}


TextEditor::TextEditor()
	: mLineSpacing(1.0f)
	, mUndoIndex(0)
	, mUndoBufferCleared(-1)
	, mOverwrite(false)
	, mReadOnly(false)
	, mWithinRender(false)
	, mScrollToCursor(false)
	, mScrollToTop(false)
	, mTextChanged(false)
	, mTextStart(20.0f)
	, mLeftMargin(10)
	, mCursorPositionChanged(false)
	, mColorRangeMin(0)
	, mColorRangeMax(0)
	, mSelectionMode(SelectionMode::Normal)
	, mHandleKeyboardInputs(true)
	, mHandleMouseInputs(true)
	, mIgnoreImGuiChild(false)
	, mShowWhitespaces(true)
	, mAlwaysRenderCursor(false)
	, mStartTime(clock::now())
	, mLastClick(-1.0f)
	, mFonts{}
	, mFontSize(13.0f)
{
	SetPalette(MscCshAppearanceList[1]);
	mLines.push_back(Line());
}

void TextEditor::SetPalette(ColorSyntaxAppearance(&P)[COLOR_MAX]) {
	for (size_t i = 0; i<COLOR_MAX; i++) {
		mPaletteBase[i].color = IM_COL32(P[i].r, P[i].g, P[i].b, 255);
		mPaletteBase[i].bold = bool(P[i].effects & COLOR_FLAG_BOLD);
		mPaletteBase[i].italic = bool(P[i].effects & COLOR_FLAG_ITALICS);
		mPaletteBase[i].underlined = bool(P[i].effects & COLOR_FLAG_UNDERLINE);
	}
}

void TextEditor::SetFonts(ImFont *regular, ImFont *bold, ImFont *italic, ImFont *bold_italic, float height,
						  ImFont *tooltip) noexcept {
	mFonts[0] = regular ? regular : ImGui::GetIO().FontDefault;
	mFonts[1] = bold ? bold : mFonts[0];
	mFonts[2] = italic ? italic : mFonts[0];
	mFonts[3] = bold_italic ? bold_italic : bold ? bold : italic ? italic : mFonts[0];
	mFonts[4] = tooltip ? tooltip : mFonts[0];
	mFontSize = height;
}


/** Returns the corrdinates of the nth character in the file.
 * Each newline counts as one character.
 * TABs are counted as one character in 'pos' but as mTabSize in the ret value
 * @param [in] char_pos The (zero-based index of the character whose coordinates we seek) */
TextEditor::Coordinates TextEditor::GetCoordinate(int char_pos) noexcept {
	if (char_pos < 0) return {0,0};
	if (mLines.empty()) return {0,0};
	int line = 0;
	while (line<(int)mLines.size()) {
		int chars_in_line = mLines[line].num_chars();
		if (char_pos<=chars_in_line)
			return {line, char_pos};
		char_pos -= chars_in_line+1; //+1 for newline
		line++;
	}
	//beyond the end - return pos after last char
	return {int(mLines.size() - 1), int(mLines.back().num_chars())};
}

/** Returns the zero-based index of the *character* before the cursor.
 * If the cursor is at the beginning of the file, we return zero.
 * If the cursor is beyond the end of the file, we return -1.*/
std::pair<int, int> TextEditor::GetCharBytePos(const Coordinates& a_) const noexcept {
    Coordinates a = SanitizeCoordinates(a_);
    if (a.mLine >= (int)mLines.size()) return { -1, -1 };
    if (a.mColumn > (int)mLines[a.mLine].num_chars()) return { -1, -1 };
    std::pair<int, int> pos = { 0,0 };
    for (int line = 0; line < a.mLine; line++) {
        pos.first += mLines[line].num_chars() + 1; //+1 for newline
        pos.second += mLines[line].size() + 1; //+1 for newline
    }
    pos.first += a.mColumn;
    int i = 0, chars = a.mColumn;
    for (; i < (int)mLines[a.mLine].size() && chars>0; chars--) 
        i += UTF8TrailingBytes(mLines[a.mLine][i].mChar) + 1;
    if (chars) return { -1,-1 };
    pos.second += i;
    return pos;
}

/** Returns the zero-based index of the *character* before the cursor.
 * If the cursor is at the beginning of the file, we return zero.
 * If the cursor is beyond the end of the file, we return -1.*/
int TextEditor::GetCharPos(const Coordinates &a_) const noexcept {
	Coordinates a = SanitizeCoordinates(a_);
	if (a.mLine>=(int)mLines.size()) return -1;
	if (a.mColumn>(int)mLines[a.mLine].num_chars()) return -1;
	int char_pos = 0;
	int line = 0;
	while (line<a.mLine) char_pos += mLines[line++].num_chars()+1; //+1 for newline
	char_pos += a.mColumn;
	return char_pos;
}


std::string TextEditor::GetText(const Coordinates & aStart, const Coordinates & aEnd) const
{
	std::string result;

	auto lstart = aStart.mLine;
	auto lend = aEnd.mLine;
	auto istart = GetCharacterIndex(aStart);
	auto iend = GetCharacterIndex(aEnd);
	size_t s = 0;

	for (int i = lstart; i < lend; i++)
		s += mLines[i].size();

	result.reserve(s + s / 8);

	while (istart < iend || lstart < lend)
	{
		if (lstart >= (int)mLines.size())
			break;

		auto& line = mLines[lstart];
		if (istart < (int)line.size())
		{
			result += line[istart].mChar;
			istart++;
		}
		else
		{
			istart = 0;
			++lstart;
			result += '\n';
		}
	}

	if (result.size() && result.back()=='\n' && lend>=(int)mLines.size()-1)
		result.pop_back();

	return result;
}

/** Search from the current cursor position. Set the selection to the text found & return true;
 * or do nothing & return false.
 * @param [in] what The UTF-8 text to look for.
 * @param [in] case_sensitive If false, we find text where only the letter case is different.
 * @param [in] whole_words_only If set, we only find text which as no alphanum chars before &
 *             after - but only if the search string starts or ends with an alphanum char, resp.
 * @param [in] fw When true we search for items after the current cursor.
 * @param [in] selection When set the search is limited to this selection.
 * @param [in] last_found_selected When set we are continuing the search and the current
 *             selection still corresponds to the previously found match of the search str.*/
bool TextEditor::Find(std::string_view what, bool case_sensitive, bool whole_words_only, bool fw,
					  const std::optional<std::pair<Coordinates, Coordinates>>& selection,
					  bool last_found_selected) {
	const int char_len = [what] {
		int ret = 0;
		for (size_t i = 0; i<what.length(); ret++)
			i += UTF8CharLength(what[i]);
		return ret;
	}();
	//The current cursor position.
	//If we have the last found item still selected, use the start/end of selection instead.
	Coordinates C = !last_found_selected
		? mState.mCursorPosition   //if no previous find or selection has changed since last find then search from current cursor pos
		: fw ? mState.mSelectionEnd    //else if we search fw, start from the end of the last found
		: mState.mSelectionStart.mColumn>0 ? Coordinates(mState.mSelectionStart.mLine, mState.mSelectionStart.mColumn-1)
		: mState.mSelectionStart.mLine>0 ? Coordinates(mState.mSelectionStart.mLine-1, mLines[mState.mSelectionStart.mLine-1].num_chars())
		: Coordinates(0,0);            //and if we search backwards we step back one from the start
	if (fw && selection) {
		if (C < selection->first) C = selection->first;
		else if (selection->second < C) return false;
	} else if (!fw && selection) {
		if (selection->second < C) C = selection->second;
		else if (C < selection->first) return false;
	}
	if (mLines.empty()) return false;
	//check if this the text at this coordinate is the same as 'what'
	//take 'case_sensitive' and 'whole_words_only' into account.
	const auto fits = [what, case_sensitive, whole_words_only, &selection, char_len, this](Coordinates c) {
		if ((int)mLines.size() <= c.mLine) return false;
		if ((int)mLines[c.mLine].num_chars() < c.mColumn + char_len) return false;
		const std::string s = GetText(c, {c.mLine, c.mColumn+char_len});
		if (case_sensitive)
			return s==what;
		std::string_view sv = s;
		for (size_t i = 0; i<what.length(); /*nope*/)
			if (const size_t l = UTF8CharLength(what[i]); sv.substr(i, l)==what.substr(i, l))
				i += l;
			else if (l>1)
				return false; //we compare upper and lowercase letters only in ASCII. Or else we need ICU, which is way to big for this shit.
			else if (const char c = what[i] & ~0x20; c<0x41 || 0x5a<c || c!=(sv[i] & ~0x20))
				return false;
			else
				i += l;
		const auto color = [this, line = c.mLine](int col) { return mLines[line][GetCharacterIndex({line, col})].mColorIndex; };
		const auto is_alpha = [this, line = c.mLine](int col) {
			const char c = mLines[line][GetCharacterIndex({line, col})].mChar;
			if ('0'<=c && c<='9') return true;
			if ('a'<=c && c<='z') return true;
			if ('A'<=c && c<='Z') return true;
			return c=='_';
		};
		if (whole_words_only)
			for (bool front : { true, false}) {
				const int col = front ? c.mColumn : c.mColumn + char_len;
				if (front && col==0) continue;
				if (!front && col==(int)mLines[c.mLine].num_chars()) continue;
				if (is_alpha(col-1) && is_alpha(col) && color(col-1)==color(col)) return false;
			}
		return true;
	};
	const Coordinates D = selection
		? fw ? selection->second : selection->first
		: fw ? Coordinates(mLines.size()-1, std::max(0, (int)mLines.back().num_chars()-1)) : Coordinates(0, 0);
	while (C!=D)
		if (fits(C)) {
			SetSelection(C, Coordinates(C.mLine, C.mColumn+char_len));
			return true;
		} else if (fw)
			Advance(C);
		else if (C.mColumn>0)
			C.mColumn--;
		else do
			C = {C.mLine-1, (int)mLines[C.mLine-1].num_chars()-1};
	    while (C.mLine>=0 && C.mColumn<0);
	return false;
}


TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const
{
	return SanitizeCoordinates(mState.mCursorPosition);
}

TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aValue) const
{
	auto line = aValue.mLine;
	auto column = aValue.mColumn;
	if (line >= (int)mLines.size())
	{
		if (mLines.empty())
		{
			line = 0;
			column = 0;
		}
		else
		{
			line = (int)mLines.size() - 1;
			column = GetLineMaxColumn(line);
		}
		return Coordinates(line, column);
	}
	else
	{
		column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line));
		return Coordinates(line, column);
	}
}

void TextEditor::Advance(Coordinates & aCoordinates) const
{
	if (aCoordinates.mLine < (int)mLines.size())
	{
		auto& line = mLines[aCoordinates.mLine];
		auto cindex = GetCharacterIndex(aCoordinates);

		if (cindex + 1 < (int)line.size())
		{
			auto delta = UTF8CharLength(line[cindex].mChar);
			cindex = std::min(cindex + delta, (int)line.size() - 1);
		}
		else
		{
			++aCoordinates.mLine;
			cindex = 0;
		}
		aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex);
	}
}

void TextEditor::DeleteRange(const Coordinates & aStart, const Coordinates & aEnd)
{
	assert(aEnd >= aStart);
	assert(!mReadOnly);

	//printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn);

	if (aEnd == aStart)
		return;

	auto start = GetCharacterIndex(aStart);
	auto end = GetCharacterIndex(aEnd);

	if (aStart.mLine == aEnd.mLine)
	{
		auto& line = mLines[aStart.mLine];
		auto n = GetLineMaxColumn(aStart.mLine);
		if (aEnd.mColumn >= n)
			line.erase(line.begin() + start, line.end());
		else
			line.erase(line.begin() + start, line.begin() + end);
		line.invalidate_num_chars();
	}
	else
	{
		auto& firstLine = mLines[aStart.mLine];
		auto& lastLine = mLines[aEnd.mLine];

		firstLine.erase(firstLine.begin() + start, firstLine.end());
		lastLine.erase(lastLine.begin(), lastLine.begin() + end);

		firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());
		firstLine.invalidate_num_chars();

		RemoveLine(aStart.mLine + 1, aEnd.mLine + 1);
	}

	mTextChanged = true;
}

int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValue)
{
	assert(!mReadOnly);

	int cindex = GetCharacterIndex(aWhere);
	int totalLines = 0;
	while (*aValue != '\0')
	{
		assert(!mLines.empty());

		if (*aValue == '\r')
		{
			// skip
			++aValue;
		}
		else if (*aValue == '\n')
		{
			if (cindex < (int)mLines[aWhere.mLine].size())
			{
				auto& newLine = InsertLine(aWhere.mLine + 1);
				auto& line = mLines[aWhere.mLine];
				newLine.insert(newLine.begin(), line.begin() + cindex, line.end());
				line.erase(line.begin() + cindex, line.end());
				line.invalidate_num_chars();
			}
			else
			{
				InsertLine(aWhere.mLine + 1);
			}
			++aWhere.mLine;
			aWhere.mColumn = 0;
			cindex = 0;
			++totalLines;
			++aValue;
		}
		else
		{
			auto& line = mLines[aWhere.mLine];
			auto d = UTF8CharLength(*aValue);
			while (d-- > 0 && *aValue != '\0')
				line.insert(line.begin() + cindex++, Glyph(*aValue++, COLOR_NORMAL));
			line.invalidate_num_chars();
			++aWhere.mColumn;
		}

		mTextChanged = true;
	}

	return totalLines;
}

void TextEditor::AddUndo(UndoRecord& aValue)
{
	assert(!mReadOnly);
	//printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n",
	//	aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn,
	//	aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn,
	//	aValue.mRemoved.c_str(), aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, aValue.mRemovedEnd.mColumn,
	//	aValue.mAfter.mCursorPosition.mLine, aValue.mAfter.mCursorPosition.mColumn
	//	);
	if (mUndoIndex>0 && (int)mUndoBuffer.size()>=mUndoIndex
		    &&  mUndoBuffer[mUndoIndex-1].Combine(aValue))
		return;
	mUndoBuffer.resize((size_t)(mUndoIndex + 1));
	mUndoBuffer.back() = aValue;
	++mUndoIndex;
	mUndoBufferCleared = mUndoIndex;
}

TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const
{
	ImVec2 local(aPosition.x - mLastOrigin.x, aPosition.y - mLastOrigin.y);

	int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y));

	int columnCoord = 0;

	if (lineNo >= 0 && lineNo < (int)mLines.size())
	{
		auto& line = mLines.at(lineNo);

		int columnIndex = 0;
		float columnX = 0.0f;

		while ((size_t)columnIndex < line.size())
		{
			float columnWidth = 0.0f;

			if (line[columnIndex].mChar == '\t') {
				columnWidth = mFonts[0]->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, " ").x;
			} else {
				char buf[7];
				auto d = UTF8CharLength(line[columnIndex].mChar);
				int i = 0;
				while (i < 6 && d-- > 0)
					buf[i++] = line[columnIndex++].mChar;
				buf[i] = '\0';
				columnWidth = mFonts[0]->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, buf).x;
			}
			if (mTextStart + columnX + columnWidth > local.x)
				break;
			columnX += columnWidth;
			columnCoord++;
		}
	}

	return SanitizeCoordinates(Coordinates(lineNo, columnCoord));
}

//returns topleft of the char and its width (not filled now, always zero) and height
//relative to the editor's origin
std::optional<std::pair<ImVec2, ImVec2>> TextEditor::CoordinatesToScreenPos(const TextEditor::Coordinates &a) const {
	const float y = a.mLine*mCharAdvance.y - mLastScroll.y;
	if (y<0 || mLastSize.y<y) return {};
	const float x =  mTextStart + TextDistanceToLineStart(a)-mLastScroll.x;
	if (x<0 || mLastSize.x<0) return {};
	return std::pair(ImVec2{x + mLastOrigin.x + mLastScroll.x, y + mLastOrigin.y + mLastScroll.y},
		             ImVec2{0, mCharAdvance.y});
}

TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const
{
	Coordinates at = aFrom;
	if (at.mLine >= (int)mLines.size())
		return at;

	auto& line = mLines[at.mLine];
	auto cindex = GetCharacterIndex(at);

	if (cindex >= (int)line.size())
		return at;

	while (cindex > 0 && isspace(line[cindex].mChar))
		--cindex;

	auto cstart = (PaletteIndex)line[cindex].mColorIndex;
	while (cindex > 0)
	{
		auto c = line[cindex].mChar;
		if ((c & 0xC0) != 0x80)	// not UTF code sequence 10xxxxxx
		{
			if (c <= 32 && isspace(c))
			{
				cindex++;
				break;
			}
			if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex)
				break;
		}
		--cindex;
	}
	return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));
}

TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates & aFrom) const
{
	Coordinates at = aFrom;
	if (at.mLine >= (int)mLines.size())
		return at;

	auto& line = mLines[at.mLine];
	auto cindex = GetCharacterIndex(at);

	if (cindex >= (int)line.size())
		return at;

	bool prevspace = (bool)isspace(line[cindex].mChar);
	auto cstart = (PaletteIndex)line[cindex].mColorIndex;
	while (cindex < (int)line.size())
	{
		auto c = line[cindex].mChar;
		auto d = UTF8CharLength(c);
		if (cstart != (PaletteIndex)line[cindex].mColorIndex)
			break;

		if (prevspace != !!isspace(c))
		{
			if (isspace(c))
				while (cindex < (int)line.size() && isspace(line[cindex].mChar))
					++cindex;
			break;
		}
		cindex += d;
	}
	return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex));
}

TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates & aFrom) const
{
	Coordinates at = aFrom;
	if (at.mLine >= (int)mLines.size())
		return at;

	// skip to the next non-word character
	auto cindex = GetCharacterIndex(aFrom);
	bool isword = false;
	bool skip = false;
	if (cindex < (int)mLines[at.mLine].size())
	{
		auto& line = mLines[at.mLine];
		isword = isalnum(line[cindex].mChar);
		skip = isword;
	}

	while (!isword || skip)
	{
		if (at.mLine >= (int)mLines.size())
		{
			auto l = std::max(0, (int) mLines.size() - 1);
			return Coordinates(l, GetLineMaxColumn(l));
		}

		auto& line = mLines[at.mLine];
		if (cindex < (int)line.size())
		{
			isword = isalnum(line[cindex].mChar);

			if (isword && !skip)
				return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));

			if (!isword)
				skip = false;

			cindex++;
		}
		else
		{
			cindex = 0;
			++at.mLine;
			skip = false;
			isword = false;
		}
	}

	return at;
}

/** Returns the byte index of this character within its line. */
int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const
{
	if (aCoordinates.mLine >= (int)mLines.size())
		return -1;
	auto& line = mLines[aCoordinates.mLine];
	int i = 0;
	for (int c=0; i < (int)line.size() && c < aCoordinates.mColumn; c++)
		i += UTF8CharLength(line[i].mChar);
	return i;
}

/** Returns the character column of a byte index within its line. */
int TextEditor::GetCharacterColumn(int aLine, int aIndex) const
{
	if (aLine >= (int)mLines.size())
		return 0;
	auto& line = mLines[aLine];
	int col = 0;
	for (int i = 0;  i < aIndex && i < (int)line.size(); i += UTF8CharLength(line[i].mChar))
		col++;
	return col;
}

int TextEditor::GetLineCharacterCount(int aLine) const
{
	if (aLine >= (int)mLines.size())
		return 0;
	auto& line = mLines[aLine];
	int c = 0;
	for (unsigned i = 0; i < line.size(); c++)
		i += UTF8CharLength(line[i].mChar);
	return c;
}

int TextEditor::GetLineMaxColumn(int aLine) const
{
	if (aLine >= (int)mLines.size())
		return 0;
	return mLines[aLine].num_chars();
}

bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const
{
	if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0)
		return true;

	auto& line = mLines[aAt.mLine];
	auto cindex = GetCharacterIndex(aAt);
	if (cindex >= (int)line.size())
		return true;

	if (mColorize)
		return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex;

	return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar);
}

void TextEditor::RemoveLine(int aStart, int aEnd)
{
	assert(!mReadOnly);
	assert(aEnd >= aStart);
	assert(mLines.size() > (size_t)(aEnd - aStart));

	ErrorMarkers etmp;
	for (auto &i : mErrorMarkers)
		if (i.first.mLine<aStart)
			etmp.insert(etmp.end(), std::move(i));
		else if (i.first.mLine>=aEnd)
			etmp.insert(etmp.end(), {Coordinates(i.first.mLine - (aEnd-aStart), i.first.mColumn), std::move(i.second)});
	mErrorMarkers = std::move(etmp);

	mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd);
	assert(!mLines.empty());

	mTextChanged = true;
}

void TextEditor::RemoveLine(int aIndex)
{
	RemoveLine(aIndex, aIndex+1);
}

TextEditor::Line& TextEditor::InsertLine(int aIndex)
{
	assert(!mReadOnly);

	auto& result = *mLines.insert(mLines.begin() + aIndex, Line());

	ErrorMarkers etmp;
	for (auto& i : mErrorMarkers)
		etmp.insert(etmp.end(), {Coordinates(i.first.mLine >= aIndex ? i.first.mLine + 1 : i.first.mLine, i.first.mColumn), std::move(i.second)});
	mErrorMarkers = std::move(etmp);

	return result;
}

std::string TextEditor::GetWordUnderCursor() const
{
	auto c = GetCursorPosition();
	return GetWordAt(c);
}

std::string TextEditor::GetWordAt(const Coordinates & aCoords) const
{
	auto start = FindWordStart(aCoords);
	auto end = FindWordEnd(aCoords);

	std::string r;

	auto istart = GetCharacterIndex(start);
	auto iend = GetCharacterIndex(end);

	for (auto it = istart; it < iend; ++it)
		r.push_back(mLines[aCoords.mLine][it].mChar);

	return r;
}

TextEditor::GlyphStyle TextEditor::GetGlyphStyle(const Glyph & aGlyph) const
{
	return mPalette[aGlyph.mColorIndex];
}

void TextEditor::HandleKeyboardInputs(bool only_when_focused)
{
	ImGuiIO& io = ImGui::GetIO();
	auto shift = io.KeyShift;
	auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
	auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;

	if (ImGui::IsWindowFocused() || !only_when_focused)
	{
		if (ImGui::IsWindowHovered())
			ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
		//ImGui::CaptureKeyboardFromApp(true);

		io.WantCaptureKeyboard = true;
		io.WantTextInput = true;

		if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Z))
			Undo();
		else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGuiKey_Backspace))
			Undo();
		else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Y))
			Redo();
		else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_UpArrow))
			MoveUp(1, shift);
		else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_DownArrow))
			MoveDown(1, shift);
		else if (!alt && ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
			MoveLeft(1, shift, ctrl);
		else if (!alt && ImGui::IsKeyPressed(ImGuiKey_RightArrow))
			MoveRight(1, shift, ctrl);
		else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageUp))
			MoveUp(GetPageSize() - 4, shift);
		else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageDown))
			MoveDown(GetPageSize() - 4, shift);
		else if (!alt && ctrl && ImGui::IsKeyPressed(ImGuiKey_Home))
			MoveTop(shift);
		else if (ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End))
			MoveBottom(shift);
		else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Home))
			MoveHome(shift);
		else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End))
			MoveEnd(shift);
		else if (!IsReadOnly() && !io.KeySuper && !shift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_Delete))
			Delete(io.KeyCtrl);
		else if (!IsReadOnly() && !io.KeySuper && !shift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_Backspace))
			Backspace(io.KeyCtrl);
		else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))
			mOverwrite ^= true;
		else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))
			Copy();
		else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_C))
			Copy();
		else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))
			Paste();
		else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_V))
			Paste();
		else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_X))
			Cut();
		else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete))
			Cut();
		else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_A))
			SelectAll();
		else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Enter))
			EnterCharacter('\n', false);
		else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab))
			EnterCharacter('\t', shift);

		if (!IsReadOnly() && !io.InputQueueCharacters.empty())
		{
			for (int i = 0; i < io.InputQueueCharacters.Size; i++)
			{
				auto c = io.InputQueueCharacters[i];
				if (c != 127 && (c == '\n' || c >= 32))
					EnterCharacter(c, shift);
			}
			io.InputQueueCharacters.resize(0);
		}
	}
}

void TextEditor::HandleMouseInputs()
{
	ImGuiIO& io = ImGui::GetIO();
	auto shift = io.KeyShift;
	auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
	auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;

	if (ImGui::IsWindowHovered())
	{
		if (!shift && !alt)
		{
			auto click = ImGui::IsMouseClicked(0);
			auto doubleClick = ImGui::IsMouseDoubleClicked(0);
			auto t = ImGui::GetTime();
			auto tripleClick = click && !doubleClick && (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime);

			/*
			Left mouse button triple click
			*/

			if (tripleClick)
			{
				if (!ctrl)
				{
					mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
					mSelectionMode = SelectionMode::Line;
					SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
				}

				mLastClick = -1.0f;
			}

			/*
			Left mouse button double click
			*/

			else if (doubleClick)
			{
				if (!ctrl)
				{
					mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
					if (mSelectionMode == SelectionMode::Line)
						mSelectionMode = SelectionMode::Normal;
					else
						mSelectionMode = SelectionMode::Word;
					SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
				}

				mLastClick = (float)ImGui::GetTime();
			}

			/*
			Left mouse button click
			*/
			else if (click)
			{
				mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
				if (ctrl)
					mSelectionMode = SelectionMode::Word;
				else
					mSelectionMode = SelectionMode::Normal;
				SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);

				mLastClick = (float)ImGui::GetTime();
			}
			// Mouse left button dragging (=> update selection)
			else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0))
			{
				io.WantCaptureMouse = true;
				mState.mCursorPosition = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
				SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
			}
		}
	}
}

void TextEditor::Render()
{
	// Ensure fonts (if [0] is empty all is empty)
	if (!mFonts[0])
		mFonts.fill(ImGui::GetIO().FontDefault);

	/* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/
	const float fontSize = mFonts[0]->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, "#", nullptr, nullptr).x;
	mCharAdvance = ImVec2(fontSize,  (mFontSize + ImGui::GetStyle().ItemSpacing.y) * mLineSpacing);

	/* Update palette with the current alpha from style */
	for (int i = 0; i < (int)COLOR_MAX; ++i)
	{
		mPalette[i] = mPaletteBase[i];
		auto color = ImGui::ColorConvertU32ToFloat4(mPalette[i].color);
		color.w *= ImGui::GetStyle().Alpha;
		mPalette[i].color = ImGui::ColorConvertFloat4ToU32(color);
	}

	std::string mLineBuffer;

	auto drawList = ImGui::GetWindowDrawList();
	float longest(mTextStart);

	if (mScrollToTop)
	{
		mScrollToTop = false;
		ImGui::SetScrollY(0.f);
	}

	mLastOrigin = ImGui::GetCursorScreenPos();
	mScrollHasChanged = mLastScroll.x != ImGui::GetScrollX() || mLastScroll.x != ImGui::GetScrollY();
	mLastScroll = {ImGui::GetScrollX(), ImGui::GetScrollY()};
	mLastSize = ImGui::GetContentRegionAvail();

	auto lineNo = (int)floor(mLastScroll.y / mCharAdvance.y);
	auto globalLineMax = (int)mLines.size();
	auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((mLastScroll.y + mLastSize.y) / mCharAdvance.y)));

	// Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width
	char buf[16];
	snprintf(buf, 16, " %d ", globalLineMax);
	mTextStart = mFonts[0]->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin;

	if (!mLines.empty())
	{
		float spaceSize = mFonts[0]->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, " ", nullptr, nullptr).x;

		while (lineNo <= lineMax)
		{
			ImVec2 lineStartScreenPos = ImVec2(mLastOrigin.x, mLastOrigin.y + lineNo * mCharAdvance.y);
			ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y);

			auto& line = mLines[lineNo];
			longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest);
			auto columnNo = 0;
			Coordinates lineStartCoord(lineNo, 0);
			Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo));

			// Draw selection for the current line
			float sstart = -1.0f;
			float ssend = -1.0f;

			assert(mState.mSelectionStart <= mState.mSelectionEnd);
			if (mState.mSelectionStart <= lineEndCoord)
				sstart = mState.mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mSelectionStart) : 0.0f;
			if (mState.mSelectionEnd > lineStartCoord)
				ssend = TextDistanceToLineStart(mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord);

			if (mState.mSelectionEnd.mLine > lineNo)
				ssend += mCharAdvance.x;

			if (sstart != -1 && ssend != -1 && sstart < ssend)
			{
				ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y);
				ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y);
				drawList->AddRectFilled(vstart, vend, mColorSelection);
			}

			auto start = ImVec2(lineStartScreenPos.x + mLastScroll.x, lineStartScreenPos.y);
			// Draw line number (right aligned)
			snprintf(buf, 16, "%d  ", lineNo + 1);

			auto lineNoWidth = mFonts[1]->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, buf, nullptr, nullptr).x;
			drawList->AddText(mFonts[1], mFontSize, //bold
							  ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y),
							  mColorLineNumber, buf);

			if (mState.mCursorPosition.mLine == lineNo)
			{
				auto focused = ImGui::IsWindowFocused();

				// Highlight the current line (where the cursor is)
				if (!HasSelection())
				{
					auto end = ImVec2(start.x + mLastSize.x + mLastScroll.x, start.y + mCharAdvance.y);
					drawList->AddRectFilled(start, end, focused ? mColorCurrentLineFill : mColorCurrentLineFillInactive);
					drawList->AddRect(start, end, mColorCurrentLineLine, 1.0f);
				}

				// Render the cursor
				if (focused || mAlwaysRenderCursor)
				{
					auto timeEnd = clock::now();
					auto elapsed = timeEnd - mStartTime;
					if (elapsed > 400ms)
					{
						float width = 1.0f;
						auto cindex = GetCharacterIndex(mState.mCursorPosition);
						float cx = TextDistanceToLineStart(mState.mCursorPosition);

						if (mOverwrite && cindex < (int)line.size())
						{
							auto c = line[cindex].mChar;
							if (c == '\t') {
								width = spaceSize;
							} else {
								char buf2[2];
								buf2[0] = line[cindex].mChar;
								buf2[1] = '\0';
								width = GetFont(mPalette[line[cindex].mColorIndex])
									->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, buf2).x;
							}
						}
						ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y);
						ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y);
						drawList->AddRectFilled(cstart, cend, mColorLineCursor);
						if (elapsed > 800ms)
							mStartTime = timeEnd;
					}
				}
			}

			// Render colorized text
			GlyphStyle prevStyle = line.empty() ? mPalette[(int)COLOR_NORMAL] : GetGlyphStyle(line[0]);
			ImVec2 bufferOffset;

			for (size_t i = 0; i < line.size(); /*none*/)
			{
				auto& glyph = line[i];
				GlyphStyle style = GetGlyphStyle(glyph);

				if ((style != prevStyle || glyph.mChar == '\t' || glyph.mChar == ' ') && !mLineBuffer.empty())
				{
					const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y);
					drawList->AddText(GetFont(prevStyle), mFontSize,
									  newOffset, prevStyle.color, mLineBuffer.c_str());
					auto textSize = GetFont(prevStyle)
						->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, mLineBuffer.c_str(), nullptr, nullptr);
					const float zoom = mFontSize/16.f;
					const float y = mFontSize*0.85f;
					if (prevStyle.underlined)
						drawList->AddLine(ImVec2(newOffset.x, newOffset.y+y), ImVec2(newOffset.x+textSize.x, newOffset.y+y), prevStyle.color, zoom);
					bufferOffset.x += textSize.x;
					mLineBuffer.clear();
				}
				prevStyle = style;

				if (glyph.mChar == '\t')
				{
					auto oldX = bufferOffset.x;
					bufferOffset.x += spaceSize;
					++i;

					if (mShowWhitespaces)
					{
						const auto s = mFontSize;
						const auto x1 = textScreenPos.x + oldX + 1.0f;
						const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f;
						const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f;
						const ImVec2 p1(x1, y);
						const ImVec2 p2(x2, y);
						const ImVec2 p3(x2 - s * 0.2f, y - s * 0.2f);
						const ImVec2 p4(x2 - s * 0.2f, y + s * 0.2f);
						drawList->AddLine(p1, p2, 0x90909090);
						drawList->AddLine(p2, p3, 0x90909090);
						drawList->AddLine(p2, p4, 0x90909090);
					}
				}
				else if (glyph.mChar == ' ')
				{
					if (mShowWhitespaces)
					{
						const auto s = mFontSize;
						const auto x = textScreenPos.x + bufferOffset.x + spaceSize * 0.5f;
						const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f;
						drawList->AddCircleFilled(ImVec2(x, y), 1.5f, 0x80808080, 4);
					}
					bufferOffset.x += spaceSize;
					i++;
				}
				else
				{
					auto l = UTF8CharLength(glyph.mChar);
					while (l-- > 0)
						mLineBuffer.push_back(line[i++].mChar);
				}
				++columnNo;
			}

			if (!mLineBuffer.empty())
			{
				const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y);
				drawList->AddText(GetFont(prevStyle), mFontSize,
								  newOffset, prevStyle.color, mLineBuffer.c_str());
				mLineBuffer.clear();
			}

			// Draw error markers
			for (auto errorIt = mErrorMarkers.lower_bound(Coordinates(lineNo, 0))
				    ; errorIt != mErrorMarkers.end() && errorIt->first.mLine==lineNo
				    ; ++errorIt) {
				const float xFrom = TextDistanceToLineStart(errorIt->first);
				const float xTill = TextDistanceToLineStart({errorIt->first.mLine, errorIt->first.mColumn + errorIt->second.first});
				const auto hStart = ImVec2(textScreenPos.x+xFrom, textScreenPos.y);
				const auto hEnd = ImVec2(textScreenPos.x+xTill + (xTill-xFrom<1 ?  spaceSize : 0), textScreenPos.y+mCharAdvance.y);
				constexpr float net_thick = 2.0f;
				const float thick = net_thick*mScale;
				std::vector<ImVec2> points;
				points.reserve(size_t((xTill-xFrom)/thick)+1);
				ImVec2 s{hStart.x, hEnd.y - 1};
				while (s.x<hEnd.x) {
					points.push_back(s);
					s.x += thick;
					if (points.size() & 1) s.y -= thick;
					else s.y += thick;
				}
				drawList->AddPolyline(points.data(), points.size(), IM_COL32(255, 0, 0, 255), ImDrawFlags_None, mScale);

				if (ImGui::IsMouseHoveringRect(hStart, hEnd)) {
					ImGui::PushFont(mFonts[4]);
					ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4);
					ImGui::BeginTooltip();
					ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(128,0,0,255));
					ImGui::TextUnformatted(errorIt->second.second.c_str());
					ImGui::PopStyleColor();
					ImGui::EndTooltip();
					ImGui::PopStyleVar();
					ImGui::PopFont();
				}
			}
			++lineNo;
		}
	}


	ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y));

	if (mScrollToCursor)
	{
		EnsureCursorVisible();
		ImGui::SetWindowFocus();
		mScrollToCursor = false;
	}
}

void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder)
{
	mWithinRender = true;
	mTextChanged = false;
	mCursorPositionChanged = false;
	mUndoBufferCleared = -1;

	ImGui::PushStyleColor(ImGuiCol_ChildBg, mColorBackground);
	ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
	if (!mIgnoreImGuiChild)
		ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavInputs);

	if (mHandleKeyboardInputs)
	{
		HandleKeyboardInputs();
		ImGui::PushAllowKeyboardFocus(true);
	}

	if (mHandleMouseInputs)
		HandleMouseInputs();

	ColorizeInternal();
	Render();

	if (mHandleKeyboardInputs)
		ImGui::PopAllowKeyboardFocus();

	if (!mIgnoreImGuiChild)
		ImGui::EndChild();

	ImGui::PopStyleVar();
	ImGui::PopStyleColor();

	mWithinRender = false;
}

void TextEditor::SetText(const std::string & aText)
{
	mLines.clear();
	mLines.emplace_back(Line());
	for (auto chr : aText)
	{
		if (chr == '\r')
		{
			// ignore the carriage return character
		}
		else if (chr == '\n')
			mLines.emplace_back(Line());
		else
		{
			mLines.back().emplace_back(Glyph(chr, COLOR_NORMAL));
		}
	}

	mTextChanged = true;
	mScrollToTop = true;

	mUndoBuffer.clear();
	mUndoIndex = 0;

	SetSelection(mState.mSelectionStart, mState.mSelectionEnd); //normalize selection

	Colorize();
}

void TextEditor::SetTextLines(const std::vector<std::string> & aLines)
{
	mLines.clear();

	if (aLines.empty())
	{
		mLines.emplace_back(Line());
	}
	else
	{
		mLines.resize(aLines.size());

		for (size_t i = 0; i < aLines.size(); ++i)
		{
			const std::string & aLine = aLines[i];

			mLines[i].reserve(aLine.size());
			for (size_t j = 0; j < aLine.size(); ++j)
				mLines[i].emplace_back(Glyph(aLine[j], COLOR_NORMAL));
			mLines[i].invalidate_num_chars();
		}
	}

	mTextChanged = true;
	mScrollToTop = true;

	mUndoBuffer.clear();
	mUndoIndex = 0;

	SetSelection(mState.mSelectionStart, mState.mSelectionEnd); //normalize selection

	Colorize();
}

void TextEditor::EnterCharacter(ImWchar aChar, bool aShift)
{
	assert(!mReadOnly);

    const bool had_selection = HasSelection();
    if (!had_selection)
        mState.mSelectionStart = mState.mSelectionEnd = GetActualCursorCoordinates();
    
    UndoRecord u;
	u.mBefore = mState;

	if (aChar == '\t') {
		const int lStartLenBefore = mLines[mState.mSelectionStart.mLine].size();
		const int lEndLenBefore = mLines[mState.mSelectionEnd.mLine].size();
		u.mRemovedStart = {mState.mSelectionStart.mLine, 0};
		if (mState.mSelectionStart.mLine==mState.mSelectionEnd.mLine || mState.mSelectionEnd.mColumn)
			u.mRemovedEnd = SanitizeCoordinates({mState.mSelectionEnd.mLine, INT_MAX});
		else
			u.mRemovedEnd = SanitizeCoordinates({mState.mSelectionEnd.mLine-1, INT_MAX});

		u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd);

		if (mIndentChars.find('\t')!=mIndentChars.npos && mIndent)
			mTextChanged |= bool(mIndent(mIndentData, mLines, '\t', aShift, u.mRemovedStart, u.mRemovedEnd));
		else for (int l = mState.mSelectionStart.mLine; l<=mState.mSelectionEnd.mLine; l++) {
			if (l==mState.mSelectionEnd.mLine && mState.mSelectionEnd.mColumn==0 && l!=mState.mSelectionStart.mLine)
				continue;
			if (!aShift)
				mLines[l].insert(mLines[l].begin(), mTabSize, Glyph{' '});
			else for (int c = 0; c<mTabSize; c++)
				if (mLines[l].empty() || mLines[l].front().mChar!=' ') break;
				else mLines[l].erase(mLines[l].begin());
			mLines[l].invalidate_num_chars(); //Well, we know how much chars we have taken away or added, but this is simpler
			mTextChanged = true;
		}

		u.mAddedStart = {mState.mSelectionStart.mLine, 0};
		if (mState.mSelectionStart.mLine==mState.mSelectionEnd.mLine || mState.mSelectionEnd.mColumn)
			u.mAddedEnd = SanitizeCoordinates({mState.mSelectionEnd.mLine, INT_MAX});
		else
			u.mAddedEnd = SanitizeCoordinates({mState.mSelectionEnd.mLine-1, INT_MAX});
		u.mAdded = GetText(u.mRemovedStart, u.mRemovedEnd);

		if (!had_selection) {
			mState.mSelectionStart = u.mBefore.mSelectionStart;
			mState.mSelectionEnd = u.mBefore.mSelectionEnd;
		}
		if (mState.mSelectionStart.mColumn || !had_selection) {
			//We only added/deleted spaces so the byte length diff is the same as the UTF8 char len diff
			const int diff = mLines[mState.mSelectionStart.mLine].size() - lStartLenBefore;
			mState.mSelectionStart.mColumn = std::max(0, mState.mSelectionStart.mColumn + diff);
		}
		if (mState.mSelectionEnd.mColumn || !had_selection) {
			const int diff = mLines[mState.mSelectionEnd.mLine].size() - lEndLenBefore;
			mState.mSelectionEnd.mColumn = std::max(0, mState.mSelectionEnd.mColumn + diff);
		}
		if (!had_selection)
			SetCursorPosition(mState.mSelectionStart);
	} else {
		if (HasSelection()) {
			u.mRemoved = GetSelectedText();
			u.mRemovedStart = mState.mSelectionStart;
			u.mRemovedEnd = mState.mSelectionEnd;
			DeleteSelection();
		} // HasSelection

		auto coord = GetActualCursorCoordinates();
		u.mAddedStart = coord;

		assert(!mLines.empty());

		if (aChar == '\n') {
			InsertLine(coord.mLine + 1);
			auto &line = mLines[coord.mLine];
			auto &newLine = mLines[coord.mLine + 1];

			const size_t whitespaceSize = newLine.size();
			auto cindex = GetCharacterIndex(coord);
			newLine.insert(newLine.end(), line.begin() + cindex, line.end());
			line.erase(line.begin() + cindex, line.begin() + line.size());
			newLine.invalidate_num_chars();
			line.invalidate_num_chars();
			SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize)));
			u.mAdded = (char)aChar;
		} else {
			char buf[7];
			int e = ImTextCharToUtf8(buf, 7, aChar);
			if (e > 0) {
				buf[e] = '\0';
				auto &line = mLines[coord.mLine];
				auto cindex = GetCharacterIndex(coord);

				if (mOverwrite && cindex < (int)line.size()) {
					auto d = UTF8CharLength(line[cindex].mChar);

					u.mRemovedStart = mState.mCursorPosition;
					u.mRemovedEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d));

					while (d-- > 0 && cindex < (int)line.size()) {
						u.mRemoved += line[cindex].mChar;
						line.erase(line.begin() + cindex);
					}
				}

				for (auto p = buf; *p != '\0'; p++, ++cindex)
					line.insert(line.begin() + cindex, Glyph(*p, COLOR_NORMAL));
				u.mAdded = buf;
				line.invalidate_num_chars();

				SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex)));
			} else
				return;
		}
		if (aChar<256 && mIndentChars.find(char(aChar))!=mIndentChars.npos && mIndent) {
			mColorize(mLines, mColorizeData);
			const int inserted = mIndent(mIndentData, mLines, char(aChar), false, GetActualCursorCoordinates(), {});
			mState.mCursorPosition.mColumn = std::max(0, mState.mCursorPosition.mColumn+inserted); //inserted may be negative for del
			if (inserted>0) {
				if (aChar == '\n')
					u.mAdded.append(inserted, ' ');
				else
					u.mAdded.insert(0, inserted, ' ');
			} else if (inserted<0) {
				if (u.mRemoved.empty()) {
					u.mRemoved.assign(-inserted, ' ');
					u.mRemovedEnd = u.mRemovedStart = coord; //cursor pos before inserting
					u.mRemovedStart.mColumn -= -inserted;
					u.mAddedStart = u.mRemovedStart;
				} else {
					u.mRemoved.insert(u.mRemoved.begin(), -inserted, ' ');
					u.mRemovedStart.mColumn -= -inserted;
					u.mAddedStart = u.mRemovedStart;
				}
			}
		}
		u.mAddedEnd = GetActualCursorCoordinates();
		mTextChanged = true;
	}

	u.mAfter = mState;

	if (mTextChanged) {
		AddUndo(u);
		Colorize(mState.mCursorPosition.mLine - 1, 3);
	}
	EnsureCursorVisible();
}

void TextEditor::SetReadOnly(bool aValue)
{
	mReadOnly = aValue;
}

void TextEditor::SetCursorPosition(const Coordinates & aPosition)
{
	if (mState.mCursorPosition != aPosition)
	{
		mState.mCursorPosition = aPosition;
		mCursorPositionChanged = true;
		EnsureCursorVisible();
	}
}

void TextEditor::SetSelectionStart(const Coordinates & aPosition)
{
	mState.mSelectionStart = SanitizeCoordinates(aPosition);
	if (mState.mSelectionStart > mState.mSelectionEnd)
		std::swap(mState.mSelectionStart, mState.mSelectionEnd);
}

void TextEditor::SetSelectionEnd(const Coordinates & aPosition)
{
	mState.mSelectionEnd = SanitizeCoordinates(aPosition);
	if (mState.mSelectionStart > mState.mSelectionEnd)
		std::swap(mState.mSelectionStart, mState.mSelectionEnd);
}

void TextEditor::SetSelection(const Coordinates & aStart, const Coordinates & aEnd, SelectionMode aMode)
{
	auto oldSelStart = mState.mSelectionStart;
	auto oldSelEnd = mState.mSelectionEnd;

	mState.mSelectionStart = SanitizeCoordinates(aStart);
	mState.mSelectionEnd = SanitizeCoordinates(aEnd);
	if (mState.mSelectionStart > mState.mSelectionEnd)
		std::swap(mState.mSelectionStart, mState.mSelectionEnd);

	switch (aMode)
	{
	case TextEditor::SelectionMode::Normal:
		break;
	case TextEditor::SelectionMode::Word:
	{
		mState.mSelectionStart = FindWordStart(mState.mSelectionStart);
		if (!IsOnWordBoundary(mState.mSelectionEnd))
			mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd));
		break;
	}
	case TextEditor::SelectionMode::Line:
	{
		const auto lineNo = mState.mSelectionEnd.mLine;
		mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0);
		mState.mSelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo));
		break;
	}
	default:
		break;
	}

	if (mState.mSelectionStart != oldSelStart ||
		mState.mSelectionEnd != oldSelEnd)
		mCursorPositionChanged = true;
}

void TextEditor::InsertText(const std::string & aValue)
{
	InsertText(aValue.c_str());
}

void TextEditor::InsertText(const char * aValue)
{
	if (aValue == nullptr)
		return;

	auto pos = GetActualCursorCoordinates();
	auto start = std::min(pos, mState.mSelectionStart);
	int totalLines = pos.mLine - start.mLine;

	totalLines += InsertTextAt(pos, aValue);

	SetSelection(pos, pos);
	SetCursorPosition(pos);
	Colorize(start.mLine - 1, totalLines + 2);
}

void TextEditor::DeleteSelection()
{
	assert(mState.mSelectionEnd >= mState.mSelectionStart);

	if (mState.mSelectionEnd == mState.mSelectionStart)
		return;

	DeleteRange(mState.mSelectionStart, mState.mSelectionEnd);

	SetSelection(mState.mSelectionStart, mState.mSelectionStart);
	SetCursorPosition(mState.mSelectionStart);
	mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
	Colorize(mState.mSelectionStart.mLine, 1);
}

void TextEditor::MoveUp(int aAmount, bool aSelect)
{
	auto oldPos = mState.mCursorPosition;
	mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount);
	if (oldPos != mState.mCursorPosition)
	{
		if (aSelect)
		{
			if (oldPos == mInteractiveStart)
				mInteractiveStart = mState.mCursorPosition;
			else if (oldPos == mInteractiveEnd)
				mInteractiveEnd = mState.mCursorPosition;
			else
			{
				mInteractiveStart = mState.mCursorPosition;
				mInteractiveEnd = oldPos;
			}
		}
		else
			mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
		SetSelection(mInteractiveStart, mInteractiveEnd);

		EnsureCursorVisible();
	}
}

void TextEditor::MoveDown(int aAmount, bool aSelect)
{
	assert(mState.mCursorPosition.mColumn >= 0);
	auto oldPos = mState.mCursorPosition;
	mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount));

	if (mState.mCursorPosition != oldPos)
	{
		if (aSelect)
		{
			if (oldPos == mInteractiveEnd)
				mInteractiveEnd = mState.mCursorPosition;
			else if (oldPos == mInteractiveStart)
				mInteractiveStart = mState.mCursorPosition;
			else
			{
				mInteractiveStart = oldPos;
				mInteractiveEnd = mState.mCursorPosition;
			}
		}
		else
			mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
		SetSelection(mInteractiveStart, mInteractiveEnd);

		EnsureCursorVisible();
	}
}

static bool IsUTFSequence(char c)
{
	return (c & 0xC0) == 0x80;
}

void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode)
{
	if (mLines.empty())
		return;

	auto oldPos = mState.mCursorPosition;
	mState.mCursorPosition = GetActualCursorCoordinates();
	auto line = mState.mCursorPosition.mLine;
	auto cindex = GetCharacterIndex(mState.mCursorPosition);

	while (aAmount-- > 0)
	{
		if (cindex == 0)
		{
			if (line > 0)
			{
				--line;
				if ((int)mLines.size() > line)
					cindex = (int)mLines[line].size();
				else
					cindex = 0;
			}
		}
		else
		{
			--cindex;
			if (cindex > 0)
			{
				if ((int)mLines.size() > line)
				{
					while (cindex > 0 && IsUTFSequence(mLines[line][cindex].mChar))
						--cindex;
				}
			}
		}

		mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex));
		if (aWordMode)
		{
			mState.mCursorPosition = FindWordStart(mState.mCursorPosition);
			cindex = GetCharacterIndex(mState.mCursorPosition);
		}
	}

	mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex));

	assert(mState.mCursorPosition.mColumn >= 0);
	if (aSelect)
	{
		if (oldPos == mInteractiveStart)
			mInteractiveStart = mState.mCursorPosition;
		else if (oldPos == mInteractiveEnd)
			mInteractiveEnd = mState.mCursorPosition;
		else
		{
			mInteractiveStart = mState.mCursorPosition;
			mInteractiveEnd = oldPos;
		}
	}
	else
		mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
	SetSelection(mInteractiveStart, mInteractiveEnd, SelectionMode::Normal);

	EnsureCursorVisible();
}

void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode)
{
	auto oldPos = mState.mCursorPosition;

	if (mLines.empty() || oldPos.mLine >= (int)mLines.size())
		return;

	auto cindex = GetCharacterIndex(mState.mCursorPosition);
	while (aAmount-- > 0)
	{
		auto lindex = mState.mCursorPosition.mLine;
		auto& line = mLines[lindex];

		if (cindex >= (int)line.size())
		{
			if (mState.mCursorPosition.mLine < (int)mLines.size() - 1)
			{
				mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1));
				mState.mCursorPosition.mColumn = 0;
			}
			else
				return;
		}
		else
		{
			cindex += UTF8CharLength(line[cindex].mChar);
			mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex));
            if (aWordMode) {
                mState.mCursorPosition = FindWordEnd(mState.mCursorPosition);
                //Step through spaces
                while (mState.mCursorPosition.mLine < (int)mLines.size()) 
                    if ((int)mLines[mState.mCursorPosition.mLine].size() <= mState.mCursorPosition.mColumn) {
                        mState.mCursorPosition.mColumn = 0;
                        mState.mCursorPosition.mLine++;
                    } else if (mLines[mState.mCursorPosition.mLine][mState.mCursorPosition.mColumn].mChar == ' ')
                        mState.mCursorPosition.mColumn++;
                    else
                        break;
            }
		}
	}

	if (aSelect)
	{
		if (oldPos == mInteractiveEnd)
			mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition);
		else if (oldPos == mInteractiveStart)
			mInteractiveStart = mState.mCursorPosition;
		else
		{
			mInteractiveStart = oldPos;
			mInteractiveEnd = mState.mCursorPosition;
		}
	}
	else
		mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
	SetSelection(mInteractiveStart, mInteractiveEnd, SelectionMode::Normal); //always use normal mode: we have already calculated the word's end

	EnsureCursorVisible();
}

void TextEditor::MoveTop(bool aSelect)
{
	auto oldPos = mState.mCursorPosition;
	SetCursorPosition(Coordinates(0, 0));

	if (mState.mCursorPosition != oldPos)
	{
		if (aSelect)
		{
			mInteractiveEnd = oldPos;
			mInteractiveStart = mState.mCursorPosition;
		}
		else
			mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
		SetSelection(mInteractiveStart, mInteractiveEnd);
	}
}

void TextEditor::TextEditor::MoveBottom(bool aSelect)
{
	auto oldPos = GetCursorPosition();
	auto newPos = Coordinates((int)mLines.size() - 1, 0);
	SetCursorPosition(newPos);
	if (aSelect)
	{
		mInteractiveStart = oldPos;
		mInteractiveEnd = newPos;
	}
	else
		mInteractiveStart = mInteractiveEnd = newPos;
	SetSelection(mInteractiveStart, mInteractiveEnd);
}

void TextEditor::MoveHome(bool aSelect)
{
	auto oldPos = mState.mCursorPosition;
	SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0));

	if (aSelect)
	{
		if (oldPos == mInteractiveStart)
			mInteractiveStart = mState.mCursorPosition;
		else if (oldPos == mInteractiveEnd)
			mInteractiveEnd = mState.mCursorPosition;
		else
		{
			mInteractiveStart = mState.mCursorPosition;
			mInteractiveEnd = oldPos;
		}
	}
	else
		mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
	SetSelection(mInteractiveStart, mInteractiveEnd);
}

void TextEditor::MoveEnd(bool aSelect)
{
	auto oldPos = mState.mCursorPosition;
	SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine)));

	if (aSelect)
	{
		if (oldPos == mInteractiveEnd)
			mInteractiveEnd = mState.mCursorPosition;
		else if (oldPos == mInteractiveStart)
			mInteractiveStart = mState.mCursorPosition;
		else
		{
			mInteractiveStart = oldPos;
			mInteractiveEnd = mState.mCursorPosition;
		}
	}
	else
		mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
	SetSelection(mInteractiveStart, mInteractiveEnd);
}

void TextEditor::Delete(bool aWordMode)
{
	assert(!mReadOnly);

	if (mLines.empty())
		return;

	UndoRecord u;
	u.mBefore = mState;

	if (HasSelection()) {
		u.mRemoved = GetSelectedText();
		u.mRemovedStart = mState.mSelectionStart;
		u.mRemovedEnd = mState.mSelectionEnd;

		DeleteSelection();
	} else {
		auto pos = GetActualCursorCoordinates();
		SetCursorPosition(pos);
		auto& line = mLines[pos.mLine];

		if (pos.mColumn == GetLineMaxColumn(pos.mLine)) {
			if (pos.mLine == (int)mLines.size() - 1)
				return;

			u.mRemoved = '\n';
			u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
			Advance(u.mRemovedEnd);

			auto& nextLine = mLines[pos.mLine + 1];
			line.insert(line.end(), nextLine.begin(), nextLine.end());
			line.invalidate_num_chars();
			RemoveLine(pos.mLine + 1);
		} else if (aWordMode) {
			MoveRight(1, true, true); //select the word right
			//Remove the resulting selection
			u.mRemoved = GetSelectedText();
			u.mRemovedStart = mState.mSelectionStart;
			u.mRemovedEnd = mState.mSelectionEnd;

			DeleteSelection();
		} else {
			auto cindex = GetCharacterIndex(pos);
			u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
			u.mRemovedEnd.mColumn++;
			u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd);

			auto d = UTF8CharLength(line[cindex].mChar);
			while (d-- > 0 && cindex < (int)line.size())
				line.erase(line.begin() + cindex);
			line.invalidate_num_chars();
		}

		mTextChanged = true;

		Colorize(pos.mLine, 1);
	}

	u.mAfter = mState;
	AddUndo(u);
}

void TextEditor::Backspace(bool aWordMode)
{
	assert(!mReadOnly);

	if (mLines.empty())
		return;

	UndoRecord u;
	u.mBefore = mState;

	if (HasSelection())
	{
		u.mRemoved = GetSelectedText();
		u.mRemovedStart = mState.mSelectionStart;
		u.mRemovedEnd = mState.mSelectionEnd;

		DeleteSelection();
	}
	else
	{
		auto pos = GetActualCursorCoordinates();
		SetCursorPosition(pos);

		if (mState.mCursorPosition.mColumn == 0)
		{
			if (mState.mCursorPosition.mLine == 0)
				return;

			u.mRemoved = '\n';
			u.mRemovedStart = u.mRemovedEnd = Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1));
			Advance(u.mRemovedEnd);

			auto& line = mLines[mState.mCursorPosition.mLine];
			auto& prevLine = mLines[mState.mCursorPosition.mLine - 1];
			auto prevSize = GetLineMaxColumn(mState.mCursorPosition.mLine - 1);
			prevLine.insert(prevLine.end(), line.begin(), line.end());
			prevLine.invalidate_num_chars();

			ErrorMarkers etmp;
			for (auto& i : mErrorMarkers)
				etmp.insert(etmp.end(), {{i.first.mLine - 1 == mState.mCursorPosition.mLine ? i.first.mLine - 1 : i.first.mLine, i.first.mColumn}, std::move(i.second)});
			mErrorMarkers = std::move(etmp);

			RemoveLine(mState.mCursorPosition.mLine);
			--mState.mCursorPosition.mLine;
			mState.mCursorPosition.mColumn = prevSize;
		} else if (aWordMode) {
			mInteractiveEnd = mInteractiveStart = pos;
			MoveLeft(1, true, true); //select the word left
			//Remove the resulting selection
			u.mRemoved = GetSelectedText();
			u.mRemovedStart = mState.mSelectionStart;
			u.mRemovedEnd = mState.mSelectionEnd;
			DeleteSelection();
		} else {
			const int del = mIndent && mIndentChars.find('\x08')==mIndentChars.npos
				? mIndent(mIndentData, mLines, '\x08', false, pos, {})
				: 1;
			mInteractiveEnd = mInteractiveStart = pos;
			MoveLeft(del, true, false); //select the word left
			u.mRemoved = GetSelectedText();
			u.mRemovedStart = mState.mSelectionStart;
			u.mRemovedEnd = mState.mSelectionEnd;
			DeleteSelection();
		}

		mTextChanged = true;

		EnsureCursorVisible();
		Colorize(mState.mCursorPosition.mLine, 1);
	}

	u.mAfter = mState;
	AddUndo(u);
}

void TextEditor::SelectWordUnderCursor()
{
	auto c = GetCursorPosition();
	SetSelection(FindWordStart(c), FindWordEnd(c));
}

void TextEditor::SelectAll()
{
	SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0));
}

bool TextEditor::HasSelection() const
{
	return mState.mSelectionEnd > mState.mSelectionStart;
}

void TextEditor::Copy()
{
	if (HasSelection())
	{
		ImGui::SetClipboardText(GetSelectedText().c_str());
        mFullLineOnClipboard = {};
	}
	else
	{
		if (!mLines.empty())
		{
            mFullLineOnClipboard = {};
			auto& line = mLines[GetActualCursorCoordinates().mLine];
			for (auto& g : line)
                mFullLineOnClipboard.push_back(g.mChar);
            mFullLineOnClipboard.push_back('\n');
			ImGui::SetClipboardText(mFullLineOnClipboard.c_str());
		}
	}
}

void TextEditor::Cut()
{
	if (IsReadOnly())
	{
		Copy();
	}
	else
	{
		if (HasSelection())
		{
            UndoRecord u;
            u.mBefore = mState;
            u.mRemoved = mFullLineOnClipboard;
            u.mRemovedStart = mState.mSelectionStart;
            u.mRemovedEnd = mState.mSelectionEnd;

			Copy();
			DeleteSelection();

			u.mAfter = mState;
			AddUndo(u);
		}
        else
        {
            Copy();
            UndoRecord u;
            u.mBefore = mState;
            u.mRemoved = mFullLineOnClipboard;
            const int line = GetActualCursorCoordinates().mLine;
            u.mRemovedStart = { line, 0 };
            if (line + 1 < (int)mLines.size())
                u.mRemovedEnd = { line + 1 , 0 };
            else
                u.mRemovedEnd = { line, GetLineMaxColumn(line)};

            mLines.erase(mLines.begin() + line);

            const Coordinates n = line < (int)mLines.size() ? Coordinates(line, 0) : Coordinates(line - 1, GetLineMaxColumn(line - 1));

            u.mAfter = mState = { n, n, n };
            AddUndo(u);
        }
	}
}

void TextEditor::Paste()
{
    if (!HasSelection() && mFullLineOnClipboard.size() && ImGui::GetClipboardText() == mFullLineOnClipboard)
        mState.mCursorPosition.mColumn = mState.mSelectionStart.mColumn = mState.mSelectionEnd.mColumn = 0;
    Insert(ImGui::GetClipboardText());
}

void TextEditor::Insert(const char *txt) {
	if (IsReadOnly())
		return;

	if (!txt)
		return;

	UndoRecord u;
	u.mBefore = mState;

	if (HasSelection()) {
		u.mRemoved = GetSelectedText();
		u.mRemovedStart = mState.mSelectionStart;
		u.mRemovedEnd = mState.mSelectionEnd;
		DeleteSelection();
	}

	u.mAdded = txt;
	u.mAddedStart = GetActualCursorCoordinates();

	InsertText(txt);

	u.mAddedEnd = GetActualCursorCoordinates();
	u.mAfter = mState;
	AddUndo(u);
}

bool TextEditor::CanUndo() const

{
	return !mReadOnly && mUndoIndex > 0;
}

bool TextEditor::CanRedo() const
{
	return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size();
}

void TextEditor::Undo(int aSteps)
{
	while (CanUndo() && aSteps-- > 0)
		mUndoBuffer[--mUndoIndex].Undo(this);
}

void TextEditor::Redo(int aSteps)
{
	while (CanRedo() && aSteps-- > 0)
		mUndoBuffer[mUndoIndex++].Redo(this);
}

std::string TextEditor::GetText() const
{
	return GetText(Coordinates(), Coordinates((int)mLines.size(), 0));
}

std::vector<std::string> TextEditor::GetTextLines() const
{
	std::vector<std::string> result;

	result.reserve(mLines.size());

	for (auto & line : mLines)
	{
		std::string text;

		text.resize(line.size());

		for (size_t i = 0; i < line.size(); ++i)
			text[i] = line[i].mChar;

		result.emplace_back(std::move(text));
	}

	return result;
}

std::string TextEditor::GetSelectedText() const
{
	return GetText(mState.mSelectionStart, mState.mSelectionEnd);
}

std::string TextEditor::GetCurrentLineText()const
{
	auto lineLength = GetLineMaxColumn(mState.mCursorPosition.mLine);
	return GetText(
		Coordinates(mState.mCursorPosition.mLine, 0),
		Coordinates(mState.mCursorPosition.mLine, lineLength));
}

void TextEditor::ProcessInputs()
{
}

void TextEditor::Colorize(int aFromLine, int aLines)
{
	int toLine = aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines);
	mColorRangeMin = std::min(mColorRangeMin, aFromLine);
	mColorRangeMax = std::max(mColorRangeMax, toLine);
	mColorRangeMin = std::max(0, mColorRangeMin);
	mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax);
}

void TextEditor::ColorizeInternal()
{
	if (mLines.empty() || !mColorize)
		return;

	if (mColorRangeMin < mColorRangeMax) {
		mColorRangeMin = std::numeric_limits<int>::max();
		mColorRangeMax = 0;
		return mColorize(mLines, mColorizeData);
	}
}

float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const
{
	auto& line = mLines[aFrom.mLine];
	float distance = 0.0f;
	float spaceSize = mFonts[0]->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
	int colIndex = GetCharacterIndex(aFrom);
	for (int it = 0; it < (int)line.size() && it < colIndex; )
	{
		if (line[it].mChar == '\t')
		{
			distance += spaceSize;
			++it;
		}
		else
		{
			auto d = UTF8CharLength(line[it].mChar);
			char tempCString[7];
			int i = 0;
			for (; i < 6 && d-- > 0 && it < (int)line.size(); i++, it++)
				tempCString[i] = line[it].mChar;

			tempCString[i] = '\0';
			distance += mFonts[0]->CalcTextSizeA(mFontSize, FLT_MAX, -1.0f, tempCString, nullptr, nullptr).x;
		}
	}

	return distance;
}

void TextEditor::EnsureCursorVisible()
{
	if (!mWithinRender)
	{
		mScrollToCursor = true;
		return;
	}

	auto top = 1 + (int)ceil(mLastScroll.y / mCharAdvance.y);
	auto bottom = (int)ceil((mLastScroll.y+ mLastSize.y) / mCharAdvance.y);

	auto pos = GetActualCursorCoordinates();
	auto len = TextDistanceToLineStart(pos);

	if (pos.mLine < top)
		ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y));
	if (pos.mLine > bottom - 4)
		ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - mLastSize.y));
	if (len + mTextStart < mLastScroll.x + 4)
		ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4));
	if (len + mTextStart > mLastScroll.x + mLastSize.x - 4)
		ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - mLastSize.x));
}

int TextEditor::GetPageSize() const
{
	auto height = ImGui::GetWindowHeight() - 20.0f;
	return (int)floor(height / mCharAdvance.y);
}

TextEditor::UndoRecord::UndoRecord(
	const std::string& aAdded,
	const TextEditor::Coordinates aAddedStart,
	const TextEditor::Coordinates aAddedEnd,
	const std::string& aRemoved,
	const TextEditor::Coordinates aRemovedStart,
	const TextEditor::Coordinates aRemovedEnd,
	TextEditor::EditorState& aBefore,
	TextEditor::EditorState& aAfter)
	: mAdded(aAdded)
	, mAddedStart(aAddedStart)
	, mAddedEnd(aAddedEnd)
	, mRemoved(aRemoved)
	, mRemovedStart(aRemovedStart)
	, mRemovedEnd(aRemovedEnd)
	, mBefore(aBefore)
	, mAfter(aAfter)
{
	assert(mAddedStart <= mAddedEnd);
	assert(mRemovedStart <= mRemovedEnd);
}

void TextEditor::UndoRecord::Undo(TextEditor * aEditor)
{
	if (!mAdded.empty())
	{
		aEditor->DeleteRange(mAddedStart, mAddedEnd);
		aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2);
	}

	if (!mRemoved.empty())
	{
		auto start = mRemovedStart;
		aEditor->InsertTextAt(start, mRemoved.c_str());
		aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2);
	}

	aEditor->mState = mBefore;
	aEditor->mInteractiveStart = mBefore.mSelectionStart;
	aEditor->mInteractiveEnd = mBefore.mSelectionEnd;
	aEditor->EnsureCursorVisible();

}

void TextEditor::UndoRecord::Redo(TextEditor * aEditor)
{
	if (!mRemoved.empty())
	{
		aEditor->DeleteRange(mRemovedStart, mRemovedEnd);
		aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 1);
	}

	if (!mAdded.empty())
	{
		auto start = mAddedStart;
		aEditor->InsertTextAt(start, mAdded.c_str());
		aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 1);
	}

	aEditor->mState = mAfter;
	aEditor->mInteractiveStart = mBefore.mSelectionStart;
	aEditor->mInteractiveEnd = mBefore.mSelectionEnd;
	aEditor->EnsureCursorVisible();
}

bool TextEditor::UndoRecord::Combine(const UndoRecord &o) {
	if (prevent_combine) return false;
	if (mAfter!=o.mBefore) return false;
	if (mAdded.empty()!=o.mAdded.empty()) return false;
	if (mRemoved.empty()!=o.mRemoved.empty()) return false;
	if (mAdded.empty() == mRemoved.empty()) return false; //combine either additions or removals, but not both
	if (mAdded.size()) {
		if (mAddedEnd!=o.mAddedStart) return false;
		if (mAdded.find('\n')!=std::string::npos) return false; //do not combine when inserting a newline
		mAddedEnd = o.mAddedEnd;
		mAdded += o.mAdded;
	} else {
		if (o.mRemoved.find('\n')!=std::string::npos) return false; //do not combine when deleting a newline
		if (o.mRemovedEnd==mRemovedStart) { //backspace
			mRemovedStart = o.mRemovedStart;
			mRemoved.insert(0, o.mRemoved);
		} else if (o.mRemovedStart==mRemovedStart) { //delete
			if (o.mRemovedStart.mLine!=o.mRemovedEnd.mLine) return false; //can only be combined inside a line
			if (mRemovedStart.mLine!=mRemovedEnd.mLine) return false;     //can only be combined inside a line
			mRemovedEnd.mColumn += o.mRemovedEnd.mColumn - o.mRemovedStart.mColumn;
			mRemoved += o.mRemoved;
		}
	}
	mAfter = o.mAfter;
	++mGeneration;
	return true;
}
