/*
 * Copyright 2010 Mihai Niculescu <q.quark@gmail.com>
 *
 * This file is part of EqualX Project (https://launchpad.net/equalx/)
 *
 * EqualX 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.
 *
 * EqualX 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/>.
 */
#include <QDebug>

#include <QApplication>
#include <QtAlgorithms>
#include <QAbstractItemView>
#include <QCompleter>
#include <QFile>
#include <QKeyEvent>
#include <QPainter>
#include <QStringListModel>
#include <QScrollBar>

#include "LatexEditor.h"
#include "LatexHighlighter.h"

LatexEditorLineNumberArea::LatexEditorLineNumberArea(LatexEditor *editor)
    : QWidget(editor)
{
    codeEditor = editor;

}

QSize LatexEditorLineNumberArea::sizeHint() const {
    return QSize(codeEditor->lineNumberAreaWidth(), 0);
}

void LatexEditorLineNumberArea::paintEvent(QPaintEvent *event) {
    codeEditor->lineNumberAreaPaintEvent(event);
}

LatexEditor::LatexEditor(QWidget *parent)
    : QPlainTextEdit(parent),
      c(0),
      minCompletionChars(2),
      lineNumberAreaStartCount(0)
{
    setWindowTitle(tr("Latex Widget Editor"));

    c = new QCompleter(this);
    //c->setModel( modelFromFile(":/resources/templates/latexKeyWords.txt") );
    c->setCaseSensitivity(Qt::CaseInsensitive);
    c->setWrapAround(false);

    highlighter = new LatexHighlighter( this->document() );
    setCompleter(c);

    lineNumberArea = new LatexEditorLineNumberArea(this);

    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
    connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
    // connect(this, SIGNAL(textChanged()), this, SLOT(findAll()) );

    pixError = new QPixmap("://resources/icons/editor/error.png");

    updateLineNumberAreaWidth(0);
}

LatexEditor::~LatexEditor()
{
    delete pixError;
}

HLSelections LatexEditor::getSelections() const
{
    return hlSelections;
}


void LatexEditor::setCompletion(bool activeCompletion)
{
    m_activeCompletion = activeCompletion;
}

void LatexEditor::setCompletionSensitive(bool caseSensitive)
{
    if(caseSensitive)
        c->setCaseSensitivity(Qt::CaseSensitive);
    else
        c->setCaseSensitivity(Qt::CaseInsensitive);

}

void LatexEditor::setHighLighting(bool activeHL)
{
    if(activeHL)
        highlighter->setDocument( this->document() );
    else
        highlighter->setDocument(0);
}

QAbstractItemModel *LatexEditor::modelFromFile(const QString& fileName)
{
    QFile file(fileName);
    if(!file.open(QFile::ReadOnly) )
        return new QStringListModel(c);

    QStringList words;

    while (!file.atEnd()) {
        QByteArray line = file.readLine();
        if (!line.isEmpty())
            words << line.trimmed();
    }

    return new QStringListModel(words, c);

}

void LatexEditor::setCompleter(QCompleter *completer)
{
    if(!completer){
        QObject::disconnect(c, 0, this, 0);
        return;
    }

    if(c)
        QObject::disconnect(c, 0, this, 0);

    c = completer;

    c->setWidget(this);
    c->setCompletionMode(QCompleter::PopupCompletion);

    QObject::connect(c, SIGNAL(activated(const QString&)),
                     this, SLOT(insertCompletion(QString)));
}

void LatexEditor::setModel(QAbstractItemModel *model)
{
    c->setModel(model);
    c->setCompletionColumn(0);
}

QCompleter *LatexEditor::completer() const
{
    return c;
}

void LatexEditor::setStartCountingBlocks(int n)
{
    lineNumberAreaStartCount = n;

    updateLineNumberAreaWidth(n);
    update();
}

void LatexEditor::clearSelections()
{
    if( hlSelections.isEmpty() )
        return;

    // Clear Highlight List
    for(int i=0; i< hlSelections.size(); i++){
        SelectionIndex *si = hlSelections.at(i);

        delete si;

    }

    hlSelections.clear();

    // Clear Highlights from editor
    QTextCursor tc( document() );
    QTextCharFormat format = tc.charFormat();
    format.setForeground( QBrush(QColor("black")) );
    format.setBackground( QBrush(QColor("white")) );

    selectAll();

    tc = textCursor();
    tc.setCharFormat( format );

    tc.clearSelection();

    setTextCursor(tc);

}

bool LatexEditor::find(const QString &expr, QTextDocument::FindFlags flags)
{
    findExpr = expr;
    findFlags = flags;

    bool findResult =  QPlainTextEdit::find(expr, flags);



    return findResult;

}

bool LatexEditor::findAll(const QString &expr, QTextDocument::FindFlags flags)
{
    clearSelections();


    if( expr.isEmpty() || expr.isNull() )
        return false;

    findExpr = expr;
    findFlags = flags;

    // get a cursor at the begining of the document
    QTextCursor tc( document() );

    // set highlight colors
    QPalette p = QApplication::palette();
    QTextCharFormat selectedFormat = tc.charFormat();
    selectedFormat.setForeground( p.highlightedText() );
    selectedFormat.setBackground(p.highlight());

    setTextCursor(tc);

    // store find results in SelectionIndex
    SelectionIndex *si;
    while( find(expr, flags) ){

        tc = textCursor();
        tc.setCharFormat( selectedFormat );

        si = new SelectionIndex;
        si->start = tc.selectionStart() ;
        si->end = tc.selectionEnd();

        hlSelections.append(si);
    }

    return true;
}

bool LatexEditor::findNext()
{
    return find(findExpr, findFlags);
}

bool LatexEditor::findPrevious()
{
    findFlags = findFlags | QTextDocument::FindBackward;

    return find(findExpr, findFlags);
}

void LatexEditor::insertCompletion(const QString &completion)
{
    if(c->widget() != this)
        return;
    QTextCursor tc = textCursor();
    //int extra = completion.length() - c->completionPrefix().length();
    tc.select(QTextCursor::WordUnderCursor);
    tc.removeSelectedText();
    tc.insertText(completion.trimmed());
    setTextCursor(tc);
}

QString LatexEditor::textUnderCursor() const
{
    QTextCursor tc = textCursor();
    tc.select(QTextCursor::WordUnderCursor);
    //QString::SectionFlag flag =  QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep;

    QString selectedText = tc.selectedText();

    QRegExp exp("[\\\\_]");
    QString returnText = selectedText.section(exp, 0,0, QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep);

    return returnText;
}

void LatexEditor::focusInEvent(QFocusEvent *e)
{
    if(c)
        c->setWidget(this);
    QPlainTextEdit::focusInEvent(e);
}

/* The keyPressEvent() is reimplemented to ignore key events like
 Qt::Key_Enter, Qt::Key_Return, Qt::Key_Escape, Qt::Key_Tab, and Qt::Key_Backtab
 so the completer can handle them.

If there is an active completer, we cannot process the shortcut, Ctrl+E.*/
void LatexEditor::keyPressEvent(QKeyEvent *e)
{
    if(c && c->popup()->isVisible()) {
        switch(e->key()) {
        case Qt::Key_Enter:
        case Qt::Key_Return:
        case Qt::Key_Escape:
        case Qt::Key_Tab:
        case Qt::Key_Backtab:
            e->ignore();
            return;

        default:
            break;

        }
    }

    bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
    if(!c || !isShortcut)
        QPlainTextEdit::keyPressEvent(e);

    /*  We also handle other modifiers and shortcuts
    for which we do not want the completer to respond to.*/

    const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
    if (!c || (ctrlOrShift && e->text().isEmpty()) ||  !m_activeCompletion)
        return;

    static QString eow("~!@#$%&*()+|:\"<>?,./;'[]-="); // end of word
    bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
    QString completionPrefix = textUnderCursor();

    if (!isShortcut && (hasModifier || e->text().isEmpty() || completionPrefix.length() < minCompletionChars
                        || eow.contains(e->text().right(1)) ) ) {
        c->popup()->hide();
        return;
    }

    if (completionPrefix != c->completionPrefix()) {
        c->setCompletionPrefix(completionPrefix);
        c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));
    }

    QRect cr = cursorRect();
    cr.setWidth(c->popup()->sizeHintForColumn(0)
                + c->popup()->verticalScrollBar()->sizeHint().width());
    c->complete(cr); // popup it up!

}

void LatexEditor::replace(const QString &expr)
{
    QTextCursor tc = textCursor();

    tc.insertText(expr);

    setTextCursor(tc);

}

void LatexEditor::replaceAll(const QString &expr)
{
    QTextCursor tc = textCursor();

    int replaceStrLength = expr.length() - findExpr.length();
    int pos1,pos2, ss, se;

    for(int i=0; i < hlSelections.size(); i++){
        SelectionIndex *selectionItem = hlSelections.at(i);

        ss = selectionItem->start; // start selection
        se = selectionItem->end; // end selection

        pos1 = ss + i*replaceStrLength;
        pos2 = se + i*replaceStrLength;

        tc.setPosition(pos1);
        tc.setPosition(pos2, QTextCursor::KeepAnchor);

        tc.insertText(expr);

        setTextCursor(tc);

    }

    clearSelections();

}

QString LatexEditor::selectedText() const
{
    QTextCursor tc = textCursor();

    return tc.selectedText();
}

int LatexEditor::lineNumberAreaWidth()
{
    int digits = 1;
    int max = qMax(1, blockCount()+lineNumberAreaStartCount);
    while (max >= 10) {
        max /= 10;
        ++digits;
    }

    int space = 6 + fontMetrics().width(QLatin1Char('9')) * digits + pixError->width();

    return space;
}

void LatexEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{
    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}

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

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

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

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

void LatexEditor::mousePressEvent(QMouseEvent *e)
{
    QPlainTextEdit::mousePressEvent(e);

    if(e->button() == Qt::LeftButton && e->modifiers() ==Qt::ControlModifier){
        qDebug() << "currentCompletion: " << c->currentCompletion();

        QString completionPrefix = textUnderCursor();

        if ( toPlainText().isEmpty() || completionPrefix.length() < minCompletionChars ) {
            c->popup()->hide();
            return;
        }


        c->setCompletionPrefix(completionPrefix);
        c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));

        QRect cr = cursorRect();
        cr.setWidth(c->popup()->sizeHintForColumn(0)
                    + c->popup()->verticalScrollBar()->sizeHint().width());
        c->complete(cr); // popup it up!
    }
}

void LatexEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
    QPainter painter(lineNumberArea);
    painter.fillRect(event->rect(), Qt::lightGray); //set background color

    QTextBlock block = firstVisibleBlock();
    int blockNumber = block.blockNumber() + lineNumberAreaStartCount;
    int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
    int bottom = top + (int) blockBoundingRect(block).height();
    while (block.isValid() && top <= event->rect().bottom()) {
        if (block.isVisible() && bottom >= event->rect().top()) {
            QString number = QString::number(blockNumber + 1);

            if(errors.contains(blockNumber+1)) painter.drawPixmap(0, top, *pixError);
            painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
                             Qt::AlignRight, number);
        }

        block = block.next();
        top = bottom;
        bottom = top + (int) blockBoundingRect(block).height();
        ++blockNumber;
    }
}

void LatexEditor::selectLines(const QMap<int, QString> &errorsMap)
{
    errors.clear();
    errors = errorsMap;

    QList<int> lines = errors.keys();
    QList<QTextEdit::ExtraSelection> extraSelections;


    if(!lines.isEmpty()){
        QColor hlLineColor = QColor(Qt::yellow).lighter(160);
        QTextCursor saveCursor = textCursor();

        qSort(lines);
        moveCursor(QTextCursor::Start);
        int blockN=lineNumberAreaStartCount;
        int blocks = blockCount();
        for (int i = 0; i < lines.size(); ++i) {
            int lineErrorNumber = lines.at(i);

            if(lineErrorNumber >= lineNumberAreaStartCount && lineErrorNumber <= blocks+lineNumberAreaStartCount){

                while( !textCursor().atEnd()){

                    if( (blockN+lineNumberAreaStartCount) == lineErrorNumber ) {

                        QTextEdit::ExtraSelection selection;
                        selection.format.setBackground(hlLineColor);
                        selection.format.setProperty(QTextFormat::FullWidthSelection, true);

                        selection.cursor = textCursor();
                        extraSelections.append(selection);
                        break;

                    }
                    QTextCursor cur = textCursor();
                    if(!cur.movePosition(QTextCursor::NextBlock)) break;
                    setTextCursor(cur);

                    blockN = textCursor().blockNumber()+1;

                }
            }

        }
        setTextCursor(saveCursor);
    }

    setExtraSelections(extraSelections);

}
