/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program 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.
 *
 * This program 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/>.
 *
 * END software license
 */


/////////////////////// Qt includes
#include <QDebug>
#include <QLineEdit>
#include <QMainWindow>
#include <QJSEngine>
#include <QMessageBox>
#include <QObject>
#include <QSettings>
#include <QWidget>
#include <QFileDialog>
#include <QFile>
#include <QScrollBar>
#include <QCloseEvent>
#include <QPlainTextEdit>
#include <QMap>
#include <QProgressDialog>

#include <Qsci/qsciscintilla.h>
#include <Qsci/qscilexerjavascript.h>

/////////////////////// pappsomspp includes


/////////////////////// Local includes
#include "MsXpS/libXpertMassGui/JavaScriptingWnd.hpp"
#include "MsXpS/libXpertMassGui/JavaScriptWorker.hpp"
#include "MsXpS/libXpertMassGui/ScriptingHistoryListWidget.hpp"

#include "ui_JavaScriptingWnd.h"

namespace MsXpS
{
namespace libXpertMassGui
{

JavaScriptingWnd::JavaScriptingWnd(const QString &application_name,
                                   QMainWindow *parent)
  : QMainWindow(parent),
    m_applicationName(application_name),
    mp_javaScriptingGuiUtils(new JavaScriptingGuiUtils(this)),
    mp_scriptingEnvironment(new JavaScriptingEnvironment(this)),
    mp_ui(new ::Ui::JavaScriptingWnd)
{
  // qDebug();

  if(parent == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  mp_ui->setupUi(this);

  initialize();
}

JavaScriptingWnd::~JavaScriptingWnd()
{
  // qDebug() << "Destructing... with window visible:" << isVisible();

  writeSettings();
}

void
JavaScriptingWnd::closeEvent(QCloseEvent *event)
{
  // qDebug() << "Closing... with window visible:" << isVisible();

  writeSettings();
  event->accept();
}

void
JavaScriptingWnd::writeSettings()
{
  QSettings settings(MsXpS::libXpertMassCore::Utils::configSettingsFilePath,
                     QSettings::IniFormat);

  // qDebug() << "Writing settings to file:"
  // << MsXpS::libXpertMassCore::Utils::configSettingsFilePath;

  settings.beginGroup("JavaScriptingWnd");

  settings.setValue("geometry", saveGeometry());
  settings.setValue("windowState", saveState());

  // Colors

  // qDebug() << __FILE__ << __LINE__
  //<< "Setting m_successColor:" << m_successColor;

  settings.setValue("successColor", m_successColor);
  settings.setValue("undefinedColor", m_undefinedColor);
  settings.setValue("failureColor", m_failureColor);
  settings.setValue("commentColor", m_commentColor);
  settings.setValue("jsInputColor", m_jsInputColor);

  // Splitters
  settings.setValue("treeSplitter", mp_ui->treeSplitter->saveState());
  settings.setValue("historySplitter", mp_ui->treeSplitter->saveState());
  settings.setValue("scriptingSplitter", mp_ui->scriptingSplitter->saveState());
  settings.setValue("generalConfigSplitter",
                    mp_ui->generalConfigSplitter->saveState());
  settings.setValue("scriptingConfigSplitter",
                    mp_ui->scriptingConfigSplitter->saveState());

  // History
  settings.setValue("history", historyAsStringList(true));
  settings.setValue("historyLoggingType", m_historyLoggingType);

  // Visibility
  // The settings are only written when the window is either closed
  // or destroyed (the window should thus be marked invisible).

  // qDebug() << "Writing settings... with window visible:" << isVisible();

  settings.setValue("visible", isVisible());

  settings.endGroup();

  settings.sync();
}

void
JavaScriptingWnd::readSettings()
{
  QSettings settings(MsXpS::libXpertMassCore::Utils::configSettingsFilePath,
                     QSettings::IniFormat);

  // qDebug() << "Reading settings from file:"
  //          << MsXpS::libXpertMassCore::Utils::configSettingsFilePath;

  settings.beginGroup("JavaScriptingWnd");

  restoreGeometry(settings.value("geometry").toByteArray());
  restoreState(settings.value("windowState").toByteArray());

  // Colors
  uint tempColorUint;

  tempColorUint = settings.value("successColor").toUInt();
  if(tempColorUint != 0)
    m_successColor = settings.value("successColor").value<QColor>();
  m_logTextColorMap[LogTextColor::Success] = m_successColor;

  tempColorUint = settings.value("commentColor").toUInt();
  if(tempColorUint != 0)
    m_commentColor = settings.value("commentColor").value<QColor>();
  m_logTextColorMap[LogTextColor::Comment] = m_commentColor;

  tempColorUint = settings.value("undefinedColor").toUInt();
  if(tempColorUint != 0)
    m_undefinedColor = settings.value("undefinedColor").value<QColor>();
  m_logTextColorMap[LogTextColor::Undefined] = m_undefinedColor;

  tempColorUint = settings.value("failureColor").toUInt();
  if(tempColorUint != 0)
    m_failureColor = settings.value("failureColor").value<QColor>();
  m_logTextColorMap[LogTextColor::Failure] = m_failureColor;

  tempColorUint = settings.value("jsInputColor").toUInt();
  if(tempColorUint != 0)
    m_jsInputColor = settings.value("jsInputColor").value<QColor>();
  m_logTextColorMap[LogTextColor::JsInput] = m_jsInputColor;

#if 0
        QMapIterator<int, QColor> i(m_logTextColorMap);
        while (i.hasNext())
        {
          i.next();

          qDebug() << __FILE__ << __LINE__
            << i.key() << ": " << i.value() << endl;
        }
#endif

  // Splitters
  mp_ui->treeSplitter->restoreState(
    settings.value("treeSplitter").toByteArray());
  mp_ui->scriptingSplitter->restoreState(
    settings.value("scriptingSplitter").toByteArray());
  mp_ui->treeSplitter->restoreState(
    settings.value("historySplitter").toByteArray());
  mp_ui->generalConfigSplitter->restoreState(
    settings.value("generalConfigSplitter").toByteArray());
  mp_ui->scriptingConfigSplitter->restoreState(
    settings.value("scriptingConfigSplitter").toByteArray());

  // History
  restoreHistory(settings);

  // Set the index to outbound size, so that upon starting the dialog the
  // inTextWidget is blank and if the user explores the history by hitting
  // C-Up, then the last history item shows.

  // Also, set the anchor to that value. If the user then moves the keys in
  // the input text edit with Ctrl and Shift, then selection start at the
  // present index with anchor at this same index.

  m_lastHistoryIndex = m_historyAnchorIndex = mp_ui->historyListWidget->count();

  m_historyLoggingType =
    static_cast<HistoryLoggingType>(restoreHistoryLoggingType(settings));

  bool wasVisible = settings.value("visible").toBool();
  setVisible(wasVisible);

  // qDebug() << "The window was visible?" << wasVisible;

  settings.endGroup();
}

void
JavaScriptingWnd::initializeJsEngine(QJSEngine *js_engine_p)
{
  Q_ASSERT(js_engine_p != nullptr);
  Q_ASSERT(mp_javaScriptingGuiUtils != nullptr);

  // We want to provide the user with print() and printToFile() functions that
  // are not available in ECMAScript. These functions are defined in the
  // ScriptingGuiUtils class.

  QJSValue returned_js_value;

  mp_scriptingEnvironment->exposeQObject(
    js_engine_p,
    "JavaScriptingGuiUtils",
    "", /*alias*/
    "The JavaScriptingGuiUtils provide utility functions",
    mp_javaScriptingGuiUtils,
    nullptr,
    returned_js_value,
    QJSEngine::ObjectOwnership::CppOwnership);

  js_engine_p->globalObject().setProperty("print",
                                          returned_js_value.property("print"));

  js_engine_p->globalObject().setProperty(
    "printToFile", returned_js_value.property("printToFile"));

  js_engine_p->globalObject().setProperty("msleep",
                                          returned_js_value.property("msleep"));

  js_engine_p->globalObject().setProperty("sleep",
                                          returned_js_value.property("sleep"));

  js_engine_p->globalObject().setProperty(
    "processEvents", returned_js_value.property("processEvents"));
}

void
JavaScriptingWnd::initialize()
{
  connect(mp_ui->historyListWidget,
          &ScriptingHistoryListWidget::setSelectedItemsTextToScriptInput,
          this,
          &JavaScriptingWnd::codeTextToCodeEditor);

  mp_codeEditor              = new QsciScintilla(this);
  QsciLexerJavaScript *lexer = new QsciLexerJavaScript(mp_codeEditor);
  mp_codeEditor->setLexer(lexer);

  // Additional mp_codeEditor settings
  mp_codeEditor->setAutoIndent(true);
  mp_codeEditor->setIndentationGuides(true);
  mp_codeEditor->setMarginLineNumbers(1, true);
  mp_codeEditor->setMarginWidth(1, "0000");
  mp_codeEditor->setBraceMatching(QsciScintilla::SloppyBraceMatch);

  // Set font
  QFont font("Consolas", 10);
  mp_codeEditor->setFont(font);
  lexer->setFont(font);

  mp_ui->qsciVerticalLayout->addWidget(mp_codeEditor);

  // This dialog window may be used by more than a single application, thus
  // set the app name along with the title.

  this->setWindowTitle(
    QString("%1 - Scripting console").arg(m_applicationName));

  connect(mp_ui->resetHistoryPushButton,
          &QPushButton::released,
          this,
          &JavaScriptingWnd::resetHistory);

  connect(mp_ui->saveHistoryPushButton,
          &QPushButton::released,
          this,
          &JavaScriptingWnd::saveHistory);

  connect(mp_ui->historyListWidget,
          &QListWidget::itemActivated,
          this,
          &JavaScriptingWnd::historyListWidgetItemActivated);

  connect(mp_ui->historyListWidget,
          &ScriptingHistoryListWidget::currentRowChanged,
          this,
          &JavaScriptingWnd::historyListWidgetCurrentRowChanged);

  connect(mp_ui->historyRegExpLineEdit,
          &QLineEdit::returnPressed,
          this,
          &JavaScriptingWnd::historyRegExpLineEditReturnPressed);

  connect(mp_ui->jsRefSearchLineEdit,
          &QLineEdit::returnPressed,
          this,
          &JavaScriptingWnd::jsRefTextSearchLineEditReturnPressed);

  connect(mp_ui->logOnSuccessCheckBox,
          &QCheckBox::checkStateChanged,
          this,
          &JavaScriptingWnd::historyLoggingCheckBoxStateChanged);

  connect(mp_ui->logOnFailureCheckBox,
          &QCheckBox::checkStateChanged,
          this,
          &JavaScriptingWnd::historyLoggingCheckBoxStateChanged);

  mp_ui->historyListWidget->setSelectionMode(
    QAbstractItemView::ExtendedSelection);
  mp_ui->historyListWidget->setVerticalScrollMode(
    QAbstractItemView::ScrollPerItem);

  // Create the menu and menu items.

  // File menu
  mp_fileMenu = menuBar()->addMenu("&File");

  mp_loadScriptFileAct = new QAction(
    tr("&Load a JavaScript file in the editor"), dynamic_cast<QObject *>(this));
  mp_loadScriptFileAct->setShortcut(QKeySequence("Ctrl+L, F"));
  mp_loadScriptFileAct->setStatusTip(
    tr("Load a JavaScript file in the editor"));

  connect(
    mp_loadScriptFileAct, SIGNAL(triggered()), this, SLOT(loadScriptFile()));

  mp_fileMenu->addAction(mp_loadScriptFileAct);

  mp_ui->jsReferenceTextTabWidget->setMovable(true);

  // Register the JS reference texts for this
  // very same library's projects: libXpertMassCore and libXpertMassGui.'
  registerKnownJsReferenceTexts();
  // This actually displays the JS reference texts previously registered above.
  displayAllMappedJsRefTextsToTabs();

  initializeJsEngine(mp_scriptingEnvironment->getJsEngine());

  readSettings();
}

JavaScriptingEnvironment *
JavaScriptingWnd::getScriptingEnvironment()
{
  return mp_scriptingEnvironment;
}

void
JavaScriptingWnd::show()
{
  QMainWindow::show();
}

void
JavaScriptingWnd::hide()
{
  QMainWindow::hide();
}

void
JavaScriptingWnd::registerKnownJsReferenceTexts()
{
  // The JS reference text exist as a number of files in the doc/js_reference
  // directory and are made available as Qt resources.

#if 0
// Debugging code to list all the resources visible from here.
QDir resourceDir(":/");
    QStringList allFiles = resourceDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
    QStringList allDirs = resourceDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);

    qDebug() << "Root resource files:" << allFiles;
    qDebug() << "Root resource directories:" << allDirs;

    // List subdirectories too
    for (const QString &dir : allDirs) {
        QDir subDir(":/" + dir);
        qDebug() << "Files in" << dir << ":" << subDir.entryList(QDir::Files);
    }
#endif


  ////////////////// generalities about JavaScript ///////////////////

  QFile jsGeneralitiesTextFile(":/core/js_generalities_text.txt");
  QString jsGeneralitiesText;

  if(!jsGeneralitiesTextFile.open(QFile::ReadOnly | QFile::Text))
    {
      qDebug() << "Attention JavaScript generalities text not available:"
               << jsGeneralitiesTextFile.errorString();
    }
  else
    {
      jsGeneralitiesText = jsGeneralitiesTextFile.readAll();
      registerJsReferenceText("Generalities", jsGeneralitiesText);

      // qDebug() << "The JS generalities text has size"
      //          << m_jsRefLabelRefTextMap.value("Generalities").size();
    }


  //////////////////// pappsomspp core and widget ////////////////////

  QFile jsClassRefPappsomsppCoreTextFile(
    ":/pappsomspp_core/class_js_reference_text.txt");

  QString jsClassRefPappsomsppCoreText;

  if(!jsClassRefPappsomsppCoreTextFile.open(QFile::ReadOnly | QFile::Text))
    {
      qDebug() << "Attention JavaScript class reference text not available for "
                  "libPappsomsppCore:"
               << jsClassRefPappsomsppCoreTextFile.errorString();
    }
  else
    {
      jsClassRefPappsomsppCoreText = jsClassRefPappsomsppCoreTextFile.readAll();
      registerJsReferenceText("libPappsomsppCore",
                              jsClassRefPappsomsppCoreText);

      // qDebug() << "The libPappsomsppCore reference text has size"
      //          << m_jsRefLabelRefTextMap.value("libPappsomsppCore").size();
    }

  // Note that this one will only be available when the program that uses this
  // JavaScriptingWnd class actually links to libpappsomspp-widget (MineXpert
  // but not MassXpert, for example).

  QFile jsClassRefPappsomsppGuiTextFile(
    ":/pappsomspp_gui/class_js_reference_text.txt");

  QString jsClassRefPappsomsppGuiText;

  if(!jsClassRefPappsomsppGuiTextFile.open(QFile::ReadOnly | QFile::Text))
    {
      qDebug() << "Attention JavaScript class reference text not available for "
                  "libPappsomsppGui:"
               << jsClassRefPappsomsppGuiTextFile.errorString();
    }
  else
    {
      jsClassRefPappsomsppGuiText = jsClassRefPappsomsppGuiTextFile.readAll();
      registerJsReferenceText("libPappsomsppGui", jsClassRefPappsomsppGuiText);

      // qDebug() << "The libPappsomsppGui reference text has size"
      //          << m_jsRefLabelRefTextMap.value("libPappsomsppGui").size();
    }


  //////////////////// libXpertMass and libXpertMassGui ////////////////////

  QFile jsClassRefXpertMassTextFile(":/core/class_js_reference_text.txt");

  QString jsClassRefXpertMassText;

  if(!jsClassRefXpertMassTextFile.open(QFile::ReadOnly | QFile::Text))
    {
      qDebug() << "Attention JavaScript class reference text not available for "
                  "libXpertMassCore:"
               << jsClassRefXpertMassTextFile.errorString();
    }
  else
    {
      jsClassRefXpertMassText = jsClassRefXpertMassTextFile.readAll();
      registerJsReferenceText("libXpertMassCore", jsClassRefXpertMassText);

      // qDebug() << "The libXpertMassCore reference text has size"
      //          << m_jsRefLabelRefTextMap.value("libXpertMassCore ").size();
    }

  QFile jsClassRefXpertMassGuiTextFile(":/gui/class_js_reference_text.txt");

  QString jsClassRefXpertMassGuiText;

  if(!jsClassRefXpertMassGuiTextFile.open(QFile::ReadOnly | QFile::Text))
    {
      qDebug() << "Attention JavaScript class reference text not available for "
                  "libXpertMassGui:"
               << jsClassRefXpertMassGuiTextFile.errorString();
    }
  else
    {
      jsClassRefXpertMassGuiText = jsClassRefXpertMassGuiTextFile.readAll();
      registerJsReferenceText("libXpertMassGui", jsClassRefXpertMassGuiText);

      // qDebug() << "The libXpertMassGui reference text has size"
      //          << m_jsRefLabelRefTextMap.value("libXpertMassGui").size();
    }
}

void
JavaScriptingWnd::registerJsReferenceText(const QString &label,
                                          const QString &text)
{
  if(m_jsRefLabelRefTextMap.contains(label))
    qDebug() << "A map entry with same" << label
             << "key is there already. Erasing it.";

  m_jsRefLabelRefTextMap[label] = text;

  // qDebug() << "Registered JS reference text for" << label;
}

void
JavaScriptingWnd::displayMappedJsRefTextToTab(const QString &label)
{
  // The m_jsRefLabelRefTextMap relates a label, like "libXpertMass" to
  // a JS reference text to be displayed in its own Tab of the QTabWidget.

  if(!m_jsRefLabelRefTextMap.contains(label))
    {
      qDebug() << "The map does not contain any such key:" << label
               << "Please first addLabelledJsReferenceText().";
      return;
    }

  QTabWidget *js_reference_text_tab_widget_p = mp_ui->jsReferenceTextTabWidget;

  // qDebug() << "Before displaying JS reference text for" << label
  //          << ", the js reference tab widget has "
  //          << js_reference_text_tab_widget_p->count() << "pages";

  QWidget *new_widget_p      = new QWidget();
  QVBoxLayout *page_layout_p = new QVBoxLayout(new_widget_p);
  page_layout_p->setContentsMargins(0, 0, 0, 0);
  QPlainTextEdit *text_edit_widget_p = new QPlainTextEdit();
  text_edit_widget_p->setObjectName("plain_text_edit");
  text_edit_widget_p->setPlainText(m_jsRefLabelRefTextMap.value(label));
  page_layout_p->addWidget(new_widget_p);

  js_reference_text_tab_widget_p->addTab(new_widget_p, label);

  // qDebug() << "And now, the js reference tab widget has"
  //          << js_reference_text_tab_widget_p->count() << "pages";
}

void
JavaScriptingWnd::displayAllMappedJsRefTextsToTabs()
{
  // The m_jsRefLabelRefTextMap relates a label, like "libXpertMass" to
  // a JS reference text to be displayed in its own Tab of the QTabWidget.

  QTabWidget *js_reference_text_tab_widget_p = mp_ui->jsReferenceTextTabWidget;

  // qDebug() << "The js reference tab widget has "
  //          << js_reference_text_tab_widget_p->count() << "tab pages";

  // The foreach() in QMap iterates over the values by default.
  foreach(const QString &label, m_jsRefLabelRefTextMap.keys())
    {
      QString reference_text = m_jsRefLabelRefTextMap.value(label);

      // qDebug() << "Now handling the JS reference text for" << label
      //          << "with text:" << reference_text;

      QWidget *new_widget_p      = new QWidget();
      QVBoxLayout *page_layout_p = new QVBoxLayout(new_widget_p);
      page_layout_p->setContentsMargins(0, 0, 0, 0);

      QPlainTextEdit *text_edit_widget_p = new QPlainTextEdit();
      text_edit_widget_p->setObjectName("plain_text_edit");
      text_edit_widget_p->setPlainText(reference_text);
      page_layout_p->addWidget(text_edit_widget_p);

      js_reference_text_tab_widget_p->addTab(new_widget_p, label);
    }

  // qDebug() << "The js reference tab widget has now"
  //          << js_reference_text_tab_widget_p->count() << "tab pages";
}

void
JavaScriptingWnd::historyListWidgetCurrentRowChanged(int row)
{

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "row is " << row << "; set index to that value";

  m_lastHistoryIndex = row;
}

void
JavaScriptingWnd::codeTextToCodeEditor(const QString code_text, bool overwrite)
{
  if(overwrite)
    {
      mp_codeEditor->clear();
      mp_codeEditor->setText(code_text);
    }
  else
    {
      mp_codeEditor->insert(code_text);
    }
}

void
JavaScriptingWnd::historyListWidgetItemActivated()
{
  // We need to get a string of all the currently selected items, and then
  // set that string to the inTextEdit widget in overwrite mode (true).

  QString code_text = mp_ui->historyListWidget->selectedItemsText();

  Qt::KeyboardModifiers modifiers = QGuiApplication::queryKeyboardModifiers();

  // If the user presses shift, than mean they do not want to replace
  // the contents in the script editor, but append new content.
  bool overwrite = !(modifiers & Qt::ShiftModifier);

  codeTextToCodeEditor(code_text, overwrite);
}

void
JavaScriptingWnd::historyLoggingCheckBoxStateChanged()
{
  // Rough work, iterate in the check boxes and construct a value with
  // their checkState

  HistoryLoggingType loggingType = HistoryLoggingType::Never;

  if(mp_ui->logOnSuccessCheckBox->isChecked())
    loggingType = static_cast<HistoryLoggingType>(
      loggingType | HistoryLoggingType::OnSuccess);

  if(mp_ui->logOnFailureCheckBox->isChecked())
    loggingType = static_cast<HistoryLoggingType>(
      loggingType | HistoryLoggingType::OnFailure);

  m_historyLoggingType = loggingType;
}

void
JavaScriptingWnd::historyRegExpLineEditReturnPressed()
{
  bool caseSensitive = mp_ui->historyCaseSensitiveCheckBox->isChecked();

  QRegularExpression regExp(mp_ui->historyRegExpLineEdit->text(),
                            (caseSensitive
                               ? QRegularExpression::NoPatternOption
                               : QRegularExpression::CaseInsensitiveOption));

  for(int iter = 0; iter < mp_ui->historyListWidget->count(); ++iter)
    {
      QString itemText = mp_ui->historyListWidget->item(iter)->text();

      QRegularExpressionMatch match = regExp.match(itemText);

      if(match.hasMatch())
        {
          mp_ui->historyListWidget->item(iter)->setHidden(false);
          mp_ui->historyListWidget->setCurrentRow(iter,
                                                  QItemSelectionModel::Select);
        }
      else
        mp_ui->historyListWidget->item(iter)->setHidden(true);
    }
}

int
JavaScriptingWnd::regexFilterJsReferenceText(const QString &input_text,
                                             QString &filtered_text)
{
  filtered_text.clear();

  int contextLines                 = mp_ui->contextLinesSpinBox->value();
  QString searched_text_expression = mp_ui->jsRefSearchLineEdit->text();
  bool caseSensitive = mp_ui->jsRefCaseSensitiveCheckBox->isChecked();

  QRegularExpression regExp(searched_text_expression,
                            (caseSensitive
                               ? QRegularExpression::NoPatternOption
                               : QRegularExpression::CaseInsensitiveOption));

  QStringList lines = input_text.split("\n");

  for(int iter = 0; iter < lines.size(); ++iter)
    {
      // We may modify newIdx later, but if not, then it needs to be identical
      // to iter.
      int newIdx = iter;

      QRegularExpressionMatch match = regExp.match(lines.at(iter));

      if(match.hasMatch())
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "match at iter:" << iter;

          // Handle the context lines insertion upstream of the matched line.

          // The position of the context line, either upstream or downstream of
          // the matched line.
          int pos;

          if(contextLines > 0)
            {
              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "Handling upstream text";

              for(pos = contextLines; pos > 0; --pos)
                {
                  newIdx = iter - pos;

                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "newIdx: " << newIdx;

                  if(newIdx < 0)
                    {
                      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ <<
                      // "()"
                      //<< "continue because newIdx less than 0";
                      continue;
                    }

                  filtered_text.append(lines.at(newIdx) + "\n");

                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "appended " << lines.at(newIdx) << "for index" << newIdx;
                }
            }

          // Now that we have prepended the upstream context lines, actually
          // insert the matching line.

          filtered_text.append(lines.at(iter) + "\n");
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "appended matched line: " << lines.at(iter);

          // Handle the context lines insertion downstream of the matched line.

          if(contextLines > 0)
            {
              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "Handling downstream text";

              for(pos = 1; pos <= contextLines; ++pos)
                {
                  newIdx = iter + pos;

                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "newIdx: " << newIdx;

                  if(newIdx >= lines.size())
                    {
                      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ <<
                      // "()"
                      //<< "breaking because newIdx is greater or equal to
                      // size()";
                      break;
                    }

                  filtered_text.append(lines.at(newIdx) + "\n");

                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "appended " << lines.at(newIdx) << "for index" << newIdx;
                }
            }

          // Add match delimiting line.
          filtered_text.append("~~~~\n");

          // Now, prepare next round in the lines string list at the new index +
          // 1 because we do not want to add matches within the
          // [-contextLines,+contextLines] text range.

          // iter will be incremented at next loop iteration. If no context line
          // was asked for, newIdx is equal to iter, so the following statement
          // does not change anything. If context lines were asked for, newIdx
          // is the index of the latest lines line appended to newText.

          iter = newIdx;

          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "after iter = newIdx, new iter: " << iter;
        }
      // End of
      // if(match.hasMatch())
    }

  return filtered_text.size();
}

int
JavaScriptingWnd::verbatimFilterJsReferenceText(const QString &input_text,
                                                QString &filtered_text)
{
  filtered_text.clear();

  int contextLines                 = mp_ui->contextLinesSpinBox->value();
  QString searched_text_expression = mp_ui->jsRefSearchLineEdit->text();

  bool caseSensitive = mp_ui->jsRefCaseSensitiveCheckBox->isChecked();
  Qt::CaseSensitivity case_sensitivity =
    caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;

  QStringList lines = input_text.split("\n");

  for(int iter = 0; iter < lines.size(); ++iter)
    {
      // We may modify newIdx later, but if not, then it needs to be identical
      // to iter.
      int newIdx = iter;


      if(lines.at(iter).contains(searched_text_expression, case_sensitivity))
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "match at iter:" << iter;

          // Handle the context lines insertion upstream of the matched line.

          if(contextLines > 0)
            {
              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "Handling upstream text";

              // pos: the position of the context line, either upstream or
              // downstream of the matched line.
              for(int pos = contextLines; pos > 0; --pos)
                {
                  newIdx = iter - pos;

                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "newIdx: " << newIdx;

                  if(newIdx < 0)
                    {
                      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ <<
                      // "()"
                      //<< "continue because newIdx less than 0";
                      continue;
                    }

                  filtered_text.append(lines.at(newIdx) + "\n");

                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "appended " << lines.at(newIdx) << "for index" << newIdx;
                }
            }

          // Now that we have prepended the upstream context lines, actually
          // insert the matching line.

          filtered_text.append(lines.at(iter) + "\n");
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "appended matched line: " << lines.at(iter);

          // Handle the context lines insertion downstream of the matched line.

          if(contextLines > 0)
            {
              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "Handling downstream text";

              // pos: the position of the context line, either upstream or
              // downstream of the matched line.
              for(int pos = 1; pos <= contextLines; ++pos)
                {
                  newIdx = iter + pos;

                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "newIdx: " << newIdx;

                  if(newIdx >= lines.size())
                    {
                      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ <<
                      // "()"
                      //<< "breaking because newIdx is greater or equal to
                      // size()";
                      break;
                    }

                  filtered_text.append(lines.at(newIdx) + "\n");

                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "appended " << lines.at(newIdx) << "for index" << newIdx;
                }
            }

          // Add match delimiting line.
          filtered_text.append("~~~~\n");

          // Now, prepare next round in the lines string list at the new index +
          // 1 because we do not want to add matches within the
          // [-contextLines,+contextLines] text range.

          // iter will be incremented at next loop iteration. If no context line
          // was asked for, newIdx is equal to iter, so the following statement
          // does not change anything. If context lines were asked for, newIdx
          // is the index of the latest lines line appended to newText.

          iter = newIdx;

          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "after iter = newIdx, new iter: " << iter;
        }
      // End of
      // if(match.hasMatch())
    }

  return filtered_text.size();
}

void
JavaScriptingWnd::jsRefTextSearchLineEditReturnPressed()
{
  // Start by defining which JS reference widget is currently shown.
  QWidget *current_widget_p = mp_ui->jsReferenceTextTabWidget->currentWidget();

  QPlainTextEdit *plain_text_edit_widget_p = dynamic_cast<QPlainTextEdit *>(
    current_widget_p->findChild<QWidget *>("plain_text_edit"));
  // qDebug() << "The text in the widget:" <<
  // plain_text_edit_widget_p->toPlainText();

  // What is the tab currently shown ?
  int tab_widget_page_index =
    mp_ui->jsReferenceTextTabWidget->indexOf(current_widget_p);
  QString tab_text =
    mp_ui->jsReferenceTextTabWidget->tabText(tab_widget_page_index);

  QString reference_text = m_jsRefLabelRefTextMap.value(tab_text);
  QString filtered_text;

  // If nothing in in the text search line edit widget, just display the
  // original JS reference text.
  if(mp_ui->jsRefSearchLineEdit->text().isEmpty())
    {
      plain_text_edit_widget_p->setPlainText(reference_text);
      return;
    }

  if(mp_ui->verbatimCheckBox->isChecked())
    verbatimFilterJsReferenceText(reference_text, filtered_text);
  else
    regexFilterJsReferenceText(reference_text, filtered_text);

  if(filtered_text.isEmpty())
    filtered_text =
      "No matches were found. Erase the regular expression and press the "
      "Return key.";

  plain_text_edit_widget_p->setPlainText(filtered_text);
}

// We want to filter the keyboard event so as to catch the arrow up/down
// key codes because they allow going through the command line history.
void
JavaScriptingWnd::keyPressEvent(QKeyEvent *event)
{

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "current index:" << m_lastHistoryIndex
  //<< "; item count:" << mp_ui->historyListWidget->count();

  if(event == nullptr)
    return;

  if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
    {
      Qt::KeyboardModifiers modifiers =
        QGuiApplication::queryKeyboardModifiers();

      if(modifiers & Qt::ControlModifier)
        {
          // The Ctrl key is pressed, the user want to actually run the
          // script in the code editor widget.

          runScript(mp_codeEditor->text());
          event->accept();
        }

      return;
    }

  if(event->key() == Qt::Key_Down || event->key() == Qt::Key_Up)
    {

      Qt::KeyboardModifiers modifiers =
        QGuiApplication::queryKeyboardModifiers();

      if(modifiers & Qt::AltModifier)
        {
          // If there is no history, then nothing to hope.
          if(mp_ui->historyListWidget->count() == 0)
            {
              event->accept();
              return;
            }

          if(event->key() == Qt::Key_Down)
            {

              // qdebug() << __file__ << __line__
              //<< "key down with index:" << m_lastHistoryIndex
              //<< "while count is:" << mp_ui->historyListWidget->count()
              //<< "going to increment index by one.";

              // Key_Down, means that we are going one item more recently in the
              // history. That is, we are moving down in the history list widget
              // to
              // more recent entries, thus we increment the index by one:

              ++m_lastHistoryIndex;

              if(!(modifiers & Qt::ShiftModifier))
                {

                  // If Shift is not pressed, there is no ongoing selection,
                  // that
                  // is, the new index is the sole selected item in the history
                  // list
                  // widget.

                  // Be careful not to go past the last history item!

                  if(m_lastHistoryIndex >= mp_ui->historyListWidget->count())
                    {
                      // qDebug() << __FILE__ << __LINE__
                      //<< "count is:" <<  mp_ui->historyListWidget->count()
                      //<< "but index is now >= count";

                      // The user want to go past the last history item, that is
                      // she
                      // wants to get an empty line edit so as to enter a brand
                      // new
                      // command line. So clear the line edit and return true
                      // (we
                      // processed the key code). Note that we set
                      // m_lastHistoryIndex =
                      // mp_ui->historyListWidget->count(); which would be fatal
                      // if we did
                      // not care of that value when using it later. The idea is
                      // that if
                      // the user finally renounces entering a new command line,
                      // but
                      // strikes the C-Up to actually go back to the last item
                      // of the
                      // history, we just need to decrement m_lastHistoryIndex
                      // by one to
                      // point the the right mp_ui->historyListWidget item.
                      //
                      // We do not need to fear problems here, because we are
                      // not
                      // accessing the history list widget from this function in
                      // this
                      // present case: we just return.

                      // Set the current item in the history list widget to the
                      // last
                      // item. Not a single item must be selected.

                      m_lastHistoryIndex = mp_ui->historyListWidget->count();

                      mp_ui->historyListWidget->deselectAll();
                      mp_ui->historyListWidget->setCurrentRow(
                        m_lastHistoryIndex - 1, QItemSelectionModel::Deselect);

                      // qDebug() << __FILE__ << __LINE__
                      //<< "Deselected all the items and set current to the last
                      // one.";

                      // We need to set that last index value to count() here,
                      // because
                      // the call to setCurrentRow triggers updating of that
                      // index to
                      // the (m_lastHistoryIndex - 1) value (see signal
                      // connection to
                      // currentRowChanged).
                      m_lastHistoryIndex = mp_ui->historyListWidget->count();

                      // qDebug() << __FILE__ << __LINE__
                      //<< "So the index is set to the count::" <<
                      // m_lastHistoryIndex;

                      // Now set the new selection anchor to that same value for
                      // later
                      // use if the user starts a selection process by pressing
                      // the
                      // Shift key.

                      m_historyAnchorIndex = m_lastHistoryIndex;

                      // qDebug() << __FILE__ << __LINE__
                      //<< "Set the anchor index to the same index value:" <<
                      // m_historyAnchorIndex;

                      mp_codeEditor->clear();

                      // qDebug() << __FILE__ << __LINE__
                      //<< "Finally, cleared the inTextEdit";
                      event->accept();
                      return;
                    }
                  else
                    {
                      // We are not past the end of the history item list, all
                      // is
                      // fine, simply selecting the item at m_lastHistoryIndex.

                      // qDebug() << __FILE__ << __LINE__
                      //<< "Should be selecting  the single item at index " <<
                      // m_lastHistoryIndex;

                      mp_ui->historyListWidget->item(m_lastHistoryIndex)
                        ->setSelected(true);

                      // Also set the anchor index to the same value.

                      m_historyAnchorIndex = m_lastHistoryIndex;

                      // qDebug() << __FILE__ << __LINE__
                      //<< "Set the anchor index to the same index value:" <<
                      // m_historyAnchorIndex;
                    }
                }
              else // the shift key modifier was pressed
                {
                  // If Shift is pressed, the user is willing to make a
                  // selection.

                  // The Key_Down moves the history down one element. But
                  // imagine the
                  // user was selecting upwards before this key, then we are
                  // reducing
                  // the selection. The anchor index value can help us.

                  // The user has effectively selected something if the anchor
                  // is
                  // different than the current index.

                  if(m_lastHistoryIndex >= mp_ui->historyListWidget->count())
                    {
                      // qDebug() << __FILE__ << __LINE__
                      //<< "count is:" <<  mp_ui->historyListWidget->count()
                      //<< "but index is now >= count"
                      //<< "setting last index to count"
                      //<< "not changing the anchro"
                      //<< "should be selecting items from " <<
                      // m_historyAnchorIndex
                      //<< "to " << m_lastHistoryIndex;

                      m_lastHistoryIndex = mp_ui->historyListWidget->count();
                    }

                  if(m_lastHistoryIndex != m_historyAnchorIndex)
                    {

                      // qDebug() << __FILE__ << __LINE__
                      //<< "We are selecting items between indices " <<
                      // m_lastHistoryIndex
                      //<< "to " << m_historyAnchorIndex;
                    }
                  else
                    {
                      // qDebug() << __FILE__ << __LINE__
                      //<< "last index and anchor index have the same value:" <<
                      // m_lastHistoryIndex
                      //<< ": should select the single item at
                      // m_lastHistoryIndex";
                    }
                }
              // End of
              // else // the shift key modifier was pressed
            }
          // End of
          // if(event->key() == Qt::Key_Down)
          else if(event->key() == Qt::Key_Up)
            {

              // qDebug() << __FILE__ << __LINE__
              //<< "key up with index: " << m_lastHistoryIndex
              //<< "while count is:" << mp_ui->historyListWidget->count()
              //<< "going to decrement index by one.";

              if(m_lastHistoryIndex > 0)
                {
                  --m_lastHistoryIndex;

                  // qDebug() << __FILE__ << __LINE__
                  //<< "decremented index by one to value: " <<
                  // m_lastHistoryIndex;
                }
              else
                {

                  // qDebug() << __FILE__ << __LINE__
                  //<< "Not decremented, m_lastHistoryIndex is already == 0.";
                }

              if(!(modifiers & Qt::ShiftModifier))
                {

                  // If Shift is not pressed, there is no ongoing selection,
                  // that
                  // is, the new index is the sole selected item in the history
                  // list
                  // widget.

                  if(m_lastHistoryIndex < 0)
                    {
                      // qDebug() << __FILE__ << __LINE__
                      //<< "but now index is less than 0, so set to 0"
                      //<< "same for the anchor because we are not pressing
                      // shift."
                      //<< "The history list item at index 0 should thus be
                      // selected.";

                      m_lastHistoryIndex   = 0;
                      m_historyAnchorIndex = m_lastHistoryIndex;
                    }
                  else
                    {
                      // We are not past the top of the history item list, all
                      // is
                      // fine, simply selecting the item at m_lastHistoryIndex.

                      // qDebug() << __FILE__ << __LINE__
                      //<< "Should be selecting  the single item at index " <<
                      // m_lastHistoryIndex;

                      // Also set the anchor index to the same value.

                      m_historyAnchorIndex = m_lastHistoryIndex;

                      // qDebug() << __FILE__ << __LINE__
                      //<< "Set the anchor index to the same index value:" <<
                      // m_historyAnchorIndex;
                    }
                }
              else // the shift key modifier was pressed
                {
                  // If Shift is pressed, the user is willing to make a
                  // selection.

                  // The Key_Up moves the history up element. But imagine the
                  // user was selecting downwards before this key, then we are
                  // reducing
                  // the selection. The anchor index value can help us.

                  // The user has effectively selected something if the anchor
                  // is
                  // different than the current index.

                  if(m_lastHistoryIndex != m_historyAnchorIndex)
                    {

                      // qDebug() << __FILE__ << __LINE__
                      //<< "We are selecting items between indices " <<
                      // m_lastHistoryIndex
                      //<< "to " << m_historyAnchorIndex;
                    }
                  else
                    {
                      // qDebug() << __FILE__ << __LINE__
                      //<< "last index and anchor index have the same value:" <<
                      // m_lastHistoryIndex
                      //<< ": should select the single item at
                      // m_lastHistoryIndex";
                    }
                }
              // End of
              // else // the shift key modifier was pressed
            }
          // End of
          // else if(event->key() == Qt::Key_Up)

          // At this point we have anchor and index, so we can select the
          // item(s):
          mp_ui->historyListWidget->selectItemIndices(m_lastHistoryIndex,
                                                      m_historyAnchorIndex);

          // Now that the item(s) have been selected, let's get their text and
          // put that text into the input text edit.

          mp_ui->historyListWidget->overwriteSelectedItemsText();

          // We did handle the event.
          event->accept();
          return;
        }
      // End of
      // if(event->key() == Qt::Key_Down || event->key() == Qt::Key_Up)
    }
  // End of
  // if(modifiers & Qt::ControlModifier)

  // We did not handle the event.
  return;
}

QStringList
JavaScriptingWnd::historyAsStringList(bool colorTagged)
{
  // We want to create a string list, where each string is a history item
  // from the history list widget. But, if colorTagged is set to true,
  // we want to prepend to each such string a <color>x</color> tag
  // reflecting the color that was used to render the list widget item. This
  // way, we serialize also the color of the item, not only the text.

  QStringList historyList;

  int count = mp_ui->historyListWidget->count();

  for(int iter = 0; iter < count; ++iter)
    {
      QListWidgetItem *item = mp_ui->historyListWidget->item(iter);

      QString itemText = item->text();

      if(colorTagged)
        {
          QBrush brush = item->foreground();
          QColor color = brush.color();

          LogTextColor colorText = static_cast<LogTextColor>(
            m_logTextColorMap.key(color, LogTextColor::Comment));

          prependColorTag(itemText, colorText);
        }

      historyList.append(itemText);
    }

  return historyList;
}

int
JavaScriptingWnd::restoreHistory(QSettings &settings)
{
  QStringList wholeHistoryAsListOfStrings =
    settings.value("history").toStringList();

  qsizetype loaded_history_lines = 0;
  // qDebug() << "History has " << wholeHistoryAsListOfStrings.size() <<
  // "lines";

  QProgressDialog *progress_dialog_p = new QProgressDialog(this, Qt::Dialog);
  progress_dialog_p->setLabelText("Importing scripting history");
  progress_dialog_p->setMaximum(wholeHistoryAsListOfStrings.size());
  connect(progress_dialog_p, &QProgressDialog::canceled, this, [&]() {
    m_shouldHistoryLoadCancel = true;
  });

  // When we store the list items, we store them with a <color>xx</color>
  // tag prepended to the text. We use that that to establish the color with
  // which to display the text in the history listwidget.
  for(int iter = 0; iter < wholeHistoryAsListOfStrings.size(); ++iter)
    {
      QString historyText = wholeHistoryAsListOfStrings.at(iter);

      if(historyText.isEmpty())
        continue;

      LogTextColor color = LogTextColor::Comment;

      if(removeColorTag(historyText, color))
        {
          // color now contains the color (call returned true).
          logHistory(historyText, color);
        }
      else
        {
          // The color will be the color of the comments.
          logHistory(historyText, color);
        }

      ++loaded_history_lines;
      progress_dialog_p->setValue(loaded_history_lines);

      if(m_shouldHistoryLoadCancel)
        break;
    }


  delete progress_dialog_p;

  // Make sure the history list widget shows the last item.
  mp_ui->historyListWidget->setCurrentRow(mp_ui->historyListWidget->count() -
                                          1);

  // Now make sure that we set the last history item index to the size of
  // the history list. That is outbound, but we are not accessing that item,
  // never, always making sure we do not try to accessit. This should go
  // along the fact that the inTextEdit should be blank and the first C-Up
  // key strike by the user to explore history should print the last history
  // item fine.

  // Also, set the anchor to that value. If the user then moves the keys in
  // the input text edit with Ctrl and Shift, then selection start at the
  // present index with anchor at this same index.

  m_lastHistoryIndex = m_historyAnchorIndex = mp_ui->historyListWidget->count();

  // qDebug() << __FILE__ << __LINE__
  //<< "Finished restoring history with " << mp_ui->historyListWidget->count()
  //<< "items"
  //<< "set the index to" << m_lastHistoryIndex << "and the anchor at the same
  // value.";

  return m_lastHistoryIndex;
}

int
JavaScriptingWnd::restoreHistoryLoggingType(QSettings &settings)
{

  // The type of history logging.
  HistoryLoggingType historyLoggingType = static_cast<HistoryLoggingType>(
    settings.value("historyLoggingType").toInt(), HistoryLoggingType::Always);

  if(historyLoggingType & HistoryLoggingType::OnSuccess)
    mp_ui->logOnSuccessCheckBox->setChecked(true);

  if(historyLoggingType & HistoryLoggingType::OnFailure)
    mp_ui->logOnFailureCheckBox->setChecked(true);

  return historyLoggingType;
}

void
JavaScriptingWnd::resetHistory()
{
  mp_ui->historyListWidget->clear();

  m_lastHistoryIndex = m_historyAnchorIndex = -1;
}

void
JavaScriptingWnd::saveHistory()
{
  QSettings settings(MsXpS::libXpertMassCore::Utils::configSettingsFilePath,
                     QSettings::IniFormat);

  settings.beginGroup("JavaScriptingWnd");

  // History
  settings.setValue("history", historyAsStringList(true));
  settings.setValue("historyLoggingType", m_historyLoggingType);

  settings.endGroup();

  settings.sync();
}

void
JavaScriptingWnd::showHistory()
{

  QList<QWidget *> widgetList =
    mp_ui->tabWidget->findChildren<QWidget *>("historyTab");
  if(widgetList.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Cannot be that no history tab be found."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  mp_ui->tabWidget->setCurrentWidget(widgetList.first());
}

QString
JavaScriptingWnd::historyAsString(bool colorTagged,
                                  const QString &lineSeparator)
{
  QStringList historyList = historyAsStringList(colorTagged);

  return historyList.join(lineSeparator);
}

void
JavaScriptingWnd::loadScriptFile(const QString &fileName)
{
  QString scriptFileName = fileName;

  if(scriptFileName.isEmpty())
    {

      QFileDialog fileDialog(this);
      fileDialog.setFileMode(QFileDialog::ExistingFile);
      fileDialog.setViewMode(QFileDialog::Detail);
      fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
      fileDialog.setDirectory(QDir::home());

      if(fileDialog.exec())
        {
          QStringList selected_files = fileDialog.selectedFiles();
          scriptFileName             = selected_files.first();
        }
      else
        return;
    }

  QFile file(scriptFileName);

  if(!file.open(QFile::ReadOnly | QFile::Text))
    return;

  QTextStream stream(&file);
  QString code_text = stream.readAll();

  file.close();

  if(code_text.isEmpty())
    return;

  mp_codeEditor->setText(code_text);
}

QColor
JavaScriptingWnd::logColor(LogTextColor contextColor)
{
  QColor color = m_logTextColorMap.value(contextColor);

  // qDebug() << "Returning color" << color << "for contextColor:" <<
  // contextColor;

  return color;
}

void
JavaScriptingWnd::logOutTextEdit(const QString &text, LogTextColor textColor)
{
  mp_ui->outTextEdit->moveCursor(QTextCursor::End);
  QTextCursor cursor(mp_ui->outTextEdit->textCursor());

  QTextCharFormat format;
  QColor reqColor = logColor(textColor);

  format.setForeground(QBrush(reqColor));
  cursor.setCharFormat(format);
  cursor.insertText(text);

  mp_ui->outTextEdit->verticalScrollBar()->setValue(
    mp_ui->outTextEdit->verticalScrollBar()->maximum());
}

/*!
\brief Outputs to the feedback text edit widget the text in the parameters.

The text that is crafted and displayed aims at identifying a JavaScript
property resulting from exposure of a QObject to the scripting engine.

\list

\li \a object_name: the name of the object as a JavaScript property name
\li \a object_alas: another way to access that property
\li \a object_description: descriptive text for the object exposed

\endlist
*/
void
JavaScriptingWnd::feedBackForExposedQObject(const QString &object_name,
                                            const QString &object_alias,
                                            const QString &object_description)
{
  QTextCursor cursor = mp_ui->feedbackTextEdit->textCursor();
  QTextCharFormat boldFormat;
  boldFormat.setFontWeight(QFont::Bold);
  QTextCharFormat normalFormat;
  normalFormat.setFontWeight(QFont::Normal);

  // Insert the object description first
  cursor.insertText(QString("%1:\n").arg(object_description), normalFormat);

  // Insert the object name, and may also the alias
  cursor.insertText(object_name, boldFormat);

  if(!object_alias.isEmpty())
    {
      cursor.insertText(" aliased as ", normalFormat);
      cursor.insertText(QString("%1").arg(object_alias), boldFormat);
    }

  cursor.insertText("\n\n");
}

void
JavaScriptingWnd::logHistory(const QString &text, LogTextColor textColor)
{
  // qDebug() << "Logging history" << text << ", before insertion of new item"
  //          << ", item count is:" << mp_ui->historyListWidget->count()
  //          << "; with last index:" << m_lastHistoryIndex
  //          << "with color:" << m_logTextColorMap[textColor];

  // Allocate a new item that is automatically set to the list widget.
  QListWidgetItem *item = new QListWidgetItem(mp_ui->historyListWidget);
  QFont font(item->font());
  font.setPointSize(11);
  item->setFont(font);

  // Make sure the item is rendered according to the textColor.
  QColor color = logColor(textColor);

  // qDebug() << "We should be logging history text" << text << "with color:" <<
  // color;
  item->setForeground(color);

  item->setText(text);

  // Since we just added a new item, we deselect all the history list widget
  // and set current item to the last one.
  mp_ui->historyListWidget->deselectAll();

  // qDebug() << __FILE__ << __LINE__
  //<< "Deselecting all items, setting current row to last item.";

  mp_ui->historyListWidget->setCurrentRow(m_lastHistoryIndex - 1,
                                          QItemSelectionModel::Deselect);

  // We need to set that last index value to count() here, because
  // the call to setCurrentRow triggers updating of that index to
  // the (m_lastHistoryIndex - 1) value (see signal connection to
  // currentRowChanged).

  // Also, set the anchor to that value. If the user then moves the keys in
  // the input text edit with Ctrl and Shift, then selection start at the
  // present index with anchor at this same index.

  m_lastHistoryIndex = m_historyAnchorIndex = mp_ui->historyListWidget->count();

  // qDebug() << __FILE__ << __LINE__
  //<< "logging history, after insertion of new item"
  //<< ", item count:" << mp_ui->historyListWidget->count()
  //<< "; with last index set to that count:" << m_lastHistoryIndex;

  // Make sure the last item is visible
  mp_ui->historyListWidget->setCurrentItem(item);
}

QString &
JavaScriptingWnd::prependColorTag(QString &text, LogTextColor color)
{
  // We get a string and a color rendering. Create a tag in the form
  // <color></color> withthe color value inside and prepend that tag to the
  // text.

  text.prepend(QString("<color>%1</color>").arg(color));
  return text;
}

bool
JavaScriptingWnd::removeColorTag(QString &text, LogTextColor &color)
{
  // We get a string that is prepended with the "<color>%1</color>" color
  // tag. We need to extrat the value between the opening and closing tags
  // an set it. In the same operation we remove that color tag from the
  // string. We return false if the color tag was not there or if the value
  // was not convertible to int.

  static QRegularExpression colorTagRegexp("<color>(\\d+)</color>");

  QRegularExpressionMatch match = colorTagRegexp.match(text);

  if(match.hasMatch())
    {
      bool ok = false;

      // Get the color value
      int tagValue = match.captured(1).toInt(&ok);

      if(!ok)
        {
          // qDebug() << __FILE__ << __LINE__ << "Failed to convert"
          //<< match.captured(1) << "to int.";

          return false;
        }

      color = static_cast<LogTextColor>(tagValue);

      // Now remove from the initial string all the matching substring.
      text.remove(colorTagRegexp);

      return true;
    }

  return false;
}

void
JavaScriptingWnd::runScript(const QString &code_text, const QString &comment)
{
  // qDebug() << "Running script";

  // We want to run the script in its own thread. For this, the QJSEngine
  // that will evaluate the script NEEDS to have been allocated IN THAT THREAD.

  // We thus first allocate a JavaScriptWorker object, that will itself
  // allocate a QJSEngine object. From that JavaScriptWorker object, we
  // will need to initialize the the QJSEngine object in that other thread
  // and the only one object that knows how to do this is ProgramWindow.
  // This is why the function
  // ProgramWindow::initializeJsEngine(QJSEngine *engine_p).

  m_scriptText    = code_text;
  m_scriptComment = comment;

  auto *java_script_worker_p =
    new JavaScriptWorker(code_text, mp_scriptingEnvironment);

  connect(java_script_worker_p,
          &JavaScriptWorker::finishedSignal,
          java_script_worker_p,
          &QObject::deleteLater);

  connect(java_script_worker_p,
          &JavaScriptWorker::finishedSignal,
          this,
          &JavaScriptingWnd::scriptHasFinishedRunning);

  // When the user clicks that push button, the value of __abortRequested
  // in the scriptiong environment is set to true. When checkAbort() is
  // called in the running script (provided the user thas put that call
  // in relevant parts of their script), the true value that is returned
  // should trigger relevant code to stop execution softly.

  connect(mp_ui->scriptAbortPushButton,
          &QPushButton::clicked,
          java_script_worker_p,
          &JavaScriptWorker::requestAbort);

  java_script_worker_p->run();
}

void
JavaScriptingWnd::scriptHasFinishedRunning(QJSValue value)
{
  // qDebug() << "The script has finished running.";

  // If there is an explanatory comment to this script command, then start
  // by logging it, so that it precedes the result output in the
  // logOutTextEdit widget.

  if(!m_scriptComment.isEmpty())
    logOutTextEdit(QString("%1%2").arg(m_commentPrefix, m_scriptComment),
                   LogTextColor::Comment);

  QStringList script_as_lines = m_scriptText.split("\n");

  bool scriptFailed = value.isError();

  if(scriptFailed)
    {
      // red color.
      logOutTextEdit(value.toString() + "\n", LogTextColor::Failure);

      if(m_historyLoggingType & HistoryLoggingType::OnFailure)
        {
          foreach(QString line, script_as_lines)
            {
              logHistory(line, LogTextColor::Failure);
            }
        }
    }
  else if(value.isUndefined())
    {
      // black color;
      logOutTextEdit(value.toString() + "\n", LogTextColor::Undefined);

      if(m_historyLoggingType & HistoryLoggingType::OnSuccess)
        {
          foreach(QString line, script_as_lines)
            {
              logHistory(line, LogTextColor::Undefined);
            }
        }
    }
  else
    {
      // green color.
      logOutTextEdit(value.toString() + "\n", LogTextColor::Success);

      if(m_historyLoggingType & HistoryLoggingType::OnSuccess)
        {
          foreach(QString line, script_as_lines)
            {
              logHistory(line, LogTextColor::Success);
            }
        }
    }

  // qDebug() << "The result is:" << value.toString();

  if(value.isError())
    {
      QString error_details = QString("Name: %1\nMessage: %2\nStack: %3\n")
                                .arg(value.property("name").toString(),
                                     value.property("message").toString(),
                                     value.property("stack").toString());

      // qDebug() << "Error details:" << error_details;
    }

  // When a command has been run, whatever its result, we remove it from the
  // text edit widget. Anyway, it has been already taken into account in the
  // history by the logHistory() call above.

  mp_codeEditor->clear();
}


} // namespace libXpertMassGui
} // namespace MsXpS
