#include <ctype.h>
#include <qpoint.h>
#include <qkeycode.h>
#include <qclipbrd.h>

#include "StateCache.h"
#include "KColorMLE.h"



extern StateCache* stateCache;



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::keyPressEvent(QKeyEvent *e) 
{
    int unknown = 0;


		///////////////////////////////////////////
		// handle readonly keystrokes:    
    if(isReadOnly()) 
    {
    	handleReadOnlyKey(e);
		return;
    }


		///////////////////////////////////////////
		// printable characters:
	if(e->ascii() >= ' ' && e->key() != Key_Delete) 
		inputChar(e->ascii());


		///////////////////////////////////////////
		// simple key (except printables):
	else
    {
		switch(e->key()) 
	    {
			case Key_Tab:
				inputTab();
				break;

			default:
				unknown++;
		}
    }

    if(unknown)        		// unknown key
    {				
		e->ignore();
		return;
    }
}
//////////////////////////////////////////////



//////////////////////////////////////////////
//
void KSyntaxMultiLineEdit::inputChar(char c) 
{
	if(isReadOnly())
		return;
		
	setAutoUpdate(false);
	if(hasMarkedText() && edSettings.overwriteSelection())
	{
		del();	//flickers!
	}
		
	CharInputAction* a = new CharInputAction(this, c);
	UndoRedoList.AddAction(a);

	setAutoUpdate(true);
	makeCursorVisibleImmediatly();

//	emit cursorPosChanged();	// done in insertChar
//    emit textChanged();
}

void KSyntaxMultiLineEdit::inputTab()
{
	if(isReadOnly())
		return;
		
	if(edSettings.tabsAsSpaces())
	{
		setAutoUpdate(false);
		for(int i = 0; i < edSettings.tabSize(); i++)
		{
			CharInputAction* a = new CharInputAction(this, ' ');
			UndoRedoList.AddAction(a);
	}
		setAutoUpdate(true);
	}
	else
	{
		CharInputAction* a = new CharInputAction(this, '\t');
		UndoRedoList.AddAction(a);
	}
	
//	emit cursorPosChanged();	// done in insertChar
//    emit textChanged();
}
//
//////////////////////////////////////////////

//////////////////////////////////////////////
//
void KSyntaxMultiLineEdit::insertAt(const char* s, int line, int col)
{
	int lines = QString(s).contains("\n");
	for(int i = 0; i < lines; i++)
		stateCache->insertLine(line + i);
		
	QMultiLineEdit::insertAt(s, line, col);
	
	int reparseFrom;
	switch(lines)
	{
		case 0:
//		case 1:
			reparseFrom = line; break;
		default:
			reparseFrom = QMAX(line - 1, 0); break;
	}
        
	stateCache->reparseFromHere(reparseFrom);
}

/*void KSyntaxMultiLineEdit::insertLine(const char* s, int line, int col)
{
}*/

/*void KSyntaxMultiLineEdit::removeLine(const char* s, int line, int col)
{
}*/
//
//////////////////////////////////////////////

//////////////////////////////////////////////
//
void KSyntaxMultiLineEdit::_UFStartOfText(bool mark)
{
	int r, c;
	getCursorPosition(&r, &c);
	QPoint p1(c, r), p2(0, 0);
	Action* a = new CursorPlacementAction(this, p1, p2, mark);
	UndoRedoList.AddAction(a);
	
	if(mark)
	{
		warning("mark!");
		repaint();
	}
	makeCursorVisibleImmediatly();
}

void KSyntaxMultiLineEdit::_UFEndOfText(bool mark)
{
	int r, c, r2, c2;
	getCursorPosition(&r, &c);
	r2 = numLines() - 1;
	c2 = getString(r2)->length();
	QPoint p1(c, r), p2(c2, r2);
	Action* a = new CursorPlacementAction(this, p1, p2, mark);
	UndoRedoList.AddAction(a);

	if(mark)
		repaint();
	makeCursorVisibleImmediatly();
}

void KSyntaxMultiLineEdit::_UFWordLeft(bool mark)
{
	wordLeft(mark);
}

void KSyntaxMultiLineEdit::_UFWordRight(bool mark)
{
	wordRight(mark);
}

void KSyntaxMultiLineEdit::_UFCursorLeft(bool mark)
{
	Action* a = new CursorMovementAction(this, CursorMovementAction::DirLeft, mark);	
	UndoRedoList.AddAction(a);
	makeCursorVisibleImmediatly();
}

void KSyntaxMultiLineEdit::_UFCursorRight(bool mark)
{
	Action* a = new CursorMovementAction(this, CursorMovementAction::DirRight, mark);
	UndoRedoList.AddAction(a);
	makeCursorVisibleImmediatly();
}

void KSyntaxMultiLineEdit::_UFCursorUp(bool mark)
{
	Action* a = new CursorMovementAction(this, CursorMovementAction::DirUp, mark);	
	UndoRedoList.AddAction(a);
	makeCursorVisibleImmediatly();
}

void KSyntaxMultiLineEdit::_UFCursorDown(bool mark)
{
	Action* a = new CursorMovementAction(this, CursorMovementAction::DirDown, mark);	
	UndoRedoList.AddAction(a);
	makeCursorVisibleImmediatly();
}

void KSyntaxMultiLineEdit::_UFStartOfLine(bool mark)
{
	int r, c;
	getCursorPosition(&r, &c);
	QPoint p1(c, r), p2(0, r);
	Action* a = new CursorPlacementAction(this, p1, p2, mark);
	UndoRedoList.AddAction(a);
	makeCursorVisibleImmediatly();
}

void KSyntaxMultiLineEdit::_UFEndOfLine(bool mark)
{
	int r, c;
	getCursorPosition(&r, &c);
	QPoint p1(c, r), p2(getString(r)->length(), r);
	Action* a = new CursorPlacementAction(this, p1, p2, mark);
	UndoRedoList.AddAction(a);
	makeCursorVisibleImmediatly();
}

void KSyntaxMultiLineEdit::_UFPageDown(bool mark)
{
		// action created in pageDown()!
	pageDown(mark);	
}

void KSyntaxMultiLineEdit::_UFPageUp(bool mark)
{
		// action created in pageUp()!
	pageUp(mark);	
}
//
//////////////////////////////////////////////



//////////////////////////////////////////////
//
	// these two functions must not generate an
	// entry in the UndoRedoList, since their
	// purpose is to move among this List (and not
	// to modify it):
void KSyntaxMultiLineEdit::UFUndo()
{ 
		// returns true, if the first Actions is undone:
	if(!UndoRedoList.Undo())
		setDirty(false);
//	setDirty(UndoRedoList.IsDirty());
}

void KSyntaxMultiLineEdit::UFRedo()
{ 
        // this will set the dirty flag on every Redo()!
/*    if(UndoRedoList.Redo())
    {
        warning("doc set dirty!");
        setDirty(true);
    }
*/    
	UndoRedoList.Redo();
	setDirty(true);
//    setDirty(UndoRedoList.IsDirty());
}
//
//////////////////////////////////////////////



//////////////////////////////////////////////
// actually not included in undo list:
void KSyntaxMultiLineEdit::UFBackspace()
{
	int row, col;			// marking!
	getCursorPosition(&row, &col);
	QString* s = getString(row);
	Action* a = new BackspaceAction(this, col == 0 ? '\n' : (*s)[col - 1]);
	UndoRedoList.AddAction(a);
}

void KSyntaxMultiLineEdit::UFDelete()
{
	Action* a;
	int row, col;			
	getCursorPosition(&row, &col);
	if(hasMarkedText())
	{
		QString m = markedText();
		a = new DeleteAction(this, m);
//		QString* s = getString(row);
//		a = new DeleteAction(this, (unsigned int)col == s->length() ? '\n' : (*s)[col]);
	}
	else
	{
		QString* s = getString(row);
		a = new DeleteAction(this, (unsigned int)col == s->length() ? '\n' : (*s)[col]);
	}
	UndoRedoList.AddAction(a);
}

void KSyntaxMultiLineEdit::UFPaste()
{
	warning("paste");
	Action* a = new ClipboardAction(this, ClipboardAction::PASTE);
	UndoRedoList.AddAction(a);
}

void KSyntaxMultiLineEdit::UFCopy()
{
	if(hasMarkedText()) 
	{
		warning("copyText");
		Action* a = new ClipboardAction(this, ClipboardAction::COPY);
		UndoRedoList.AddAction(a);
	}
}

void KSyntaxMultiLineEdit::UFCut()
{
	warning("cut");
	Action* a = new ClipboardAction(this, ClipboardAction::CUT);
	UndoRedoList.AddAction(a);
}

void KSyntaxMultiLineEdit::UFKillLine()
{
	killLine();
}

void KSyntaxMultiLineEdit::UFNewLine()
{
//    newLine();

	Action* a = new NewlineAction(this);
	UndoRedoList.AddAction(a);
	emit returnPressed();
}

void KSyntaxMultiLineEdit::UFInsertToggle()
{
	setOverwriteMode(!isOverwriteMode());
	emit insModeChanged();
}
//
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::handleReadOnlyKey(QKeyEvent* e)
{
	int pageSize = viewHeight() / cellHeight();

	switch(e->key()) 
	{
		case Key_Left:
    		setXOffset(xOffset() - viewWidth()/10);
		    break;
		case Key_Right:
    		setXOffset(xOffset() + viewWidth()/10);
		    break;
		case Key_Up:
    		setTopCell(topCell() - 1);
		    break;
		case Key_Down:
    		setTopCell(topCell() + 1);
		    break;
		case Key_Next:
    		setTopCell(topCell() + pageSize);
		    break;
		case Key_Prior:
    		setTopCell(QMAX(topCell() - pageSize, 0));
		    break;
		default:
		    e->ignore();
	}
}
//////////////////////////////////////////////


//////////////////////////////////////////////
void KSyntaxMultiLineEdit::insertChar(char c)
{
	int x, y;
	getCursorPosition(&y, &x);
    QString *s = getString(y);
    if(x > (int)s->length())
		x = s->length();
		
	setAutoUpdate(false);
	if(isOverwriteMode())		// Overwrite mode
	{
		char sr[2];
		sr[0] = c; sr[1] = 0;
		s->replace(x, 1, sr);
	}
	else						// Insertion mode
	    s->insert(x, c);
	setAutoUpdate(true);

    int lastParsed = stateCache->reparseFromHere(y);
	updateAllNewStateVisibleRowsFrom(y, lastParsed);

    setCellWidth(QMAX(cellWidth(), textWidth(s)));
    cursorRight(false);	
    makeCursorVisibleImmediatly();
    
	emit cursorPosChanged();
	emit textChanged(); 
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::newLine(bool doAutoIndent)
{
	setAutoUpdate(false);
	
    int cx, cy;
    getCursorPosition(&cy, &cx);
    QString *s = getString(cy);
    bool recalc = cx != (int)s->length() && textWidth(s) == cellWidth();
    
	QString newString = s->mid(cx, s->length());
    s->remove(cx, s->length());
		
	unsigned int tabPos = 0;
	if(doAutoIndent && edSettings.autoIndent() && s->length() != 0)
	{
			// increment tabPos until nonspace character found:
		while(isspace((*s)[tabPos]) && tabPos < s->length())
			tabPos++;
    
		if(tabPos)
			newString = s->left(tabPos) + newString;
	}

		// insert autoindented or unmodified string:
	insertLine(newString, cy + 1);

		// this is a quick hack. It works, since calling
		// reparse() function twice could lead to no more
		// than parsing on line twice; well, this is not good,
		// but it's acceptable:
    stateCache->insertLine(cy, 0);
    stateCache->reparseFromHere(cy);
    stateCache->reparseFromHere(cy+1);

	setAutoUpdate(true);
    updateAllVisibleRowsFrom(cy);
	
		// Cursor to indent position
	setCursorPosition(cy + 1, tabPos);
	
		// to make the cursor visible at once in its new position:
	makeCursorVisibleImmediatly();	
	
	emit textChanged();
   	emit returnPressed();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::killLine()
{
	int x, y;
	getCursorPosition(&y, &x);
    QString *s = getString(y);
        
    if(x == (int)s->length()) 	// at the end of the line this function
    {							// works like a del()!
		del();
		return;
    } 
    else 
    {    
    	setCursorPosition(y, x, false);	// turns mark off
    	bool recalc = textWidth(s) == cellWidth();
		s->remove(x, s->length());

//		if(recalc)
//			updateCellWidth();
	    int lastParsed = stateCache->reparseFromHere(y);
		updateAllNewStateVisibleRowsFrom(y, lastParsed);
	    makeCursorVisibleImmediatly();
	    emit textChanged();
    }
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::backspace()
{
    if(hasMarkedText())
		del();
    else 
    {
		if(!atBeginning()) 
		{
		    cursorLeft(false);
		    del();
		}
    }
    makeCursorVisibleImmediatly();
    emit cursorPosChanged();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::del()
{
	int x, y;
	getCursorPosition(&y, &x);
	
    if(hasMarkedText()) 								// marked text
    {
    	int x1, y1, x2, y2;
		mark.getMark(x1, y1, x2, y2);
		setAutoUpdate(false);
		if(y1 == y2) 									// just one line marked?
		{ 
		    QString *s  = getString(y1);
		    ASSERT(s);
		    s->remove(x1, x2 - x1);
//updateCellWidth();

			stateCache->reparseFromHere(y1);
			updateAllVisibleRowsFrom(y1);
		    setCursorPosition(y1, x1, false);
		} 
		else 											// multiline!
		{ 
		    QString *fS = getString(y1), 
		    		*lS = getString(y2);

		    ASSERT(y1 >= 0);
		    ASSERT(y2 < (int)numLines());
		    ASSERT(fS != lS);

				// this deselects the marked text:
		    setCursorPosition(y2, x2, false);

		    fS->remove(x1, fS->length() - x1);
		    lS->remove(0, x2);
		    fS->append(*lS);

		    for(int yy = y1 + 1 ; yy <= y2; yy++)
		    {
		    	removeLine(y1 + 1);
				stateCache->removeLine(y1 + 1);
		    }
		    if(!numLines())
		    {
				stateCache->insertLine(0, 0);
		    	insertLine("", -1);
		    }
	
			stateCache->reparseFromHere(y1);
			updateAllVisibleRowsFrom(y1);
		    setCursorPosition(y1, x1, false);
		}
//		updateCellWidth();
		setAutoUpdate(true);
    } 
    else 												// no marked text
    {
		if(atEnd()) 
			return;
	    QString *s = getString(y);
	    if(x == (int)s->length()) 						// remove \n character
	    { 
			*s += *getString(y + 1);
//			setCellWidth(QMAX(cellWidth(), textWidth(s)));
			setAutoUpdate(false);
			removeLine(y + 1);
			setAutoUpdate(true);
			
			stateCache->removeLine(y + 1);
			stateCache->reparseFromHere(y);
			updateAllVisibleRowsFrom(y);
	    } 
	    else 											// remove printable character
	    {
			bool recalc = textWidth(s) == cellWidth();
			s->remove(x, 1);
//			if (recalc)
//			    updateCellWidth();
			int lastParsed = stateCache->reparseFromHere(y);
			updateAllNewStateVisibleRowsFrom(y, lastParsed);
		}
    }
    makeCursorVisibleImmediatly();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::cut()
{
	if(hasMarkedText())
	{
		emit textChanged();
		copyText();
		del();
	}
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::copyText()
{
    QString s = markedText();
    if(!s.isEmpty())
        QApplication::clipboard()->setText(s);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::paste()
{
	int y;
	getCursorPosition(&y, 0);
	setAutoUpdate(false);

		// call the troll's function:
	QMultiLineEdit::paste();

		// make pasted text visible:
	updateAllVisibleRowsFrom(y);

	setAutoUpdate(true);
	emit textChanged();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::clear()
{
	deleteStateCache();
	QMultiLineEdit::clear();
	initStateCache();
	
	UndoRedoList.Clear();
	repaint(true);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::setText(const char* newText)
{
	deleteStateCache();
	QMultiLineEdit::setText(newText);
	initStateCache();
	repaint(true);
}
//////////////////////////////////////////////




//////////////////////////////////////////////
void KSyntaxMultiLineEdit::updateAllVisibleRowsFrom(int row)
{
	int to = lastRowVisible();
    for(int y = row; y <= to; y++)
    	updateCell(y, 0, false);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::updateAllNewStateVisibleRowsFrom(int from, int to)
{
	to = QMIN(lastRowVisible(), to);
    for(int y = from; y <= to; y++)
    	updateCell(y, 0, false);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::textChangedSlot()
{
//	warning("KSyntaxMultiLineEdit::textChangedSlot()");
    setDirty();
}
//////////////////////////////////////////////

