#ifndef EDITOR_HXX
#define EDITOR_HXX
/// from qt http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html

#include <QPlainTextEdit>
#include <QPainter>
#include <QTextBlock>
#include <QResizeEvent>
#include <QSyntaxHighlighter>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <cassert>

#include <iostream>

class Highlighter: public QSyntaxHighlighter {
    Q_OBJECT
  Q_SIGNALS:
    void include(QString);
  public:
    Highlighter(QTextDocument *parent): QSyntaxHighlighter(parent) {
      QString commands="auth|ca-certificate|call|case|default|check|clear-cookies|click|clicktype|client-certificate|do|download|echo|execute|exists|exit|expect|fail|for|function|if|else|while|ignore|unignore|ignoreto|include|label|load|not|offline-storage-path|open|screenshot|set|setvalue|sleep|testcase|testsuite|timeout|unset|upload";
      _expressions<<Expression("^ *("+commands+")\\b").weight(QFont::Bold).fg(Qt::darkBlue)
                  <<Expression("^ *#.*$").weight(QFont::Bold).fg(Qt::black);
    }
  protected:
    void highlightBlock(const QString &text) {
      static QRegularExpression inc("^ *include +([^ ].*.\\.wt)");
      QRegularExpressionMatch m(inc.match(text));
      if (m.hasMatch()) {
        QTextCharFormat fmt;
        if (QFile(m.captured(1)).exists()) {
          fmt.setForeground(Qt::darkGreen);
          fmt.setFontWeight(QFont::Bold);
        } else {
          fmt.setForeground(Qt::darkRed);
          fmt.setFontStrikeOut(true);
        }
        setFormat(m.capturedStart(1), m.capturedLength(1), fmt);
        include(m.captured(1));
      }
      for (auto e: _expressions) {
        auto m2(e.re.match(text));
        for (int i(0); i<=m2.lastCapturedIndex(); ++i) {
          setFormat(m2.capturedStart(i), m2.capturedLength(i), e.fmt);
        }
      }
    }
  private:
    struct Expression {
        Expression(QString s): re(s) {}
        Expression(QString s, QTextCharFormat f): re(s), fmt(f) {}
        Expression& weight(int w) {fmt.setFontWeight(w); return *this;}
        Expression& strike(bool s=true) {fmt.setFontStrikeOut(s); return *this;}
        Expression& fg(const QBrush& b) {fmt.setForeground(b); return *this;}
        QRegularExpression re;
        QTextCharFormat fmt;
    };
    QList<Expression> _expressions;
};

class CodeEditor;

class LineNumberArea: public QWidget {
  public:
    LineNumberArea(CodeEditor *editor);
    QSize sizeHint() const override;
  protected:
    void paintEvent(QPaintEvent *event) override;
  private:
    CodeEditor *codeEditor;
};

class CodeEditor: public QPlainTextEdit {
    Q_OBJECT;
  Q_SIGNALS:
    void include(QString);
    void link(QString);
  public:
    CodeEditor(QWidget *parent = 0): QPlainTextEdit(parent) {
      Highlighter *highlighter(new Highlighter(document()));
      lineNumberArea = new LineNumberArea(this);
      assert(connect(this, SIGNAL(blockCountChanged(int)), SLOT(updateLineNumberAreaWidth(int))));
      assert(connect(this, SIGNAL(updateRequest(QRect,int)), SLOT(updateLineNumberArea(QRect,int))));
      assert(connect(this, SIGNAL(cursorPositionChanged()), SLOT(highlightCurrentLine())));
      assert(connect(highlighter, SIGNAL(include(QString)), SIGNAL(include(QString))));
      updateLineNumberAreaWidth(0);
      highlightCurrentLine();
    }
    void gotoLine(int line) {
      QTextCursor cursor(document()->findBlockByNumber(line-1));
      setTextCursor(cursor);
      highlightCurrentLine();
    }
    void lineNumberAreaPaintEvent(QPaintEvent *event) {
      QPainter painter(lineNumberArea);
      painter.fillRect(event->rect(), Qt::lightGray);
      QTextBlock block = firstVisibleBlock();
      int blockNumber = block.blockNumber();
      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);
            painter.setPen(Qt::black);
            painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
                             Qt::AlignRight, number);
        }
        block = block.next();
        top = bottom;
        bottom = top + (int) blockBoundingRect(block).height();
        ++blockNumber;
      }
    }
    int lineNumberAreaWidth() {
      int digits(1);
      int max = qMax(1, blockCount());
      while (max >= 10) {
        max /= 10;
        ++digits;
      }
      int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
      return space;
    }
    void mousePressEvent(QMouseEvent *e) {
      clickedAnchor = (e->button() & Qt::LeftButton)
        ? document()->findBlock(cursorForPosition(e->pos()).position()).text()
        : QString();
      QPlainTextEdit::mousePressEvent(e);
    }
    void mouseReleaseEvent(QMouseEvent *e) {
      if (e->button() & Qt::LeftButton && !clickedAnchor.isEmpty()
          && document()->findBlock(cursorForPosition(e->pos()).position()).text() == clickedAnchor) {
        static QRegularExpression inc("^ *include +([^ ].*.\\.wt)");
        QRegularExpressionMatch m(inc.match(clickedAnchor));
        if (m.hasMatch() && QFile(m.captured(1)).exists()) {
          link(m.captured(1));
        }
      }
      QPlainTextEdit::mouseReleaseEvent(e);
    }
  protected:
    void resizeEvent(QResizeEvent *e) override {
      QPlainTextEdit::resizeEvent(e);
      QRect cr = contentsRect();
      lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
    }
  private Q_SLOTS:
    void updateLineNumberAreaWidth(int newBlockCount) {
      setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
    }
    void highlightCurrentLine() {
      QList<QTextEdit::ExtraSelection> extraSelections;
      if (!isReadOnly()) {
        QTextEdit::ExtraSelection selection;
        QColor lineColor = QColor(Qt::yellow).lighter(160);
        selection.format.setBackground(lineColor);
        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
        selection.cursor = textCursor();
        selection.cursor.clearSelection();
        extraSelections.append(selection);
      }
      setExtraSelections(extraSelections);
    }
    void 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);
    }
  private:
    QWidget *lineNumberArea;
    QString clickedAnchor;
};

inline LineNumberArea::LineNumberArea(CodeEditor *editor): QWidget(editor) {
  codeEditor = editor;
}
inline QSize LineNumberArea::sizeHint() const {
  return QSize(codeEditor->lineNumberAreaWidth(), 0);
}
inline void LineNumberArea::paintEvent(QPaintEvent *event) {
  codeEditor->lineNumberAreaPaintEvent(event);
}
#endif
