/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 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 include
#include <QCloseEvent>
#include <QSettings>
#include <QScriptEngine>
#include <QListWidget>
#include <QDebug>
#include <QFileDialog>
#include <QFile>
#include <QScrollBar>


///////////////////////////// Local includes
#include <globals/globals.hpp>
#include <minexpert/gui/ScriptingWnd.hpp>
#include <minexpert/gui/ScriptingHistoryListWidget.hpp>
#include <minexpert/gui/ScriptingObjectTreeWidgetItem.hpp>
#include <libmass/DataPointJs.hpp>
#include <libmass/TraceJs.hpp>
#include <libmass/MassSpectrumJs.hpp>
#include <minexpert/nongui/SavGolFilterJs.hpp>
#include <minexpert/nongui/MzIntegrationParamsJs.hpp>


namespace msXpSmineXpert
{


ScriptingWnd::ScriptingWnd(QWidget *parent, const QString &applicationName)
  : QMainWindow(parent), m_applicationName{applicationName}

{
  if(parent == nullptr)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  m_ui.setupUi(this);

  connect(m_ui.historyListWidget,
          &ScriptingHistoryListWidget::setSelectedItemsTextToScriptInput,
          this,
          &ScriptingWnd::historyListWidgetSendTextToInTextEdit);

  connect(m_ui.inTextEdit,
          &QTextEdit::textChanged,
          this,
          &ScriptingWnd::inTextEditTextChanged);

  // 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(m_ui.resetHistoryPushButton,
          &QPushButton::released,
          this,
          &ScriptingWnd::resetHistory);

  connect(m_ui.saveHistoryPushButton,
          &QPushButton::released,
          this,
          &ScriptingWnd::saveHistory);

  connect(m_ui.historyListWidget,
          &QListWidget::itemActivated,
          this,
          &ScriptingWnd::historyListWidgetItemActivated);

  connect(m_ui.historyListWidget,
          &ScriptingHistoryListWidget::currentRowChanged,
          this,
          &ScriptingWnd::historyListWidgetCurrentRowChanged);

  connect(m_ui.historyRegExpLineEdit,
          &QLineEdit::returnPressed,
          this,
          &ScriptingWnd::historyRegExpLineEditReturnPressed);

  connect(m_ui.jsRefRegExpLineEdit,
          &QLineEdit::returnPressed,
          this,
          &ScriptingWnd::jsRefTextRegExpLineEditReturnPressed);

  // There should be a column for the name and one for the alias.

  m_ui.objectTreeWidget->setColumnCount(2);

  QHeaderView *headerView =
    new QHeaderView(Qt::Horizontal, m_ui.objectTreeWidget);
  m_ui.objectTreeWidget->setHeader(headerView);
  QStringList headerLabels = {"var name", "alias"};
  m_ui.objectTreeWidget->setHeaderLabels(headerLabels);

  connect(m_ui.objectTreeWidget,
          &QTreeWidget::itemDoubleClicked,
          this,
          &ScriptingWnd::objectTreeWidgetItemDoubleClicked);

  connect(m_ui.logOnSuccessCheckBox,
          &QCheckBox::stateChanged,
          this,
          &ScriptingWnd::historyLoggingCheckBoxStateChanged);

  connect(m_ui.logOnFailureCheckBox,
          &QCheckBox::stateChanged,
          this,
          &ScriptingWnd::historyLoggingCheckBoxStateChanged);

  // Install the event filter for the textedit related to script input:
  m_ui.inTextEdit->installEventFilter(this);

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

  // Create the menu and menu items.

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

  mp_runScriptFileAct = new QAction(tr("&Run a JavaScript from file"),
                                    dynamic_cast<QObject *>(this));
  mp_runScriptFileAct->setShortcut(QKeySequence("Ctrl+O, F"));
  mp_runScriptFileAct->setStatusTip(tr("Run a JavaScript"));

  connect(
    mp_runScriptFileAct, SIGNAL(triggered()), this, SLOT(runScriptFile()));

  mp_fileMenu->addAction(mp_runScriptFileAct);

  // Make sure we can convert QList<double> to script value:
  qScriptRegisterSequenceMetaType<QList<double>>(&m_scriptEngine);

  /// We'll need these strings many times.
  QString result;
  QString help;

  // We want to provide the user with a print() function that is not available
  // in ECMAScript.

  QScriptValue function = m_scriptEngine.newFunction(print);
  function.setData(m_scriptEngine.newQObject(this));
  m_scriptEngine.globalObject().setProperty("print", function);

  help = QString(
    "JavaScript has no print() function, we provide one.\n"
    "Run it like this: print(<object>)");

  publishEntityWithScriptValue(
    function,
    this,
    "print()",
    "",
    "Make available the function print() to print object data",
    help,
    ScriptEntityType::FunctionEntity,
    &result);


  function = m_scriptEngine.newFunction(printToFile);
  function.setData(m_scriptEngine.newQObject(this));
  m_scriptEngine.globalObject().setProperty("printToFile", function);

  help = QString(
    "JavaScript has no printToFile() function, we provide one.\n"
    "Run it like this: printToFile(<object>, <fileName>).\n"
    "For example: printToFile(t1, \"/tmp/trial.txt\").");

  publishEntityWithScriptValue(
    function,
    this,
    "printToFile()",
    "",
    "Make available the function printToFile() to print object data to a file",
    help,
    ScriptEntityType::FunctionEntity,
    &result);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"Now making available all the scriptable classes to the scripting
  // environment";

  // Make sure we make the DataPoint class available to the script environment.
  msXpSlibmass::DataPointJs *dataPointJs =
    new msXpSlibmass::DataPointJs(&m_scriptEngine);
  m_scriptEngine.globalObject().setProperty("DataPoint",
                                            dataPointJs->constructor());

  // Make sure we make the Trace class available to the script environment.
  msXpSlibmass::TraceJs *traceJs = new msXpSlibmass::TraceJs(&m_scriptEngine);
  m_scriptEngine.globalObject().setProperty("Trace", traceJs->constructor());

  // Make sure we make the MassSpectrum class available to the script
  // environment.
  msXpSlibmass::MassSpectrumJs *massSpectrumJs =
    new msXpSlibmass::MassSpectrumJs(&m_scriptEngine);
  m_scriptEngine.globalObject().setProperty("MassSpectrum",
                                            massSpectrumJs->constructor());

  // Make sure we make the SavGolFilter class available to the script
  // environment.
  SavGolFilterJs *savGolFilterJs = new SavGolFilterJs(&m_scriptEngine);
  m_scriptEngine.globalObject().setProperty("SavGolFilter",
                                            savGolFilterJs->constructor());

  // Make sure we make the SavGolParams struct available to the script
  // environment.
  // This is a structure that is located in the SavGolFilter class definition
  // file.
  QScriptValue ctor =
    m_scriptEngine.newFunction(SavGolFilterJs::SavGolParamsCtor);
  m_scriptEngine.globalObject().setProperty("SavGolParams", ctor);

  // Make sure we make the MzIntegrationParams class available to the script
  // environment.
  MzIntegrationParamsJs *mzIntegrationParamsJs =
    new MzIntegrationParamsJs(&m_scriptEngine);
  m_scriptEngine.globalObject().setProperty(
    "MzIntegrationParams", mzIntegrationParamsJs->constructor());

  // Finally make sure we display the JavaScript reference text in its tab.
  setJsReferenceText();

  readSettings();
}


ScriptingWnd::~ScriptingWnd()
{
  writeSettings();
}


void
ScriptingWnd::closeEvent(QCloseEvent *event)
{
  writeSettings();
  event->accept();
}


void
ScriptingWnd::writeSettings()
{
  QSettings settings;
  settings.beginGroup("ScriptingWnd");

  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", m_ui.treeSplitter->saveState());
  settings.setValue("scriptingSplitter", m_ui.scriptingSplitter->saveState());
  settings.setValue("generalConfigSplitter",
                    m_ui.generalConfigSplitter->saveState());
  settings.setValue("scriptingConfigSplitter",
                    m_ui.scriptingConfigSplitter->saveState());

  // objectTreeWidget
  settings.setValue("objectTreeWidgetHeaderView",
                    m_ui.objectTreeWidget->header()->saveState());

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

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

  settings.endGroup();

  settings.sync();
}


void
ScriptingWnd::readSettings()
{
  QSettings settings;

  settings.beginGroup("ScriptingWnd");

  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
  m_ui.treeSplitter->restoreState(settings.value("treeSplitter").toByteArray());
  m_ui.scriptingSplitter->restoreState(
    settings.value("scriptingSplitter").toByteArray());
  m_ui.generalConfigSplitter->restoreState(
    settings.value("generalConfigSplitter").toByteArray());
  m_ui.scriptingConfigSplitter->restoreState(
    settings.value("scriptingConfigSplitter").toByteArray());

  // objectTreeWidget
  m_ui.objectTreeWidget->header()->restoreState(
    settings.value("objectTreeWidgetHeaderView").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 = m_ui.historyListWidget->count();

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

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

  settings.endGroup();
}


void
ScriptingWnd::setJsReferenceText()
{

  // We load the history file now. It is part of the source code in the form
  // of libmass/history.html.

  QFile jsReferenceTextFile(":/doc/user-manuals/minexpert/xml/devdoc-javascript-reference.txt");
  if(!jsReferenceTextFile.open(QFile::ReadOnly | QFile::Text))
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Attention JavaScript documentation file not found.";
    }

  m_jsReferenceText = jsReferenceTextFile.readAll();

  m_ui.jsRefPlainTextEdit->setPlainText(m_jsReferenceText);
}


void
ScriptingWnd::historyListWidgetCurrentRowChanged(int row)
{

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

  m_lastHistoryIndex = row;
}


void
ScriptingWnd::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 text = m_ui.historyListWidget->selectedItemsText();

  historyListWidgetSendTextToInTextEdit(text, true);
}


void
ScriptingWnd::inTextEditTextChanged()
{
  // Get the opportunity to check the syntax of the current script input
  // text.

  // Fixme
}


void
ScriptingWnd::historyListWidgetSendTextToInTextEdit(
  const QString &scriptContents, bool overwrite)
{
  // We want to put in the inTextEdit the scriptContents text, but we may
  // make the distinction between making a whole text replace or a simple
  // insertion at point.

  inputInTextEdit(scriptContents, overwrite);

  // And now set focus to the inTextEdit.
  m_ui.inTextEdit->setFocus();

  // And now switch tab to the scripting tab.
  m_ui.tabWidget->setCurrentIndex(0);
}


void
ScriptingWnd::objectTreeWidgetItemDoubleClicked(QTreeWidgetItem *item,
                                                int column)
{
  Qt::KeyboardModifiers modifiers = QGuiApplication::queryKeyboardModifiers();

  if(modifiers & Qt::ControlModifier)
    {
      // The text item is the name of a QScript object of which the user wants
      // to list all the properties.

      QString scriptContents = QString(
                                 "for (var property in %1) {"
                                 "if (%1.hasOwnProperty(property)) {"
                                 "print(\"property:\" + %1[property]) }}")
                                 .arg(item->text(column));

      runScript(
        scriptContents,
        QString("Asking properties of object %1.\n").arg(item->text(column)));
    }
  else
    {
      // We want on insert the item's text in the inTextEdit, at the current
      // cursor position. Not replacing of unselected text.

      QTextCursor cursor = m_ui.inTextEdit->textCursor();
      cursor.insertText(item->text(column));

      // And now switch tab to the scripting tab.
      m_ui.tabWidget->setCurrentIndex(0);

      // And now set focus to the inTextEdit.
      m_ui.inTextEdit->setFocus();

      // And now go to the end of the paragraph.
      cursor = m_ui.inTextEdit->textCursor();
      cursor.movePosition(QTextCursor::End);
      m_ui.inTextEdit->setTextCursor(cursor);
    }
}

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

  HistoryLoggingType loggingType = HistoryLoggingType::Never;

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

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

  m_historyLoggingType = loggingType;
}


void
ScriptingWnd::historyRegExpLineEditReturnPressed()
{
  bool caseSensitive = m_ui.historyCaseSensitiveCheckBox->isChecked();

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

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

      QRegularExpressionMatch match = regExp.match(itemText);

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


void
ScriptingWnd::jsRefTextRegExpLineEditReturnPressed()
{

  int contextLines = m_ui.contextLinesSpinBox->value();

  QString regExpression = m_ui.jsRefRegExpLineEdit->text();

  if(regExpression.isEmpty())
    {
      m_ui.jsRefPlainTextEdit->setPlainText(m_jsReferenceText);
      return;
    }

  m_ui.jsRefPlainTextEdit->clear();

  bool caseSensitive = m_ui.jsRefCaseSensitiveCheckBox->isChecked();

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

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

  QString newText;

  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 = 0;

          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;
                    }

                  newText.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.

          newText.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;
                    }

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

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

          // Add match delimiting line.
          newText.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())
    }
  // End of
  // for(int iter = 0; iter < lines.size(); ++iter)

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

  m_ui.jsRefPlainTextEdit->setPlainText(newText);
}


bool
ScriptingWnd::eventFilter(QObject *obj, QEvent *event)
{
  if(obj == m_ui.inTextEdit)
    {
      if(event->type() == QEvent::KeyPress)
        {
          QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);

          return handleKeyPressEvent(keyEvent);
        }
      else
        {
          // pass the event on to the parent class
          return QMainWindow::eventFilter(obj, event);
        }
    }

  // pass the event on to the parent class
  return QMainWindow::eventFilter(obj, event);
}


// 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.
bool
ScriptingWnd::handleKeyPressEvent(QKeyEvent *event)
{

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

  if(event == Q_NULLPTR)
    return false;

  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 send the
          // command.

          runScript(m_ui.inTextEdit->toPlainText());

          return true;
        }

      return false;
    }

  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(m_ui.historyListWidget->count() == 0)
            return false;

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

              // qdebug() << __file__ << __line__
              //<< "key down with index:" << m_lastHistoryIndex
              //<< "while count is:" << m_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 >= m_ui.historyListWidget->count())
                    {
                      // qDebug() << __FILE__ << __LINE__
                      //<< "count is:" <<  m_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 =
                      // m_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 m_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 = m_ui.historyListWidget->count();

                      m_ui.historyListWidget->deselectAll();
                      m_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 = m_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;

                      m_ui.inTextEdit->clear();

                      // qDebug() << __FILE__ << __LINE__
                      //<< "Finally, cleared the inTextEdit";

                      return true;
                    }
                  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;

                      m_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 >= m_ui.historyListWidget->count())
                    {
                      // qDebug() << __FILE__ << __LINE__
                      //<< "count is:" <<  m_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 = m_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:" << m_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):
          m_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.

          m_ui.historyListWidget->overwriteSelectedItemsText();

          // We did handle the event.

          return true;
        }
      // 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 false;
}

QStringList
ScriptingWnd::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 = m_ui.historyListWidget->count();

  for(int iter = 0; iter < count; ++iter)
    {
      QListWidgetItem *item = m_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
ScriptingWnd::restoreHistory(QSettings &settings)
{
  QStringList historyList = settings.value("history").toStringList();

  // 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 < historyList.size(); ++iter)
    {
      QString historyText = historyList.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);
        }
    }

  // Make sure the history list widget shows the last item.
  m_ui.historyListWidget->setCurrentRow(m_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 = m_ui.historyListWidget->count();

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

  return m_lastHistoryIndex;
}


int
ScriptingWnd::restoreHistoryLoggingType(QSettings &settings)
{

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

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

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

  return historyLoggingType;
}


void
ScriptingWnd::resetHistory()
{
  m_ui.historyListWidget->clear();

  m_lastHistoryIndex = m_historyAnchorIndex = -1;
}


void
ScriptingWnd::saveHistory()
{
  QSettings settings;
  settings.beginGroup("ScriptingWnd");

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

  settings.endGroup();

  settings.sync();
}


void
ScriptingWnd::showHistory()
{

  QList<QWidget *> widgetList =
    m_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__);

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

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

  return historyList.join(lineSeparator);
}


bool
ScriptingWnd::publishQObject(QObject *object,
                             QObject *parent,
                             const QString &jsName,
                             const QString &alias,
                             const QString &comment,
                             const QString &help,
                             QScriptValue &jsValue,
                             QScriptEngine::ValueOwnership ownership,
                             const QScriptEngine::QObjectWrapOptions &options)
{
  if(object == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QScriptValue localJsValue =
    m_scriptEngine.newQObject(object, ownership, options);
  m_scriptEngine.globalObject().setProperty(jsName, localJsValue);

  if(jsValue.isValid())
    jsValue = localJsValue;

  // Now that we have published this object, show it in the tree view by its
  // name (and alias, if any). But, create a hierarchy, if possible: if
  // parent is non-nullptr, then make the new item a child of parent->item.
  //
  // Note that, if an alias was provided, we may have to remove it from the
  // object tree at the relevant place.

  ScriptingObjectTreeWidgetItem *newTreeWidgetItem = Q_NULLPTR;
  ScriptingObjectTreeWidgetItem *parentWidgetItem  = Q_NULLPTR;

  if(parent != Q_NULLPTR)
    {

      // We are provided a QObject * as parent of the new one, so search in
      // the object tree widget if there is an item that has that parent
      // object pointer. If so, make the new object's widget item a child of
      // the parent's one.

      parentWidgetItem = getObjectTreeWidgetItemByQObject(parent);
    }

  // We do not want to create the new item, with alias, if there is an alias
  // in this function call and there might be one that we must first erase.

  if(!alias.isEmpty())
    {

      // Create the alias object.

      m_scriptEngine.globalObject().setProperty(alias, localJsValue);

      // There is an alias. Often, aliases are overwritten during creation of
      // new objects with the same alias ("lastTicChromPlot" as an alias of
      // the last ticChromPlot<index>, for example). We need to erase that
      // alias text in the corresponding column from the object tree widget.

      QList<ScriptingObjectTreeWidgetItem *> list =
        getObjectTreeWidgetItemsByColumnContent(
          alias, parentWidgetItem, ObjectTreeWidgetColumn::Alias);

      for(int iter = 0; iter < list.size(); ++iter)
        {
          ScriptingObjectTreeWidgetItem *widgetItem = list.at(iter);
          widgetItem->setText(ObjectTreeWidgetColumn::Alias, QString(""));
        }
    }

  // At this point, we have removed any alias text from any widget item that
  // corresponds to the current situation (whole tree or only children of
  // parent widget item).
  //
  // We can go on with the creation of the new widget item.

  if(parentWidgetItem != Q_NULLPTR)
    {
      newTreeWidgetItem = new ScriptingObjectTreeWidgetItem(
        parentWidgetItem, QTreeWidgetItem::ItemType::Type);
    }
  else
    {
      newTreeWidgetItem = new ScriptingObjectTreeWidgetItem(
        m_ui.objectTreeWidget, QTreeWidgetItem::ItemType::Type);
    }

  newTreeWidgetItem->setText(ObjectTreeWidgetColumn::Name, jsName);
  newTreeWidgetItem->setText(ObjectTreeWidgetColumn::Alias, alias);

  newTreeWidgetItem->setProperties(
    object, comment, help, ScriptEntityType::QObjectEntity);

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

  // Now that the tree widget item is configured, let's make sure that it is
  // removed from the list when the QObject is destroyed.

  connect(object,
          &QObject::destroyed,
          this,
          &ScriptingWnd::publishedQObjectDestroyed);

  return true;
}


bool
ScriptingWnd::publishEntityWithScript(QString script,
                                      QObject *parent,
                                      const QString &jsName,
                                      const QString &alias,
                                      const QString &comment,
                                      const QString &help,
                                      ScriptEntityType entityType,
                                      QString *result)
{
  if(result == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Pointer cannot be nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // First of all run the script and go on only if the returned value is ok.
  bool ok = runScript(script, comment, result);

  if(!ok)
    return false;

  // Now use the js name of the object to insert a new element in the
  // script environment tree widget.

  // But, create a hierarchy, if possible: if parent is non-nullptr, then
  // make the new item a child of parent->item.
  //
  // Note that, if an alias was provided, we will add it also.

  ScriptingObjectTreeWidgetItem *newTreeWidgetItem = Q_NULLPTR;
  ScriptingObjectTreeWidgetItem *parentWidgetItem  = Q_NULLPTR;

  if(parent != Q_NULLPTR)
    {

      // We are provided a QObject * as parent of the new one, so search in
      // the object tree widget if there is an item that has that parent
      // object pointer. If so, make the new object's widget item a child of
      // the parent's one.

      parentWidgetItem = getObjectTreeWidgetItemByQObject(parent);
    }

  // We do not want to create the new item, with alias, if there is an alias
  // in this function call and there might be one that we must first erase.

  if(!alias.isEmpty())
    {

      // Create the alias object.

      m_scriptEngine.globalObject().setProperty(alias, jsName);
    }

  // We can go on with the creation of the new widget item.

  if(parentWidgetItem != Q_NULLPTR)
    {
      newTreeWidgetItem = new ScriptingObjectTreeWidgetItem(
        parentWidgetItem, QTreeWidgetItem::ItemType::Type);
    }
  else
    {
      newTreeWidgetItem = new ScriptingObjectTreeWidgetItem(
        m_ui.objectTreeWidget, QTreeWidgetItem::ItemType::Type);
    }

  newTreeWidgetItem->setText(ObjectTreeWidgetColumn::Name, jsName);
  newTreeWidgetItem->setText(ObjectTreeWidgetColumn::Alias, alias);

  newTreeWidgetItem->setProperties(Q_NULLPTR, comment, help, entityType);

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

  return true;
}


bool
ScriptingWnd::publishEntityWithScriptValue(QScriptValue scriptValue,
                                           QObject *parent,
                                           const QString &jsName,
                                           const QString &alias,
                                           const QString &comment,
                                           const QString &help,
                                           ScriptEntityType entityType,
                                           QString *result)
{
  if(result == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Pointer cannot be nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Now use the js name of the object to insert a new element in the
  // script environment tree widget.

  // But, create a hierarchy, if possible: if parent is non-nullptr, then
  // make the new item a child of parent->item.
  //
  // Note that, if an alias was provided, we will add it also.

  ScriptingObjectTreeWidgetItem *newTreeWidgetItem = Q_NULLPTR;
  ScriptingObjectTreeWidgetItem *parentWidgetItem  = Q_NULLPTR;

  if(parent != Q_NULLPTR)
    {

      // We are provided a QObject * as parent of the new one, so search in
      // the object tree widget if there is an item that has that parent
      // object pointer. If so, make the new object's widget item a child of
      // the parent's one.

      parentWidgetItem = getObjectTreeWidgetItemByQObject(parent);
    }

  // We do not want to create the new item, with alias, if there is an alias
  // in this function call and there might be one that we must first erase.

  if(!alias.isEmpty())
    {

      // Create the alias object.

      m_scriptEngine.globalObject().setProperty(alias, jsName);
    }

  // We can go on with the creation of the new widget item.

  if(parentWidgetItem != Q_NULLPTR)
    {
      newTreeWidgetItem = new ScriptingObjectTreeWidgetItem(
        parentWidgetItem, QTreeWidgetItem::ItemType::Type);
    }
  else
    {
      newTreeWidgetItem = new ScriptingObjectTreeWidgetItem(
        m_ui.objectTreeWidget, QTreeWidgetItem::ItemType::Type);
    }

  newTreeWidgetItem->setText(ObjectTreeWidgetColumn::Name, jsName);
  newTreeWidgetItem->setText(ObjectTreeWidgetColumn::Alias, alias);

  newTreeWidgetItem->setProperties(Q_NULLPTR, comment, help, entityType);

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

  return true;
}


ScriptingObjectTreeWidgetItem *
ScriptingWnd::getObjectTreeWidgetItemByQObject(QObject *parent)
{
  QTreeWidgetItemIterator iterator(m_ui.objectTreeWidget);
  while(*iterator)
    {
      if(static_cast<ScriptingObjectTreeWidgetItem *>(*iterator)->mp_qobject ==
         parent)
        {
          return static_cast<ScriptingObjectTreeWidgetItem *>(*iterator);
        }
      ++iterator;
    }

  return Q_NULLPTR;
}


QList<ScriptingObjectTreeWidgetItem *>
ScriptingWnd::getObjectTreeWidgetItemsByColumnContent(
  const QString &text,
  ScriptingObjectTreeWidgetItem *parent,
  ObjectTreeWidgetColumn column)
{
  // We are asked to return the tree widget items that have text as the
  // contents of the column. If parent is not nullptr, then search only for
  // items that are children of that parent.

  QList<ScriptingObjectTreeWidgetItem *> list;

  if(parent == Q_NULLPTR)
    {

      // There is no parent widget item, so iterate over all the tree widget.

      QTreeWidgetItemIterator iterator(m_ui.objectTreeWidget);
      while(*iterator)
        {
          if(static_cast<ScriptingObjectTreeWidgetItem *>(*iterator)->text(
               column) == text)
            list.append(
              static_cast<ScriptingObjectTreeWidgetItem *>(*iterator));

          ++iterator;
        }
    }
  else
    {
      // There is a widget item parent, so iterated only in that widget item
      // children

      QTreeWidgetItemIterator iterator(parent);
      while(*iterator)
        {
          if(static_cast<ScriptingObjectTreeWidgetItem *>(*iterator)->text(
               column) == text)
            list.append(
              static_cast<ScriptingObjectTreeWidgetItem *>(*iterator));

          ++iterator;
        }
    }

  return list;
}


void
ScriptingWnd::deleteObjectTreeWidgetItemsByColumnContent(
  const QString &text,
  ScriptingObjectTreeWidgetItem *parent,
  ObjectTreeWidgetColumn column)
{
  // We are asked to delete all the tree widget items that have text as the
  // contents of the column. If parent is not nullptr, then search only for
  // items that are children of that parent.

  if(parent == Q_NULLPTR)
    {

      // There is no parent widget item, so iterate over all the tree widget.

      QTreeWidgetItemIterator iterator(m_ui.objectTreeWidget);
      while(*iterator)
        {
          if(static_cast<ScriptingObjectTreeWidgetItem *>(*iterator)->text(
               column) == text)
            delete *iterator;

          ++iterator;
        }
    }
  else
    {
      // There is a widget item parent, so iterated only in that widget item
      // children

      QTreeWidgetItemIterator iterator(parent);
      while(*iterator)
        {
          if(static_cast<ScriptingObjectTreeWidgetItem *>(*iterator)->text(
               column) == text)
            delete *iterator;

          ++iterator;
        }
    }
}


void
ScriptingWnd::publishedQObjectDestroyed(QObject *object)
{
  if(object == Q_NULLPTR)
    return;

  // We are about to destroy a QObject that was published in the
  // scripting environment and that is listed in the tree view. We need
  // to remove it from there.

  QTreeWidgetItemIterator iterator(m_ui.objectTreeWidget);
  while(*iterator)
    {
      if(static_cast<ScriptingObjectTreeWidgetItem *>(*iterator)->mp_qobject ==
         object)
        {
          delete(*iterator);
          return;
        }
      ++iterator;
    }
}


void
ScriptingWnd::loadScriptFile(const QString &fileName)
{
  return runScriptFile(fileName);
}


void
ScriptingWnd::runScriptFile(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());

      QStringList fileNames;

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

  QFile file(scriptFileName);

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

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

  file.close();

  if(scriptContents.isEmpty())
    return;

  QString explanation = QString("Running script from file %1\n").arg(fileName);

  logOutTextEdit(QString("%1%2").arg(m_commentPrefix).arg(explanation),
                 LogTextColor::Comment);


  qDebug() << __FILE__ << __LINE__
           << "Should run unitary script:" << scriptContents;

  QScriptValue value = m_scriptEngine.evaluate(scriptContents);

  bool scriptFailed = value.isError();

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

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

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

      if(m_historyLoggingType & HistoryLoggingType::OnSuccess)
        // Make sure we put back the ';' because we want it in the history
        // items.
        logHistory(scriptContents, LogTextColor::Success);
    }
}


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

  // qDebug() << __FILE__ << __LINE__
  //<< "returning color" << color << "for contextColor:" << contextColor;

  return color;
}


void
ScriptingWnd::inputInTextEdit(const QString &text, bool overwrite)
{
  if(overwrite)
    {
      m_ui.inTextEdit->setPlainText(text);

      // And now for the cursor at the end of the document
      QTextCursor cursor = m_ui.inTextEdit->textCursor();
      cursor.movePosition(QTextCursor::End);
      m_ui.inTextEdit->setTextCursor(cursor);
    }
  else
    {
      QTextCursor cursor     = m_ui.inTextEdit->textCursor();
      QTextCharFormat format = cursor.charFormat();
      cursor.insertText(text, format);
      m_ui.inTextEdit->setTextCursor(cursor);
    }
}


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

  QTextCharFormat format;
  QColor reqColor = logColor(textColor);

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

  m_ui.outTextEdit->verticalScrollBar()->setValue(
    m_ui.outTextEdit->verticalScrollBar()->maximum());
}


void
ScriptingWnd::logHistory(const QString &text, LogTextColor textColor)
{
  // qDebug() << __FILE__ << __LINE__
  //<< "logging history" << text
  //<<", before insertion of new item"
  //<< ", item count is:" << m_ui.historyListWidget->count()
  //<< "; with last index:" << m_lastHistoryIndex;

  // Make a copy because we'll modify it.
  QString localText = text;

  // Allocate a new item that is automatically set to the list widget.
  QListWidgetItem *item = new QListWidgetItem(m_ui.historyListWidget);

  // Make sure the item is rendered according to the textColor.
  QBrush brush = item->foreground();
  QColor color = logColor(textColor);
  brush.setColor(color);
  item->setForeground(brush);

  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.
  m_ui.historyListWidget->deselectAll();

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

  m_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 = m_ui.historyListWidget->count();

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

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


QString &
ScriptingWnd::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
ScriptingWnd::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.

  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;
}


bool
ScriptingWnd::runScript(const QString &script,
                        const QString &comment,
                        QString *result)
{

  // 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(!comment.isEmpty())
    logOutTextEdit(QString("%1%2").arg(m_commentPrefix).arg(comment),
                   LogTextColor::Comment);

  QScriptValue value = m_scriptEngine.evaluate(script);

  bool scriptFailed = value.isError();

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

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

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

      if(m_historyLoggingType & HistoryLoggingType::OnSuccess)
        // Make sure we put back the ';' because we want it in the history
        // items.
        logHistory(script, LogTextColor::Success);
    }

  // 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.

  m_ui.inTextEdit->clear();

  if(result != Q_NULLPTR)
    result->append(value.toString());

  return !scriptFailed;
}


// This function serves as a js print replacement such that the output of the
// print function goes to the outTextEdit and not to the system shell console.
QScriptValue
print(QScriptContext *context, QScriptEngine *engine)
{
  // Get to know the arguments to the jsPrint command, for example:

  // for (var property in ticChromPlot0)
  //{
  // if (ticChromPlot0.hasOwnProperty(property))
  //{
  // jsPrint("property:" + ticChromPlot0[property])
  //}
  //}

  QString result;

  for(int i = 0; i < context->argumentCount(); ++i)
    {
      if(i > 0)
        result.append(" ");

      result.append(context->argument(i).toString());
    }

  // Finally, append a newline to the result string.
  result.append("\n");

  QScriptValue calleeData = context->callee().data();

  ScriptingWnd *scriptingWnd =
    qobject_cast<ScriptingWnd *>(calleeData.toQObject());

  if(scriptingWnd == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  scriptingWnd->logOutTextEdit(result);

  return engine->undefinedValue();
}


// This function serves as a js print replacement such that the output of the
// print function goes to the provided file nameand not to the system shell
// console.
QScriptValue
printToFile(QScriptContext *context, QScriptEngine *engine)
{
  QString result;
  QString fileName;
  bool wasError = false;

  int argc = context->argumentCount();

  if(argc < 2)
    {
      result =
        QString("Error, expected two arguments to call to printToFile\n");
      wasError = true;
    }
  else /* if(argc >= 2) */
    {
      // The last argument is the file name.

      fileName = context->argument(argc - 1).toString();

      for(int i = 0; i < context->argumentCount() - 1; ++i)
        {

          // Do not add the newline until the last string item (not the file
          // name) is read.

          if(i > 0)
            result.append(" ");

          result.append(context->argument(i).toString());
        }
    }

  // Finally, append a newline to the result string.
  result.append("\n");

  // Get the calling context. We may need it later.

  QScriptValue calleeData = context->callee().data();
  ScriptingWnd *scriptingWnd =
    qobject_cast<ScriptingWnd *>(calleeData.toQObject());

  if(wasError)
    {
      if(scriptingWnd == Q_NULLPTR)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

      scriptingWnd->logOutTextEdit(result);
      return engine->undefinedValue();
    }

  // Now we know we can try to write to the file.

  QFile file(fileName);
  if(!file.open(QIODevice::WriteOnly | QIODevice::Append))
    {
      result = QString("Failed to open file %1.\n").arg(fileName);
      scriptingWnd->logOutTextEdit(result);
      return engine->undefinedValue();
    }

  QTextStream stream(&file);
  stream.setCodec("UTF-8");
  stream << result;
  file.close();

  return engine->undefinedValue();
}


} // namespace msXpSmineXpert
