#ifndef __SCRIPTFILE__HXX
#define __SCRIPTFILE__HXX

#include <commands.hxx>
#include <ui_scriptfile.hxx>
#include <QMessageBox>
#include <QScrollBar>
#include <QShortcut>
#include <QTextDocumentFragment>
#include <cassert>

class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
    Q_OBJECT
  Q_SIGNALS:
    void modified(ScriptFile*);
    void link(QString);
    void include(QString);
    void close(ScriptFile*);
    void run(const QString&, const QString&, bool, Script&);
    void progress(QString, int, int, int);
  public:
    ScriptFile(QWidget* p = nullptr, QString name = QString()): QDockWidget(p), _name(name) {
      setupUi(this);
      setWindowTitle(_name+"[*]");
      assert(connect(_editor, SIGNAL(textChanged()), SLOT(modified())));
      assert(connect(_editor, SIGNAL(include(QString)), SIGNAL(include(QString))));
      assert(connect(_editor, SIGNAL(link(QString)), SIGNAL(link(QString))));
      assert(connect(_editor, SIGNAL(cursorPositionChanged()), SLOT(setLine())));
      assert(connect(_line, SIGNAL(valueChanged(int)), SLOT(gotoLine(int))));
      _searchBar->hide();
      _replaceBar->hide();
      _status->setCurrentIndex(STATUS_NONE);
    }
    CodeEditor* editor() {
      return _editor;
    }
    QString name() {
      return _name;
    }
    void name(QString name) {
      _name = name;
      setWindowTitle(name+"[*]");
      setWindowModified(false);
    }
  public Q_SLOTS:
    void setLine() {
      int pos(_editor->textCursor().blockNumber()+1);
      if (pos) _line->setValue(pos);
    }
    void gotoLine(int line) {
      _editor->gotoLine(line);
    }
    void load(QString name = QString()) {
      if (isWindowModified() &&
          QMessageBox::question(this, tr("Changes Not Saved"),
                                tr("Load script without saving changes?"))
          != QMessageBox::Yes)
        return;
      QString oldname(_name);
      if (!name.isEmpty()) _name = name;
      QFileInfo info(name);
      if (info.absoluteDir()==QDir::current()) _name = info.fileName();
      try {
        QFile file(_name);
        if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
          throw std::runtime_error("file open failed");
        _editor->setPlainText(QString::fromUtf8(file.readAll()));
        if (file.error()!=QFileDevice::NoError)
          throw std::runtime_error("file read failed");
        setWindowTitle(_name+"[*]");
        setWindowModified(false);
        _status->setCurrentIndex(STATUS_NONE);
      } catch(const std::exception& x) {
          QMessageBox::critical(this, tr("Open Failed"),
                                tr("Reading test script failed, %2. "
                                   "Cannot read test script from file %1.")
                                .arg(_name).arg(x.what()));
          _name = oldname;
      }
    }
    void save(QString name = QString()) {
      QString oldname(_name);
      if (!name.isEmpty()) _name = name;
      QFile file(_name);
      try {
        if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
          throw std::runtime_error("file open failed");
        QTextStream out(&file);
        out<<_editor->toPlainText();
        if (out.status()!=QTextStream::Ok)
          throw std::runtime_error(std::string("file write failed (")
                                   +char(out.status()+48)+")");
        setWindowModified(false);
        setWindowTitle(_name+"[*]");
      } catch(const std::exception& x) {
        QMessageBox::critical(this, tr("Save Failed"),
                              tr("Saving test script failed, %2. "
                                 "Cannot write test script to file %1.")
                              .arg(_name).arg(x.what()));
        _name = oldname;
      }
    }
    void clear() {
      if (isWindowModified() &&
          QMessageBox::question(this, tr("Changes Not Saved"),
                                tr("Clear script without saving changes?"))
          != QMessageBox::Yes)
        return;
      _editor->clear();
      setWindowModified(false);
    }
    void modified() {
      setWindowModified(true);
      modified(this);
    }
    void run() {
      _status->setCurrentIndex(STATUS_RUNNING);
      bool oldRecordState(_record->isChecked());
      _record->setChecked(false);
      _record->setEnabled(false);
      _run->setEnabled(false);
      Script script;
      try {
        assert(connect(&script, SIGNAL(progress(QString, int, int, int)),
                       SIGNAL(progress(QString, int, int, int))));
        QString text(_editor->textCursor().selection().toPlainText());
        if (text.isEmpty()) text = _editor->toPlainText();
        run(_name, text, _screenshots->isChecked(), script);
        _status->setCurrentIndex(STATUS_SUCCESS);
      } catch (std::exception &x) {
        _status->setCurrentIndex(STATUS_ERROR);
        std::shared_ptr<Command> cmd(script.command());
        if (cmd)
          QMessageBox::critical(this,
                                tr("Test Failed"),
                                tr("<html>"
                                   "  <h1>Error [%1]</h1>"
                                   "  <dl>"
                                   "    <dt>Command:</dt><dd><code>%3</code></dd>"
                                   "    <dt>File:</dt><dd>%4</dd>"
                                   "    <dt>Line:</dt><dd>%5</dd>"
                                   "    <dt>Error Message:</dt><dd><pre>%2</pre></dd>"
                                   "  </dl>"
                                   "</html>")
                                .arg(demangle(typeid(x).name()))
                                .arg(x.what())
                                .arg(cmd->command())
                                .arg(cmd->file())
                                .arg(cmd->line()));
        else
          QMessageBox::critical(this,
                                tr("Test Failed"),
                                tr("<html>"
                                   "  <h1>Error [%1]</h1>"
                                   "  <p><code>%2</code></p>"
                                   "</html>")
                                .arg(demangle(typeid(x).name()))
                                .arg(QString(x.what()).replace("\n", "<br/>")));
      }
      _run->setEnabled(true);
      _record->setEnabled(true);
      _record->setChecked(oldRecordState);
    }
    void appendCommand(const QString& txt) {
      if (!_record->isChecked()) return;
      _editor->appendPlainText(txt);
      QScrollBar *vb(_editor->verticalScrollBar());
      _editor->moveCursor(QTextCursor::End);
      _editor->ensureCursorVisible();
      if (!vb) return;
      vb->setValue(vb->maximum());
    }
    void appendCommand(const QString& selector, const QString& txt) {
      if (!_record->isChecked()) return;
      QString text(_editor->toPlainText());
      QStringList lines(text.split("\n"));
      bool changed(false);
      while (lines.size() &&
             (lines.last()=="click "+selector ||
              lines.last().startsWith("setvalue "+selector+" -> "))) {
        lines.removeLast();
        changed = true;
      }
      if (changed) {
        _editor->setPlainText(lines.join("\n"));
        _editor->moveCursor(QTextCursor::End);
        _editor->ensureCursorVisible();
      }
      appendCommand(txt);
    }
    void appendWebLoadFinished(bool ok) {
      if (!_record->isChecked()) return;
      QString text(_editor->toPlainText());
      QStringList lines(text.split("\n"));
      if (ok && lines.size()>1 &&
          lines.last().startsWith("expect urlChanged") &&
          lines.at(lines.size()-2)=="expect loadStarted") {
        // replace three expect lines by one single line
        QString url(lines.last().replace("expect urlChanged", "").trimmed());
        lines.removeLast(); lines.removeLast();
        _editor->setPlainText(lines.join("\n"));
        _editor->moveCursor(QTextCursor::End);
        _editor->ensureCursorVisible();
        appendCommand("expect load "+url);
      } else {
        appendCommand("expect loadFinished "+QString(ok?"true":"false"));
      }
    }
    void unsupportedContent(QNetworkReply* reply) {
      if (!_record->isChecked()) return;
      QString filename(reply->url().toString().split('/').last());
      if (reply->header(QNetworkRequest::ContentDispositionHeader).isValid()) {
        QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
                     .toString());
        if (part.contains(QRegularExpression("attachment; *filename="))) {
          part.replace(QRegularExpression(".*attachment; *filename="), "");
          if (part.size()) filename = part;
        }
      }
      QString text(_editor->toPlainText());
      int pos1(text.lastIndexOf(QRegularExpression("^do ")));
      int pos2(text.lastIndexOf(QRegularExpression("^load ")));
      int pos3(text.lastIndexOf(QRegularExpression("^click ")));
      text.insert(std::max({pos1, pos2, pos3}), "download "+filename);
      _editor->setPlainText(text);
      _editor->moveCursor(QTextCursor::End);
      _editor->ensureCursorVisible();
    }
    void runEnabled(bool f = true) {
      _run->setEnabled(f);
    }
    void on__run_clicked() {
      run();
    }
    void on__from_textEdited(const QString& txt) {
      on__next_clicked();
    }
    void on__next_clicked() {
      if (_regex->isChecked())
        _editor->find(QRegExp(_from->text()));
      else
        _editor->find(_from->text());
    }
    void on__previous_clicked() {
      if (_regex->isChecked())
        _editor->find(QRegExp(_from->text()), QTextDocument::FindBackward);
      else
        _editor->find(_from->text(), QTextDocument::FindBackward);
    }
    void on__replace_clicked() {
      QTextCursor tc(_editor->textCursor());
      if (tc.hasSelection()) tc.insertText(_to->text());
    }
    void on__replaceAll_clicked() {
      QTextCursor cursor(_editor->textCursor());
      if (_regex->isChecked())
        _editor->setPlainText(_editor->toPlainText().replace(QRegExp(_from->text()), _to->text()));
      else
        _editor->setPlainText(_editor->toPlainText().replace(_from->text(), _to->text()));
      _editor->setTextCursor(cursor);
    }
    void startSearch() {
      _searchBar->show();
      _from->setFocus();
      _from->selectAll();
    }
    void startReplace() {
      _searchBar->show();
      _replaceBar->show();
      _to->setFocus();
      _to->selectAll();
    }
  protected:
    void closeEvent(QCloseEvent*) {
      close(this);
    }
    void keyPressEvent(QKeyEvent *e) {
      if (e->matches(QKeySequence::Find))
        startSearch();
      if (e->matches(QKeySequence::Replace))
        startReplace();
      if (e->matches(QKeySequence::FindNext))
        on__next_clicked();
      if (e->matches(QKeySequence::FindPrevious))
        on__previous_clicked();
      if (e->matches(QKeySequence::Cancel)) {
        _searchBar->hide();
        _replaceBar->hide();
      }        
      return QDockWidget::keyPressEvent(e);
    }
  private:
    enum RunStatus {
      STATUS_NONE = 0,
      STATUS_RUNNING,
      STATUS_SUCCESS,
      STATUS_ERROR
    };
  private:
    QString _name;
};

#endif
