/*
 Program WinCaml: Graphical User Interface
 for interactive use of Caml-Light and Ocaml.
 Copyright (C) 2005-2017 Jean Mouric 35700 Rennes France
 email: jean.mouric@orange.fr
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// File CInput.cpp

#include "CChildFrame.h"
#include "CFrame.h"
#include "CHighlighter.h"
#include "CInput.h"
#include "COutput.h"
#include "CUndoManager.h"
#include <sstream>

void caretCoordinates(const wstring& text, size_t caretPosition, size_t SpaceCountPerTab, size_t& line, size_t& column);
wstring doIndent(wstring* str, int count, int indent_after_in, int indent_filter);
wstring doOCamlIndent(wstring* str, int count, int indent_after_in, int indent_filter);
string toLatin1(const wstring& str);
#ifdef _MSVC98_
wstring errorMessages[4] = {L"Commentaire inachev\xE9", L"Marque de fin de commentaire inattendue", L"Cha\xEEne inachev\xE9"L"e dans un commentaire", L"Cha\xEEne inachev\xE9"L"e"};
#else
wstring errorMessages[4] = {L"Commentaire inachev\u00E9", L"Marque de fin de commentaire inattendue", L"Cha\u00EEne inachev\u00E9e dans un commentaire", L"Cha\u00EEne inachev\u00E9e"};
#endif

CInput::CInput(CChildFrame* cfr): CRichEdit(cfr)
{
	highlighter = NULL;
	commandStart = 0;
	commandEnd = 0;
	inBuffer = true;
	bufferStart = 0;
	cChildFrame = cfr;
    forecolorCount = COLORCOUNT - 3;
	T = new unsigned long[COLORCOUNT];
	setFont(cChildFrame->font1);
	setTabs(cChildFrame->InputFontSize, cChildFrame->SpaceCountPerTab);
	undoManager = new CUndoManager(this);
	previousString = L"";
	cChildFrame->SyntaxColoring ? setColors() : removeColors();
	highlighter = new CHighlighter(this);
}
CInput::~CInput()
{
	delete highlighter;
	delete undoManager;
	delete [] T;
}
void CInput::onKeyDown()
{
	selectionChanged = true;
}
void CInput::onEnterKeyDown()
{
	sendCommand();
	selectionChanged = true;
}
void CInput::onReturnKeyDown()
{
	size_t selStart, selEnd;
	getSelection(selStart, selEnd);
	size_t n = lineFirstChar(lineFromChar(selStart));
    wstring s = endofline;
	if (n < selStart)
	{
		wstring s1 = getText(n, selStart - n);
		s1 = s1.substr(0, s1.find_first_not_of(L" \t"));
		s += s1;
	}
	suspendLayout();
	replaceSelection(s);
	selStart += s.length();
	resumeLayout();
	setSelection(selStart, selStart);
}
void CInput::onKeyUp()
{
	selectionChanged = true;
}
void CInput::onLeftButtonUp(bool)
{
	undoManager->setType(UR_TYPING);
	cChildFrame->textEdit = this;
	selectionChanged = true;
	setSearchEdit(this);
}
void CInput::addCommands(wstring str)
{
	size_t len = str.length();
	if (len == 0) return;
	tuneEndOfLines(str);
	int err = removeComments(str);
	if (err == -1)
	{
		len = str.length();
		unsigned int cc = 0;
		wstring s = L";;";
		int pp =  0;
		wchar_t oc = L'\0';
		int inString = 0;
		int location = 0;
		int length = 1;
		bool active = true;
		for (cc = 0; cc < len; oc = str[cc++])
		{
			if (cc < len - 1 && ((oc == L'\'' && str[cc + 1] == L'\'') || (oc == L'`' && str[cc + 1] == L'`'))){}
			else if (!inString)
			{
				if (str[cc] == L'"')
				{
					inString = true; active = false;
				}
			}
			else
			{
				wchar_t ch = str[cc];
				if (oc == L'\\')
				{
					if (ch ==  L'"')
					{
						if (active) {active = false;}
						else {inString = false;}
					}
					else if (ch == L'\\')
					{
						active = !active;
					}
					else
					{
						active = false;
					}
				}
				else if (ch == L'\\')
				{
					active = true;
				}
				else if (ch == L'"')
				{
					inString = false;
					active = false;
				}
			}
			if (inString == 0 && s[pp] == str[cc]) pp++; else pp = 0;
			if (pp == 2)
			{
				pp = 0;
				wstring st = str.substr(location, length) + L"\n";
				cChildFrame->commandArray->push_back(st);
				location += length;
				length = 1;
			}
			else
			{
				length++;
			}
		}
	}
	else
	{
		errorMessage(errorMessages[err]);
	}
}
void CInput::commandRange()
{
	size_t cc = 0;
	wstring s = L";;";
	int pp =  0;
	wchar_t oc = L'\0';
    
	size_t selLoc, selEnd;
	getSelection(selLoc, selEnd);
    
	wstring data = getText();
	size_t len = data.length();
    
	int d = 0;
	int inString = 0;
	size_t location = 0;
	bufferStart = 0;
	int length = 1;
	bool active = true;
	if (len == 0)
	{
		commandStart = 0;
		commandEnd = 0;
		return;
	}
	for (cc = 0; cc < len; oc = data[cc++])
	{
		if (cc < len - 1 && ((oc == L'\'' && data[cc + 1] == L'\'') || (oc == L'`' && data[cc + 1] == L'`'))){}
		else if (inString == 0)
		{
			if (data[cc] == L'"')
			{
				inString = 1; active = false;
			}
		}
		else
		{
			wchar_t ch = data[cc];
			if (oc == L'\\')
			{
				if (ch ==  L'"')
				{
					if (active) {active = false;}
					else {inString = 0;}
				}
				else if (ch == L'\\')
				{
					active = !active;
				}
				else
				{
					active = false;
				}
			}
			else if (ch == L'\\')
			{
				active = true;
			}
			else if (ch == L'"')
			{
				inString = 0;
				active = false;
			}
		}
		if (inString == 0 && data[cc] == L'*' && oc == L'(') {d++; pp = 0;}
		if (inString == 0 && d > 0 && data[cc] == L')' && oc == L'*') d--;
		if (d == 0 && inString == 0 && s[pp] == data[cc]) pp++; else pp = 0;
		if (pp == 2)
		{
			pp = 0;
			bufferStart = cc + 1;
			if (location + length < selLoc)
			{
				location += length;
				length = 1;
			}
			else if (location + length >= selEnd) break;
			else length++;
		}
		else
		{
			length++;
		}
	}
	if (cc == len)
	{
		if (location < bufferStart)
		{
			inBuffer = false;
			commandStart = location;
			commandEnd = bufferStart;
		}
		else
		{
			inBuffer = true;
			commandStart = bufferStart;
			commandEnd = len;
		}
	}
	else
	{
		inBuffer = false;
		commandStart = location;
		commandEnd = location + length;
	}
}
void CInput::sendCommand()
{
	if (!cChildFrame->process)
	{
		cChildFrame->startProcess();
	}
	if (!cChildFrame->process) return;
	commandRange();
	size_t length = commandEnd - commandStart;
	if (inBuffer)
	{
		//size_t length = commandEnd - commandStart;
		size_t selStart;
		size_t selEnd;
		getSelection(selStart, selEnd);
		if (selStart != selEnd)
		{
			wstring theInput = getText(selStart, selEnd - selStart);
			trim(theInput);
			tuneEndOfLines(theInput);
			theInput += L"\n";
			cChildFrame->cOutput->nlCount = 0;
			if (cChildFrame->CamlTop == OCAML)
				for (size_t cc = 0; cc < length; cc++)
				{
					if (theInput[cc] == L'\n')
						cChildFrame->cOutput->nlCount += 2;
				}
				cChildFrame->process->write(::toLatin1(theInput).c_str());
		}
		else if (length != 0)
		{
			//wstring theInput = getText(commandStart, commandEnd - commandStart);
			wstring theInput = getText(commandStart, length);
			tuneEndOfLines(theInput);
			int err = removeComments(theInput);
			if (err != -1)
			{
				errorMessage(errorMessages[err]);
			}
			else
			{
				trim(theInput);
				if (theInput.length() != 0) theInput += L"\n";
				cChildFrame->process->write(::toLatin1(theInput).c_str());
			}
		}
	}
	else
	{
		//wstring s = getText(commandStart, commandEnd - commandStart);
		wstring s = getText(commandStart, length);
		addCommands(s);
		if (commandEnd < getTextLength() - 1)
		{
			setSelection(commandEnd + 1, commandEnd + 1);
		}
		else
		{
			setSelection(commandEnd, commandEnd);
		}
	}
	selectionChanged = true;
}
/*
void CInput::sendSelection()
{
    size_t selStart;
    size_t selEnd;
    getSelection(selStart, selEnd);
    wstring theInput = getText(selStart, selEnd - selStart) + L"\n";
    cChildFrame->process->write(::toLatin1(theInput).c_str());
}
*/
void CInput::undo()
{
	undoManager->undo();
}
void CInput::redo()
{
	undoManager->redo();
}
wstring CInput::status()
{
	size_t line;
	size_t column;
	caretCoordinates(getText(), getCaretPosition(), cChildFrame->SpaceCountPerTab, line, column);
	wstringstream format(wstringstream::in | wstringstream::out);
#ifdef _MSVC98_
	format << L"Entr\xE9"L"e:   Ligne "  << line << L" Colonne " << column;
#else
	format << L"Entr\u00E9e:   Ligne " << line << L" Colonne " << column;
#endif
	return format.str();
}
void CInput::replace(size_t a, size_t l, wstring s)
{
	highlighter->invalidate(a, a + l);
	setText(a, l, s);
	textHasChanged();
}
void CInput::setSelection(size_t selStart, size_t selEnd)
{
	CRichEdit::setSelection(selStart, selEnd);
	selectionChanged = true;
}
void CInput::replaceSelection(wstring s)
{
	size_t startPos, endPos;
	getSelection(startPos, endPos);
	highlighter->invalidate(startPos, endPos);
	setSelectedText(s);
	textHasChanged();
}
void CInput::doCopy()
{
	setTextDefaultBackground(commandStart, commandEnd - commandStart);
	copy();
	setTextBackground(commandStart, commandEnd - commandStart, cChildFrame->InputCommandBackground);
}
void CInput::doCut()
{
	undoManager->setType(UR_CUT);
	cut();
	textHasChanged();
	undoManager->setType(UR_TYPING);
}
void CInput::doPaste()
{
	undoManager->setType(UR_PASTE);
	size_t selStart, selEnd;
	getSelection(selStart, selEnd);
	size_t len = getTextLength();
	paste();
	size_t len1 = getTextLength();
	setTextBackground(commandStart, commandEnd - commandStart, cChildFrame->InputCommandBackground);
	setSelection(selEnd +len1 - len, selEnd +len1 - len);
	textHasChanged();
	undoManager->setType(UR_TYPING);
}
void CInput::indent()
{
	wstring s = getText(commandStart, commandEnd - commandStart);
	int i = cChildFrame->IndentWithTabs ? 0 : cChildFrame->SpaceCountPerTab;
	wstring ss = L"";
    
	if (cChildFrame->UseOCamlIndenter && cChildFrame->CamlTop == OCAML)
	{
		ss = doOCamlIndent(&s, i, cChildFrame->IndentAfterIn, cChildFrame->IndentFilters);
	}
	else
	{
		ss = doIndent(&s, i, cChildFrame->IndentAfterIn, cChildFrame->IndentFilters);
        
	}
    
	if (ss == L"")
	{
		errorMessage(L"erreur d'indentation");
		return;
	}
	if (ss != s)
	{
		undoManager->setSelection();
		undoManager->setType(UR_INDENT);
		replacedText = s;
		highlighter->invalidate(commandStart, commandEnd);
		setText(commandStart, commandEnd - commandStart, ss);
		commandEnd = commandStart + ss.length();
        setSelection(commandEnd, commandEnd);
        highlighter->textChanged = true;
	}
	else
	{
		setSelection(commandEnd, commandEnd);
	}
}
void CInput::textHasChanged()
{
	wstring newstring = getText();
	if (previousString == newstring)
	{
		return;
	}
    
	cChildFrame->setWindowModified(true);
	size_t l1 = previousString.length();
	size_t l2 = newstring.length();
	size_t pre = commonPrefix(previousString, newstring);
	size_t su = commonSuffix(previousString.substr(pre, l1 - pre), newstring.substr(pre, l2 - pre));
    
	replacedText = previousString.substr(pre, l1 - su - pre);
	replacingText = newstring.substr(pre, l2 - su - pre);
	undoManager->add(pre, replacingText, replacedText);
    
	bool negative = l1 > l2;
	size_t d = negative ? l1 - l2 : l2 - l1;
	highlighter->tokenize(pre, l1 - su, d, negative);
    
	previousString.replace(pre, l1 - su - pre, replacingText);
	size_t selStart, selEnd;
	getSelection(selStart, selEnd);
	highlighter->location = selStart;
}
void CInput::setColors()
{
	T[commentColor] = cChildFrame->CommentColor;
	T[delimiterColor] = cChildFrame->DelimiterColor;
	T[stringColor] = cChildFrame->StringColor;
	T[keywordColor] = cChildFrame->KeywordColor;
	T[numberColor] = cChildFrame->NumberColor;
	T[operatorColor] = cChildFrame->OperatorColor;
	T[inputBackground] = cChildFrame->InputCommandBackground;
	T[bufferBackground] = cChildFrame->InputBufferBackground;
	T[otherColor] = cChildFrame->OtherColor;
    T[matchingBackground] = rgb(0, 0, 255);
    CRichEdit::setColors();
}
void CInput::setColor(int colorType)
{
	if (colorType == commentColor) T[colorType] = cChildFrame->CommentColor;
	else if (colorType == delimiterColor) T[colorType] = cChildFrame->DelimiterColor;
	else if (colorType == stringColor) T[colorType] = cChildFrame->StringColor;
	else if (colorType == keywordColor) T[colorType] = cChildFrame->KeywordColor;
	else if (colorType == numberColor) T[colorType] = cChildFrame->NumberColor;
	else if (colorType == operatorColor) T[colorType] = cChildFrame->OperatorColor;
	else if (colorType == inputBackground) T[colorType] = cChildFrame->InputCommandBackground;
	else if (colorType == bufferBackground) T[colorType] = cChildFrame->InputBufferBackground;
	else if (colorType == otherColor) T[colorType] = cChildFrame->OtherColor;
    CRichEdit::setColors();
}
void CInput::removeColors()
{
	for (unsigned int i = 0; i < forecolorCount; i++)
        {
            T[i] = cChildFrame->OtherColor;
        }
    CRichEdit::setColors();
}
void CInput::beginPrint()
{
	CRichEdit::beginPrint();
	highlighter->colorizeAll();
	setTextDefaultBackground(commandStart, commandEnd - commandStart);
	updateView();
	margins.left = cChildFrame->cFrame->LeftMargin;
	margins.right = cChildFrame->cFrame->RightMargin;
	margins.top = cChildFrame->cFrame->TopMargin;
	margins.bottom = cChildFrame->cFrame->BottomMargin;
}
void CInput::endPrint()
{
	setTextBackground(commandStart, commandEnd - commandStart, cChildFrame->InputCommandBackground);
	CRichEdit::endPrint();
}
void CInput::onReplace(const wstring& replaceWith)
{
	undoManager->setType(UR_REPLACE);
	replaceSelection(replaceWith);
	undoManager->setType(UR_TYPING);
}
void CInput::onReplaceAll(const wstring& findWhat, const wstring& replaceWith, unsigned long flags)
{
	undoManager->setType(UR_REPLACE);
    replaceAll(findWhat, replaceWith, (int)flags);
	textHasChanged();
	undoManager->setType(UR_TYPING);
}
