#include "QGCMAVLinkTextEdit.h"
#include <QMessageBox>
#include <QMenu>
#include <QEvent>
#include <QPainter>
#include <QScrollBar>
#include <QTextLayout>

QGCMAVLinkTextEdit::QGCMAVLinkTextEdit(QWidget *parent)
: QTextEdit(parent)
{
    setViewportMargins(50, 0, 0, 0);
    highlight = new XmlHighlighter(document());
    setLineWrapMode ( QTextEdit::NoWrap );
    setAcceptRichText ( false );
    connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(update()));
    connect(this, SIGNAL(textChanged()), this, SLOT(update()));
}


bool QGCMAVLinkTextEdit::Conform()
{
    QString errorStr;
    int errorLine, errorColumn;
    QDomDocument doc;
    return doc.setContent(text(),false, &errorStr, &errorLine, &errorColumn);
}

QDomDocument QGCMAVLinkTextEdit::xml_document()
{
    QString errorStr;
    int errorLine, errorColumn;
    QDomDocument doc;
    doc.setContent(text(),false, &errorStr, &errorLine, &errorColumn);
    return doc;
}


void QGCMAVLinkTextEdit::setPlainText( const QString txt )
{
    QString errorStr;
    int errorLine, errorColumn;
    QDomDocument doc;
    if (!doc.setContent(txt,false, &errorStr, &errorLine, &errorColumn)) {
        QTextEdit::setPlainText(txt);
    } else {
        QTextEdit::setPlainText(doc.toString(5));
    }
}

bool QGCMAVLinkTextEdit::syntaxcheck()
{
    bool noError = true;
    if (text().size() > 0 ) {
        QString errorStr;
        int errorLine, errorColumn;
        QDomDocument doc;
        if (!doc.setContent(text(),false, &errorStr, &errorLine, &errorColumn)) {
            //////return doc.toString(5);
            QMessageBox::critical(0, tr("Found xml error"),tr("Check line %1 column %2 on string \"%3\"!")
								  .arg(errorLine - 1)
								  .arg(errorColumn - 1)
								  .arg(errorStr));
            noError = false;
			
            // FIXME Mark line
            if (errorLine >= 0 ) {
				
            }
        } else {
            QMessageBox::information(0, tr("XML valid."),tr("All tags are valid. Document size is %1 characters.").arg(text().size()));
            setPlainText(doc.toString(5));
        }
    } else {
        QMessageBox::information(0, tr("XML not found!"),tr("Null size xml document!"));
        noError = false;
    }
	return noError;
}

void QGCMAVLinkTextEdit::contextMenuEvent ( QContextMenuEvent * e )
{
	Q_UNUSED(e);
    QMenu *RContext = createOwnStandardContextMenu();
    RContext->exec(QCursor::pos());
    delete RContext;
}

QMenu *QGCMAVLinkTextEdit::createOwnStandardContextMenu()
{
    QMenu *TContext = createStandardContextMenu();
    TContext->addAction(QIcon(QString::fromUtf8(":/img/zoomin.png")),tr( "Zoom In" ), this , SLOT( zoomIn() ) );
    TContext->addAction(QIcon(QString::fromUtf8(":/img/zoomout.png")),tr( "Zoom Out" ), this , SLOT( zoomOut() ) );
    TContext->addAction(tr("Check xml syntax" ), this , SLOT( syntaxcheck() ) );
    return TContext;
}

bool QGCMAVLinkTextEdit::event( QEvent *event )
{
    if (event->type()==QEvent::Paint) {
        QPainter p(this);
        p.fillRect(0, 0, 50, height(), QColor("#636363"));
        QFont workfont(font());
        QPen pen(QColor("#ffffff"),1);
        p.setPen(pen);
        p.setFont (workfont);
        int contentsY = verticalScrollBar()->value();
        qreal pageBottom = contentsY+viewport()->height();
        int m_lineNumber(1);
        const QFontMetrics fm=fontMetrics();
        const int ascent = fontMetrics().ascent() +1;
		
        for (QTextBlock block=document()->begin(); block.isValid(); block=block.next(), m_lineNumber++) {
            QTextLayout *layout = block.layout();
            const QRectF boundingRect = layout->boundingRect();
            QPointF position = layout->position();
            if ( position.y() +boundingRect.height() < contentsY ) {
                continue;
            }
            if ( position.y() > pageBottom ) {
                break;
            }
            const QString txt = QString::number(m_lineNumber);
            p.drawText(50-fm.width(txt)-2, qRound(position.y())-contentsY+ascent, txt);
			
        }
        p.setPen(QPen(Qt::NoPen));
    } else if ( event->type() == QEvent::KeyPress ) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(event);
        if ((ke->modifiers() & Qt::ControlModifier) && ke->key() == Qt::Key_Minus)   {
            QTextEdit::zoomOut();
            return true;
        }
        if ((ke->modifiers() & Qt::ControlModifier) && ke->key() == Qt::Key_Plus)   {
            QTextEdit::zoomIn();
            return true;
        }
    }
    return QTextEdit::event(event);
}















static const QColor DEFAULT_SYNTAX_CHAR		= Qt::blue;
static const QColor DEFAULT_ELEMENT_NAME	= Qt::darkRed;
static const QColor DEFAULT_COMMENT			= Qt::darkGreen;
static const QColor DEFAULT_ATTRIBUTE_NAME	= Qt::red;
static const QColor DEFAULT_ATTRIBUTE_VALUE	= Qt::darkGreen;
static const QColor DEFAULT_ERROR			= Qt::darkMagenta;
static const QColor DEFAULT_OTHER			= Qt::black;

// Regular expressions for parsing XML borrowed from:
// http://www.cs.sfu.ca/~cameron/REX.html
static const QString EXPR_COMMENT			= "<!--[^-]*-([^-][^-]*-)*->";
static const QString EXPR_COMMENT_BEGIN		= "<!--";
static const QString EXPR_COMMENT_END		= "[^-]*-([^-][^-]*-)*->";
static const QString EXPR_ATTRIBUTE_VALUE	= "\"[^<\"]*\"|'[^<']*'";
static const QString EXPR_NAME				= "([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*";

XmlHighlighter::XmlHighlighter(QObject* parent)
: QSyntaxHighlighter(parent)
{
    init();
}

XmlHighlighter::XmlHighlighter(QTextDocument* parent)
: QSyntaxHighlighter(parent)
{
    init();
}

XmlHighlighter::XmlHighlighter(QTextEdit* parent)
: QSyntaxHighlighter(parent)
{
    init();
}

XmlHighlighter::~XmlHighlighter()
{
}

void XmlHighlighter::init()
{
    fmtSyntaxChar.setForeground(DEFAULT_SYNTAX_CHAR);
    fmtElementName.setForeground(DEFAULT_ELEMENT_NAME);
    fmtComment.setForeground(DEFAULT_COMMENT);
    fmtAttributeName.setForeground(DEFAULT_ATTRIBUTE_NAME);
    fmtAttributeValue.setForeground(DEFAULT_ATTRIBUTE_VALUE);
    fmtError.setForeground(DEFAULT_ERROR);
    fmtOther.setForeground(DEFAULT_OTHER);
}

void XmlHighlighter::setHighlightColor(HighlightType type, QColor color, bool foreground)
{
    QTextCharFormat format;
    if (foreground)
        format.setForeground(color);
    else
        format.setBackground(color);
    setHighlightFormat(type, format);
}

void XmlHighlighter::setHighlightFormat(HighlightType type, QTextCharFormat format)
{
    switch (type)
    {
		case SyntaxChar:
			fmtSyntaxChar = format;
			break;
		case ElementName:
			fmtElementName = format;
			break;
		case Comment:
			fmtComment = format;
			break;
		case AttributeName:
			fmtAttributeName = format;
			break;
		case AttributeValue:
			fmtAttributeValue = format;
			break;
		case Error:
			fmtError = format;
			break;
		case Other:
			fmtOther = format;
			break;
    }
    rehighlight();
}

void XmlHighlighter::highlightBlock(const QString& text)
{
    int i = 0;
    int pos = 0;
    int brackets = 0;
	
    state = (previousBlockState() == InElement ? ExpectAttributeOrEndOfElement : NoState);
	
    if (previousBlockState() == InComment)
    {
        // search for the end of the comment
        QRegExp expression(EXPR_COMMENT_END);
        pos = expression.indexIn(text, i);
		
        if (pos >= 0)
        {
            // end comment found
            const int iLength = expression.matchedLength();
            setFormat(0, iLength - 3, fmtComment);
            setFormat(iLength - 3, 3, fmtSyntaxChar);
            i += iLength; // skip comment
        }
        else
        {
            // in comment
            setFormat(0, text.length(), fmtComment);
            setCurrentBlockState(InComment);
            return;
        }
    }
	
    for (; i < text.length(); i++)
    {
        switch (text.at(i).toAscii())
        {
			case '<':
				brackets++;
				if (brackets == 1)
				{
					setFormat(i, 1, fmtSyntaxChar);
					state = ExpectElementNameOrSlash;
				}
				else
				{
					// wrong bracket nesting
					setFormat(i, 1, fmtError);
				}
				break;
				
			case '>':
				brackets--;
				if (brackets == 0)
				{
					setFormat(i, 1, fmtSyntaxChar);
				}
				else
				{
					// wrong bracket nesting
					setFormat( i, 1, fmtError);
				}
				state = NoState;
				break;
				
			case '/':
				if (state == ExpectElementNameOrSlash)
				{
					state = ExpectElementName;
					setFormat(i, 1, fmtSyntaxChar);
				}
				else
				{
					if (state == ExpectAttributeOrEndOfElement)
					{
						setFormat(i, 1, fmtSyntaxChar);
					}
					else
					{
						processDefaultText(i, text);
					}
				}
				break;
				
			case '=':
				if (state == ExpectEqual)
				{
					state = ExpectAttributeValue;
					setFormat(i, 1, fmtOther);
				}
				else
				{
					processDefaultText(i, text);
				}
				break;
				
			case '\'':
			case '\"':
				if (state == ExpectAttributeValue)
				{
					// search attribute value
					QRegExp expression(EXPR_ATTRIBUTE_VALUE);
					pos = expression.indexIn(text, i);
					
					if (pos == i) // attribute value found ?
					{
						const int iLength = expression.matchedLength();
						
						setFormat(i, 1, fmtOther);
						setFormat(i + 1, iLength - 2, fmtAttributeValue);
						setFormat(i + iLength - 1, 1, fmtOther);
						
						i += iLength - 1; // skip attribute value
						state = ExpectAttributeOrEndOfElement;
					}
					else
					{
						processDefaultText(i, text);
					}
				}
				else
				{
					processDefaultText(i, text);
				}
				break;
				
			case '!':
				if (state == ExpectElementNameOrSlash)
				{
					// search comment
					QRegExp expression(EXPR_COMMENT);
					pos = expression.indexIn(text, i - 1);
					
					if (pos == i - 1) // comment found ?
					{
						const int iLength = expression.matchedLength();
						
						setFormat(pos, 4, fmtSyntaxChar);
						setFormat(pos + 4, iLength - 7, fmtComment);
						setFormat(iLength - 3, 3, fmtSyntaxChar);
						i += iLength - 2; // skip comment
						state = NoState;
						brackets--;
					}
					else
					{
						// Try find multiline comment
						QRegExp expression(EXPR_COMMENT_BEGIN); // search comment start
						pos = expression.indexIn(text, i - 1);
						
						//if (pos == i - 1) // comment found ?
						if (pos >= i - 1)
						{
							setFormat(i, 3, fmtSyntaxChar);
							setFormat(i + 3, text.length() - i - 3, fmtComment);
							setCurrentBlockState(InComment);
							return;
						}
						else
						{
							processDefaultText(i, text);
						}
					}
				}
				else
				{
					processDefaultText(i, text);
				}
				
				break;
				
			default:
				const int iLength = processDefaultText(i, text);
				if (iLength > 0)
					i += iLength - 1;
				break;
        }
    }
	
    if (state == ExpectAttributeOrEndOfElement)
    {
        setCurrentBlockState(InElement);
    }
}

int XmlHighlighter::processDefaultText(int i, const QString& text)
{
    // length of matched text
    int iLength = 0;
	
    switch(state)
    {
		case ExpectElementNameOrSlash:
		case ExpectElementName:
        {
            // search element name
            QRegExp expression(EXPR_NAME);
            const int pos = expression.indexIn(text, i);
			
            if (pos == i) // found ?
            {
                iLength = expression.matchedLength();
				
                setFormat(pos, iLength, fmtElementName);
                state = ExpectAttributeOrEndOfElement;
            }
            else
            {
                setFormat(i, 1, fmtOther);
            }
        }
			break;
			
		case ExpectAttributeOrEndOfElement:
        {
            // search attribute name
            QRegExp expression(EXPR_NAME);
            const int pos = expression.indexIn(text, i);
			
            if (pos == i) // found ?
            {
                iLength = expression.matchedLength();
				
                setFormat(pos, iLength, fmtAttributeName);
                state = ExpectEqual;
            }
            else
            {
                setFormat(i, 1, fmtOther);
            }
        }
			break;
			
		default:
			setFormat(i, 1, fmtOther);
			break;
    }
    return iLength;
}