#include "qgpyeditdialog.h"

#include <QMimeData>
#include <QUrl>

#include <QLineEdit>
#include <QLabel>
#include <QPushButton>
#include <QToolButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QDialogButtonBox>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QCheckBox>
#include <QComboBox>
#include <QSpinBox>
#include <QFile>
#include <QTextStream>
#include <QAction>
#include <QGroupBox>
#include <QSyntaxHighlighter>

#include "qgpython.h"

class Highlighter : public QSyntaxHighlighter
{
    //Q_OBJECT

public:
    Highlighter(QTextDocument *parent = 0):
        QSyntaxHighlighter(parent)
    {
        HighlightingRule rule;

        keywordFormat.setForeground(Qt::darkBlue);
        keywordFormat.setFontWeight(QFont::Bold);
        QStringList keywordPatterns;
        keywordPatterns << "\\bFalse\\b" << "\\bclas\\b" << "\\bfinally\\b"
                        << "\\breturn\\b" << "\\bNone\\b" << "\\bcontinue\\b"
                        << "\\bfor\\b" << "\\blambda\\b" << "\\btry\\b"
                        << "\\bTrue\\b" << "\\bdef\\b" << "\\bfor\\b"
                        << "\\bis\\b" << "\\bfrom\\b" << "\\bnonlocal\\b"
                        << "\\bwhile\\b" << "\\band\\b" << "\\bglobal\\b"
                        << "\\bnot\\b" << "\\bwith\\b" << "\\bas\\b"
                        << "\\belif\\b" << "\\bor\\b" << "\\byeld\\b"
                        << "\\bassert\\b" << "\\belse\\b" << "\\bimport\\b"
                        << "\\bpass\\b" << "\\bbreak\\b" << "\\bin\\b" << "\\braise\\b" << "\\bif\\b" <<"\\bself\\b";
        foreach (const QString &pattern, keywordPatterns) {
            rule.pattern = QRegularExpression(pattern);
            rule.format = keywordFormat;
            highlightingRules.append(rule);
        }
        classFormat.setFontWeight(QFont::Bold);
        classFormat.setForeground(Qt::darkMagenta);
        rule.pattern = QRegularExpression("\\bQ[A-Za-z]+\\b");
        rule.format = classFormat;
        highlightingRules.append(rule);

        quotationFormat.setForeground(Qt::darkGreen);
        rule.pattern = QRegularExpression("\".*\"");
        rule.format = quotationFormat;
        highlightingRules.append(rule);

        functionFormat.setFontItalic(true);
        functionFormat.setForeground(Qt::blue);
        rule.pattern = QRegularExpression("\\b[A-Za-z0-9_]+(?=\\()");
        rule.format = functionFormat;
        highlightingRules.append(rule);
        singleLineCommentFormat.setForeground(Qt::red);
        rule.pattern = QRegularExpression("//[^\n]*");
        rule.format = singleLineCommentFormat;
        highlightingRules.append(rule);

        multiLineCommentFormat.setForeground(Qt::red);

        commentStartExpression = QRegularExpression("\"\"\"");
        commentEndExpression = QRegularExpression("\"\"\"");
    }

protected:
    void highlightBlock(const QString &text) override
    {
        foreach (const HighlightingRule &rule, highlightingRules) {
            QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
            while (matchIterator.hasNext()) {
                QRegularExpressionMatch match = matchIterator.next();
                setFormat(match.capturedStart(), match.capturedLength(), rule.format);
            }
        }
        setCurrentBlockState(0);
        int startIndex = 0;
        if (previousBlockState() != 1)
            startIndex = text.indexOf(commentStartExpression);
        while (startIndex >= 0)
        {
            QRegularExpressionMatch match = commentEndExpression.match(text, startIndex);
            int endIndex = match.capturedStart();
            int commentLength = 0;
            if (endIndex == -1) {
                setCurrentBlockState(1);
                commentLength = text.length() - startIndex;
            } else {
                commentLength = endIndex - startIndex
                             + match.capturedLength();
            }
            setFormat(startIndex, commentLength, multiLineCommentFormat);
            startIndex = text.indexOf(commentStartExpression, startIndex + commentLength);
        }
    }
private:
    struct HighlightingRule
    {
        QRegularExpression pattern;
        QTextCharFormat format;
    };
    QVector<HighlightingRule> highlightingRules;

    QRegularExpression commentStartExpression;
    QRegularExpression commentEndExpression;

    QTextCharFormat keywordFormat;
    QTextCharFormat classFormat;
    QTextCharFormat singleLineCommentFormat;
    QTextCharFormat multiLineCommentFormat;
    QTextCharFormat quotationFormat;
    QTextCharFormat functionFormat;
};

class QgPyEditDialog::Implementation
{
public:
    //const QMimeData* md;

    QHBoxLayout* fbox;
    QLineEdit* lblPath;
    QToolButton* btnFile;
    QToolButton* btnEdit;

    //QLabel* lblContents;
    QTextEdit* txt;
    QVBoxLayout* ly;

    QLabel* lblMode;
    QComboBox* cbMode;

    QDialogButtonBox* bbox;

    QAction* actPreviewOff;
    QAction* actPreviewFull;
    QAction* actPreviewEdit;
    QAction* actDownload;

    QString fileName;
    QString txtStore;

    QLineEdit* leRemPattern;
    QCheckBox* cbLinePattern;

    QGroupBox* boxLineDelay;
    QLabel* lblLineDelay;
    QSpinBox* sbLineDelay;
    QSpinBox* sbCharDelay;
    QLineEdit* lePrompt;

    //QComboBox* cbEolTranslate;
    Highlighter* highlighter;


    void fileMode(bool enable)
    {
        lblPath->setVisible(true); //enable);
        btnFile->setVisible(true); //false);
        btnEdit->setVisible(true); //enable);
        fbox->setEnabled(true); //enable);
        if (!enable)
            fileName.clear();
    }

    void preview(bool full)
    {
        if (!fileName.isEmpty())
        {
            QFile f(fileName);
            if (f.open(QIODevice::ReadOnly|QIODevice::Text))
            {
                QTextStream in(&f);
                if (full)
                  txtStore=in.readAll();
                else
                {
                  txtStore=in.read(2048);
                  if (f.size()>2048)
                      txtStore+="\r\n[...]";
                }
                //p_.lblContents->setVisible(true);
            }
            else
                txtStore="Error reading file: "+f.errorString();
        }

        txt->setPlainText(txtStore);
    }
};

QgPyEditDialog::QgPyEditDialog(QWidget *parent):
    QDialog(parent, Qt::Tool),
    p_(*new Implementation)
{
    //File name box:
    p_.fbox=new QHBoxLayout;
    p_.lblPath=new QLineEdit("<path>");
    p_.lblPath->setReadOnly(true);
    p_.lblPath->setDisabled(true);
    p_.btnFile=new QToolButton;
    p_.btnFile->setText("<");
    p_.btnEdit=new QToolButton;
    p_.btnEdit->setText("+");
    p_.btnEdit->setPopupMode(QToolButton::InstantPopup);
      p_.actPreviewOff=new QAction("Small Preview", this);
      p_.actPreviewFull=new QAction("Full Preview", this);
      p_.actPreviewEdit=new QAction("Edit", this);
      //p_.actDownload=new QAction("Download...", this);
    p_.btnEdit->addAction(p_.actPreviewOff);
    p_.btnEdit->addAction(p_.actPreviewFull);
    p_.btnEdit->addAction(p_.actPreviewEdit);
    //p_.btnEdit->addAction(p_.actDownload);

    p_.fbox->addWidget(p_.lblPath);
    p_.fbox->addWidget(p_.btnFile);
    p_.fbox->addWidget(p_.btnEdit);

    p_.ly=new QVBoxLayout;
    p_.ly->addLayout(p_.fbox); //lblPath);
    //p_.lblContents=new QLabel("<Contents>");
    //p_.lblContents->setFrameShape(QFrame::Box);
    p_.txt=new QTextEdit;
    p_.txt->setReadOnly(true);
    p_.txt->addAction(p_.actPreviewEdit);
    p_.txt->setContextMenuPolicy(Qt::ActionsContextMenu);
    p_.txt->setVisible(false);
    p_.txt->setAcceptDrops(false);
    p_.ly->addWidget(p_.txt);
    //p_.ly->addWidget(p_.lblContents);

   p_.highlighter = new Highlighter(p_.txt->document());

    //p_.lblMode=new QLabel("Mode:");
#if 0
    QFormLayout* hly=new QFormLayout;

    p_.cbMode=new QComboBox;
    p_.cbMode->addItems(QStringList()<<"auto"<<"Text"<<"Binary");
    p_.cbMode->setEnabled(true);
    hly->addRow("Mode:", p_.cbMode);
    p_.cbLinePattern=new QCheckBox("Remove:", this);
    p_.leRemPattern=new QLineEdit("-----", this);
    p_.leRemPattern->setEnabled(false);
    connect(p_.cbLinePattern, SIGNAL(toggled(bool)), p_.leRemPattern, SLOT(setEnabled(bool)));
    hly->addRow(p_.cbLinePattern, p_.leRemPattern);
    //hly->addWidget(p_.lblMode);
    //hly->addWidget(p_.cbMode);

    p_.ly->addLayout(hly);

    //Line delay
    {
      p_.boxLineDelay=new QGroupBox("Line Control:");
      QVBoxLayout* lyCtrl=new QVBoxLayout;
      QHBoxLayout* lyDelay=new QHBoxLayout;
      p_.lblLineDelay=new QLabel("Char/Line delay (ms)");
      lyDelay->addWidget(p_.lblLineDelay);
      p_.sbCharDelay=new QSpinBox;
      p_.sbCharDelay->setRange(-1, 1000);
      p_.sbCharDelay->setToolTip("<0 = wait for echo with timeout");
      lyDelay->addWidget(p_.sbCharDelay);
      p_.sbLineDelay=new QSpinBox;
      p_.sbLineDelay->setRange(-1, 1000);
      p_.sbLineDelay->setToolTip("<0 = wait for prompt, with timeout");
      lyDelay->addWidget(p_.sbLineDelay);
      p_.lePrompt=new QLineEdit;
      p_.lePrompt->setToolTip("Prompt pettern");
      p_.lePrompt->setPlaceholderText("Prompt");
      lyDelay->addWidget(p_.lePrompt);

      lyCtrl->addLayout(lyDelay);
      p_.boxLineDelay->setLayout(lyCtrl);

      p_.ly->addWidget(p_.boxLineDelay);
    }
#endif
    p_.bbox=new QDialogButtonBox(QDialogButtonBox::Ok
                                 | QDialogButtonBox::Cancel); //|QDialogButtonBox::Open);
    //p_.bbox->button(QDialogButtonBox::Open)->setText("Execute");
    p_.bbox->button(QDialogButtonBox::Ok)->setText("Execute");

    p_.ly->addWidget(p_.bbox);
    setLayout(p_.ly);

    connect(p_.actPreviewEdit, SIGNAL(triggered()), SLOT(previewEdit()));
    connect(p_.actPreviewFull, SIGNAL(triggered()), SLOT(previewFull()));
    connect(p_.actPreviewOff, SIGNAL(triggered()), SLOT(previewSmall()));

    connect(p_.bbox, SIGNAL(rejected()), SLOT(reject()));
    //connect(p_.bbox, SIGNAL(accepted()), SLOT(accept()));
    connect(p_.bbox, SIGNAL(accepted()), SLOT(executeScript()));
}

QgPyEditDialog::~QgPyEditDialog()
{
  delete &p_;
}

void QgPyEditDialog::previewSmall()
{
    p_.txt->setVisible(true);
    p_.preview(false);
}

void QgPyEditDialog::previewFull()
{
    p_.txt->setVisible(true);
    p_.preview(true);
}

void QgPyEditDialog::previewEdit()
{
  previewFull();
  p_.txt->setContextMenuPolicy(Qt::DefaultContextMenu);
  p_.txt->setReadOnly(false);
  p_.btnEdit->setEnabled(false);
  p_.fileMode(false);
  //p_.cbMode->setCurrentIndex(1);
  //p_.cbMode->setEnabled(false);
}

void QgPyEditDialog::executeScript()
{
    QgPython::instance().mainContext().evalScript(p_.txt->toPlainText());
}

void QgPyEditDialog::showFile(const QString& filename)
{
    QMimeData md;
    md.setUrls(QList<QUrl>()<<QUrl::fromLocalFile(filename));
    setMimeInfo(&md);
    show();
}

void QgPyEditDialog::setMimeInfo(const QMimeData* md)
{
    //p_.md=md;
    p_.btnEdit->setEnabled(true);
    p_.txt->setReadOnly(true);
    p_.txt->setContextMenuPolicy(Qt::ActionsContextMenu);

    if (md->hasUrls())
    {
        p_.lblPath->setText(md->urls()[0].fileName());
        p_.fileName=md->urls()[0].toLocalFile();
        p_.txtStore.clear();
        p_.txt->clear();
        p_.txt->setVisible(false);
        p_.fileMode(true);
    }
    else if (md->hasText())
    {
      p_.txtStore=md->text();
      p_.fileMode(false);
      previewFull();
    }
}

QString QgPyEditDialog::text()
{
    if (!p_.txt->isReadOnly())
        return p_.txt->toPlainText();
    return QString();
}

QString QgPyEditDialog::execFile()
{
    return p_.fileName;
}

QgPyEditDialog* QgPyEditDialog::singleInstance()
{
    static QgPyEditDialog dlg;
    return &dlg;
}
