/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
    aneditor-indent.cxx
    Copyright (C) 2004 Naba Kumar

    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 2 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, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "aneditor-priv.h"

void AnEditor::IndentationIncrease(){
	CharacterRange crange = GetSelection();
	if (crange.cpMin != crange.cpMax)
	{
		SendEditor (SCI_TAB);
		return;
	}
	int line =SendEditor(SCI_LINEFROMPOSITION, SendEditor (SCI_GETCURRENTPOS));
	int indent =GetLineIndentation(line);
	indent +=SendEditor(SCI_GETINDENT);
	SetLineIndentation(line, indent);
}

void AnEditor::IndentationDecrease(){
	CharacterRange crange = GetSelection();
	if (crange.cpMin != crange.cpMax)
	{
		SendEditor (SCI_BACKTAB);
		return;
	}
	int line =SendEditor(SCI_LINEFROMPOSITION, SendEditor (SCI_GETCURRENTPOS));
	int indent = GetLineIndentation(line);
	indent -=SendEditor(SCI_GETINDENT);
	if (indent < 0) indent = 0;
	SetLineIndentation(line, indent);
}

void AnEditor::SetLineIndentation(int line, int indent) {
	if (indent < 0)
		return;
	CharacterRange crange = GetSelection();
	int posBefore = GetLineIndentPosition(line);
	SendEditor(SCI_SETLINEINDENTATION, line, indent);
	int posAfter = GetLineIndentPosition(line);
	int posDifference =  posAfter - posBefore;
	if (posAfter > posBefore) {
		// Move selection on
		if (crange.cpMin >= posBefore) {
			crange.cpMin += posDifference;
		}
		if (crange.cpMax >= posBefore) {
			crange.cpMax += posDifference;
		}
	} else if (posAfter < posBefore) {
		// Move selection back
		if (crange.cpMin >= posAfter) {
			if (crange.cpMin >= posBefore)
				crange.cpMin += posDifference;
			else
				crange.cpMin = posAfter;
		}
		if (crange.cpMax >= posAfter) {
			if (crange.cpMax >= posBefore)
				crange.cpMax += posDifference;
			else
				crange.cpMax = posAfter;
		}
	}
	SetSelection(crange.cpMin, crange.cpMax);
}

int AnEditor::GetLineIndentation(int line) {
	return SendEditor(SCI_GETLINEINDENTATION, line);
}

int AnEditor::GetLineIndentPosition(int line) {
	return SendEditor(SCI_GETLINEINDENTPOSITION, line);
}

bool AnEditor::RangeIsAllWhitespace(int start, int end) {
	char *buffer = new char [end - start + 1];
	bool all_white = true;
	
	GetRange(wEditor, start, end, buffer);
	
	for (int i = start;i < end;i++) {
		if ((buffer[i] != ' ') && (buffer[i] != '\t'))
			all_white = false;
			break;
	}

	return all_white;
}

#if 0

unsigned int AnEditor::GetLinePartsInStyle(int line, int style1, int style2,
										   SString sv[], int len) {
	for (int i = 0; i < len; i++)
		sv[i] = "";
	WindowAccessor acc(wEditor.GetID(), *props);
	SString s;
	int part = 0;
	int thisLineStart = SendEditor(SCI_POSITIONFROMLINE, line);
	int nextLineStart = SendEditor(SCI_POSITIONFROMLINE, line + 1);
	for (int pos = thisLineStart; pos < nextLineStart; pos++) {
		if ((acc.StyleAt(pos) == style1) || (acc.StyleAt(pos) == style2)) {
			char c[2];
			c[0] = acc[pos];
			c[1] = '\0';
			s += c;
		} else if (s.length() > 0) {
			if (part < len) {
				sv[part++] = s;
			}
			s = "";
		}
	}
	if ((s.length() > 0) && (part < len)) {
		sv[part++] = s;
	}
	return part;
}

static bool includes(const StyleAndWords &symbols, const SString value) {
	if (symbols.words.length() == 0) {
		return false;
	} else if (IsAlphabetic(symbols.words[0])) {
		// Set of symbols separated by spaces
		size_t lenVal = value.length();
		const char *symbol = symbols.words.c_str();
		while (symbol) {
			const char *symbolEnd = strchr(symbol, ' ');
			size_t lenSymbol = strlen(symbol);
			if (symbolEnd)
				lenSymbol = symbolEnd - symbol;
			if (lenSymbol == lenVal) {
				if (strncmp(symbol, value.c_str(), lenSymbol) == 0) {
					return true;
				}
			}
			symbol = symbolEnd;
			if (symbol)
				symbol++;
		}
	} else {
		// Set of individual characters. Only one character allowed for now
		char ch = symbols.words[0];
		return strchr(value.c_str(), ch) != 0;
	}
	return false;
}

#define ELEMENTS(a)	(sizeof(a) / sizeof(a[0]))

IndentationStatus AnEditor::GetIndentState(int line) {
	// C like language indentation defined by braces and keywords
	IndentationStatus indentState = isNone;
	SString controlWords[20];
	unsigned int parts = GetLinePartsInStyle(line, statementIndent.styleNumber,
		-1, controlWords, ELEMENTS(controlWords));
	for (unsigned int i = 0; i < parts; i++) {
		if (includes(statementIndent, controlWords[i]))
			indentState = isKeyWordStart;
	}
	// Braces override keywords
	SString controlStrings[20];
	parts = GetLinePartsInStyle(line, blockEnd.styleNumber,
		-1, controlStrings, ELEMENTS(controlStrings));
	for (unsigned int j = 0; j < parts; j++) {
		if (includes(blockEnd, controlStrings[j]))
			indentState = isBlockEnd;
		if (includes(blockStart, controlStrings[j]))
			indentState = isBlockStart;
	}
	return indentState;
}

int AnEditor::IndentOfBlockProper(int line) {
	
	if (line < 0)
		return 0;
	
	int indentSize = SendEditor(SCI_GETINDENT);
	int indentBlock = GetLineIndentation(line);
	int minIndent = indentBlock;
	int backLine = line;
	
	IndentationStatus indentState = isNone;
	if (statementIndent.IsEmpty() && blockStart.IsEmpty() && blockEnd.IsEmpty())
		indentState = isBlockStart;	// Don't bother searching backwards

	int lineLimit = line - statementLookback;
	if (lineLimit < 0)
		lineLimit = 0;
	bool noIndentFound = true;
	while ((backLine >= lineLimit) && (indentState == 0)) {
		indentState = GetIndentState(backLine);
		if (indentState != 0) {
			noIndentFound = false;
			indentBlock = GetLineIndentation(backLine);
			if (indentState == isBlockStart) {
				if (!indentOpening)
					indentBlock += indentSize;
			}
			if (indentState == isBlockEnd) {
				if (indentClosing)
					indentBlock -= indentSize;
				if (indentBlock < 0)
					indentBlock = 0;
			}
			if ((indentState == isKeyWordStart) && (backLine == line))
				indentBlock += indentSize;
		} else if (noIndentFound) {
			int currentIndent = GetLineIndentation(backLine);
			minIndent = MIN(minIndent, currentIndent);
		}
		backLine--;
	}
	if (noIndentFound && minIndent > 0)
		indentBlock = minIndent;
	return indentBlock;
}

int AnEditor::IndentOfBlock(int line) {

	int backLine = line;

	/* Find mismatched parenthesis */
	if (lexLanguage == SCLEX_CPP) {
		WindowAccessor acc(wEditor.GetID(), *props);
		int currentPos = SendEditor(SCI_POSITIONFROMLINE, backLine + 1);
		
		while (backLine >= 0) {
			int thisLineStart = SendEditor(SCI_POSITIONFROMLINE, backLine);
			int nextLineStart = currentPos;
			// printf("Scanning at line: %d (%d)\n", backLine, currentPos);
	
			int foundTerminator = false;
			int pos;
			for (pos = nextLineStart - 1; pos >= thisLineStart; pos--) {
	
				// printf ("Style at %d = %d\n", pos, acc.StyleAt(pos));
	
				if ((acc.StyleAt(pos) == 10)) {
					char c[2];
					c[0] = acc[pos];
					c[1] = '\0';
					// printf ("Looking at: \"%s\"\n", c);
					if (includes(blockEnd, c) ||
						includes(blockStart, c) ||
						includes(statementEnd, c))
					{
						// printf ("Found block start/block end/statement end. Breaking\n");
						foundTerminator = true;
						break;
					}
					if (c[0] == '(' || c[0] == '[') {
						int indentBlock = SendEditor(SCI_GETCOLUMN, pos) + 1;// - thisLineStart;
						// printf ("Found Unmatched '(' at col: %d. Returning\n", indentBlock);
						return indentBlock;
					}
					if (c[0] == ')' || c[0] == ']') {
						int braceOpp;
						int lineOpp;
						braceOpp = SendEditor(SCI_BRACEMATCH, pos, 0);
						// printf ("Brace opp. at %d\n", braceOpp);
						if (braceOpp >= 0) {
							currentPos = braceOpp - 1;
							lineOpp = SendEditor (SCI_LINEFROMPOSITION,
												  braceOpp);
							if (backLine != lineOpp) {
								backLine = lineOpp;
								// printf ("Jumping to line %d (%d)\n", backLine, currentPos);
								break;
							} else {
								pos = currentPos;
							}
						} else {
							foundTerminator = true;
							break;
						}
					}
				} else if ((acc.StyleAt(pos) == statementIndent.styleNumber) &&
						   (acc.StyleAt(pos+1) != statementIndent.styleNumber)) {
					char buffer[128];
					if (GetWordAtPosition (buffer, 128, pos)) {
						if (includes (statementIndent, buffer)) {
							printf ("Found keyword terminator before unmatched (\n");
							foundTerminator = true;
							break;
						}
					}
				}
			}
			if (foundTerminator)
				break;
			if (pos < thisLineStart) {
				backLine--;
				currentPos = pos;
			}
		}
	}
	return IndentOfBlockProper(backLine);
}
#endif

void AnEditor::MaintainIndentation(char ch) {
	int eolMode = SendEditor(SCI_GETEOLMODE);
	int curLine = GetCurrentLineNumber();
	int lastLine = curLine - 1;
	int indentAmount = 0;

	if (((eolMode == SC_EOL_CRLF || eolMode == SC_EOL_LF) && ch == '\n') ||
		 (eolMode == SC_EOL_CR && ch == '\r')) {
		if (props->GetInt("indent.automatic")) {
			while (lastLine >= 0 && GetLineLength(lastLine) == 0)
				lastLine--;
		}
		if (lastLine >= 0) {
			indentAmount = GetLineIndentation(lastLine);
		}
		if (indentAmount > 0) {
			SetLineIndentation(curLine, indentAmount);
		}
	}
}

#if 0
void AnEditor::AutomaticIndentation(char ch) {
	CharacterRange crange = GetSelection();
	int selStart = crange.cpMin;
	int curLine = GetCurrentLineNumber();
	int thisLineStart = SendEditor(SCI_POSITIONFROMLINE, curLine);
	int indentSize = SendEditor(SCI_GETINDENT);

	if (blockEnd.IsSingleChar() && ch == blockEnd.words[0]) {
		// Dedent maybe
		if (!indentClosing) {
			if (RangeIsAllWhitespace(thisLineStart, selStart - 1)) {
				int indentBlock = IndentOfBlockProper(curLine - 1);
				SetLineIndentation(curLine, indentBlock - indentSize);
			}
		}
	} else if (!blockEnd.IsSingleChar() && (ch == ' ')) {
		// Dedent maybe
		if (!indentClosing && (GetIndentState(curLine) == isBlockEnd)) {}
	} else if (ch == blockStart.words[0]) {
		// Dedent maybe if first on line and previous line was starting keyword
		if (!indentOpening &&
			(GetIndentState(curLine - 1) == isKeyWordStart)) {
			if (RangeIsAllWhitespace(thisLineStart, selStart - 1)) {
				int indentBlock = IndentOfBlockProper(curLine - 1);
				SetLineIndentation(curLine, indentBlock - indentSize);
			}
		}
	} else if ((ch == '\r' || ch == '\n') && (selStart == thisLineStart)) {
		// printf("New line block\n");
		int indentBlock = IndentOfBlock(curLine - 1);
		if (!indentClosing && !blockEnd.IsSingleChar()) {
			// Dedent previous line maybe
			
			SString controlWords[1];
			// printf ("First if\n");
			
			if (GetLinePartsInStyle(curLine-1, blockEnd.styleNumber,
				-1, controlWords, ELEMENTS(controlWords))) {
				// printf ("Second if\n");
				if (includes(blockEnd, controlWords[0])) {
					// printf ("Third if\n");
					// Check if first keyword on line is an ender
					SetLineIndentation(curLine-1,
									   IndentOfBlockProper(curLine-2)
									   - indentSize);
					// Recalculate as may have changed previous line
					indentBlock = IndentOfBlock(curLine - 1);
				}
			}
		}
		SetLineIndentation(curLine, indentBlock);
		
		// Home cursor.
		if (SendEditor (SCI_GETCOLUMN,
						SendEditor(SCI_GETCURRENTPOS)) < indentBlock)
			SendEditor (SCI_VCHOME);
		
	} else if (lexLanguage == SCLEX_CPP) {
		if ((ch == '\t')) {
		
			int indentBlock = IndentOfBlock(curLine - 1);
			int indentState = GetIndentState (curLine);
			
			if (blockStart.IsSingleChar() && indentState == isBlockStart) {
				if (!indentOpening) {
					if (RangeIsAllWhitespace(thisLineStart, selStart - 1)) {
						// int indentBlock = IndentOfBlockProper(curLine - 1);
						SetLineIndentation(curLine, indentBlock - indentSize);
					}
				}
			} else if (blockEnd.IsSingleChar() && indentState == isBlockEnd) {
				if (!indentClosing) {
					if (RangeIsAllWhitespace(thisLineStart, selStart - 1)) {
						// int indentBlock = IndentOfBlockProper(curLine - 1);
						SetLineIndentation(curLine, indentBlock - indentSize);
					}
				}
			} else {
				SetLineIndentation(curLine, indentBlock);
			}
			
			// Home cursor.
			if (SendEditor (SCI_GETCOLUMN,
							SendEditor(SCI_GETCURRENTPOS)) < indentBlock)
				SendEditor (SCI_VCHOME);
		}
	}
}
#endif
