#include "CodeEditor.h"
#include "EditorGutter.h"
#include <QPainter>
#include <QTextBlock>
#include <QDebug>
//
// Adapted from codeeditor example
// http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html
//
// Used term gutter as I want to expand it to do other things to like error
// position marking.
//

CodeEditor::CodeEditor(QWidget *parent)
	: QPlainTextEdit(parent)
	, gutterArea(new EditorGutter(this))
    , colorTheme(GetColorTheme())
{
	connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateGutterAreaWidth(int)));
	connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateGutterArea(QRect,int)));
	connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
	connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged()));

	setWordWrapMode(QTextOption::NoWrap);

	updateGutterAreaWidth(0);
	setTabSize(4);
	highlightCurrentLine();
}

int CodeEditor::gutterAreaWidth()
{
	int digits = 1;
	int max = qMax(1, blockCount());
    while (max >= 10)
    {
		max /= 10;
		++digits;
	}

	int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * (digits + 2);

	return space;
}

void CodeEditor::updateGutterAreaWidth(int /* newBlockCount */)
{
	setViewportMargins(gutterAreaWidth(), 0, 0, 0);
}

void CodeEditor::updateGutterArea(const QRect &rect, int dy)
{
	if (dy)
		gutterArea->scroll(0, dy);
	else
		gutterArea->update(0, rect.y(), gutterArea->width(), rect.height());

	if (rect.contains(viewport()->rect()))
		updateGutterAreaWidth(0);
}

void CodeEditor::resizeEvent(QResizeEvent *e)
{
	QPlainTextEdit::resizeEvent(e);

	QRect cr = contentsRect();
	gutterArea->setGeometry(QRect(cr.left(), cr.top(), gutterAreaWidth(), cr.height()));
}

void CodeEditor::highlightCurrentLine()
{
	QTextEdit::ExtraSelection selection;
    selection.format.setBackground(colorTheme.currentLine);
	selection.format.setProperty(QTextFormat::FullWidthSelection, true);
	selection.cursor = textCursor();
	selection.cursor.clearSelection();
	currentLine = selection;

	updateExtraSelections();
}

void CodeEditor::onTextChanged()
{
	clearErrorMarkers();
}

void CodeEditor::addErrorMarker(int position, int length)
{
    QTextEdit::ExtraSelection selection;
    selection.format.setBackground(colorTheme.errorLine);
	selection.format.setFontItalic(true);
	selection.cursor = textCursor();
	selection.cursor.setPosition(position);
	int lineno = selection.cursor.blockNumber();
	errorLines.insert(lineno);
	selection.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, length);
	errorMarkers.append(selection);

	update();

	updateExtraSelections();
}

void CodeEditor::clearErrorMarkers()
{
	errorMarkers.clear();
	errorLines.clear();
	update();
	updateExtraSelections();
}

void CodeEditor::updateExtraSelections()
{
	QList<QTextEdit::ExtraSelection> extraSelections;
	extraSelections.append(currentLine);
	for (auto&& e : errorMarkers)
		extraSelections.append(e);

	setExtraSelections(extraSelections);
}

bool CodeEditor::lineHasError(int blockNumber) const
{
    return errorLines.count(blockNumber) > 0;
}

void CodeEditor::keyPressEvent(QKeyEvent *e)
{
	auto k = e->key();
    switch (k) {
        case Qt::Key_Tab:
            if (indentSelection(true))
                return;
            break;
        case Qt::Key_Backtab:
            if (indentSelection(false))
                return;
            break;
        case Qt::Key_Equal:
        case Qt::Key_Plus:
            if (e->modifiers().testFlag(Qt::ControlModifier))
            {
                auto f = font();
                f.setPointSize(f.pointSize() + 1);
                setFont(f);
            }
            break;
        case Qt::Key_Minus:
            if (e->modifiers().testFlag(Qt::ControlModifier))
            {
                auto f = font();
                f.setPointSize(f.pointSize() - 1);
                setFont(f);
            }
            break;
	}
	QPlainTextEdit::keyPressEvent(e);
}



bool CodeEditor::indentSelection(bool indent)
{
	auto cursor = textCursor();

	if(!cursor.hasSelection())
		return false;

	auto first_pos = cursor.anchor();
	auto end_pos   = cursor.position();

	if(first_pos > end_pos)
		std::swap(first_pos, end_pos);

	cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
	auto start_block = cursor.block().blockNumber();

	cursor.setPosition(end_pos, QTextCursor::MoveAnchor);
	auto end_block = cursor.block().blockNumber();
	if (end_block == start_block)
		return false;

	cursor.beginEditBlock();
	cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
	const auto block_count = end_block - start_block;
    for(int block = 0; block <= block_count; ++block)
    {
		cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
        if (indent)
            insertIndentation(cursor);
        else
            removeIndentation(cursor);
		cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
	}
	cursor.endEditBlock();

    makeSelection(cursor, first_pos, end_block);

	setTextCursor(cursor);
	return true;
}

void CodeEditor::insertIndentation(QTextCursor &cursor)
{
    if (m_useTab)
        cursor.insertText("\t");
    else
        cursor.insertText(QString(m_tabSize, ' '));
}

void CodeEditor::removeIndentation(QTextCursor &cursor)
{
    // remove tab if there is a tab
    cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
    const QString text = cursor.selectedText();
    int index = 0;
    int pos = 0;
    while (pos < m_tabSize && index < text.length())
    {
        QChar c = text[index++];
        if (c == ' ')
            ++pos;
        else if (c == '\t')
            pos = ((pos + m_tabSize) / m_tabSize) * m_tabSize;
        else
        {
            --index;
            break;
        }
    }
    cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
    for (int i = 0; i < index; ++i)
        cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
    cursor.removeSelectedText();

}

void CodeEditor::makeSelection(QTextCursor &cursor, int first_pos, int end_block)
{
    cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
    cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
    while(cursor.block().blockNumber() < end_block)
        cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);

    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
}

void CodeEditor::setFont(const QFont &f)
{
    QWidget::setFont(f);
    auto orig_tab = m_tabSize;
    m_tabSize = 0;
    setTabSize(orig_tab);
}

void CodeEditor::setTabSize(int chars)
{
    m_tabSize = chars;
    int pixels = fontMetrics().horizontalAdvance(QString(chars, '0'));
    this->setTabStopDistance(pixels);
}
