/******************************************************************************

  This source file is part of the Avogadro project.

  Copyright 2012-2014 Kitware, Inc.

  This source code is released under the New BSD License, (the "License").

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

******************************************************************************/

#include "mainwindow.h"

#include "aboutdialog.h"
#include "avogadroappconfig.h"
#include "backgroundfileformat.h"
#include "menubuilder.h"
#include "ui_mainwindow.h"
#include "viewfactory.h"

#include <avogadro/core/elements.h>
#include <avogadro/io/cjsonformat.h>
#include <avogadro/io/cmlformat.h>
#include <avogadro/io/fileformat.h>
#include <avogadro/io/fileformatmanager.h>
#include <avogadro/qtgui/customelementdialog.h>
#include <avogadro/qtgui/extensionplugin.h>
#include <avogadro/qtgui/fileformatdialog.h>
#include <avogadro/qtgui/layermodel.h>
#include <avogadro/qtgui/molecule.h>
#include <avogadro/qtgui/moleculemodel.h>
#include <avogadro/qtgui/multiviewwidget.h>
#include <avogadro/qtgui/periodictableview.h>
#include <avogadro/qtgui/rwmolecule.h>
#include <avogadro/qtgui/sceneplugin.h>
#include <avogadro/qtgui/scenepluginmodel.h>
#include <avogadro/qtgui/toolplugin.h>
#include <avogadro/qtopengl/activeobjects.h>
#include <avogadro/qtopengl/glwidget.h>
#include <avogadro/qtplugins/pluginmanager.h>
#include <avogadro/rendering/glrenderer.h>
#include <avogadro/rendering/scene.h>

#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QMimeData>
#include <QtCore/QSettings>
#include <QtCore/QStandardPaths>
#include <QtCore/QString>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <QtGui/QCloseEvent>
#include <QtGui/QKeySequence>
#include <QtWidgets/QActionGroup>
#include <QtWidgets/QApplication>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QToolBar>

#include <QtWidgets/QColorDialog>
#include <QtWidgets/QDockWidget>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QProgressDialog>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTreeView>

#include <QtGui/QOpenGLFramebufferObject>

#ifdef QTTESTING
#include <QXmlStreamReader>
#include <pqEventObserver.h>
#include <pqEventSource.h>
#include <pqTestUtility.h>
#endif

#ifdef Avogadro_ENABLE_RPC
#include <molequeue/client/client.h>
#endif // Avogadro_ENABLE_RPC

#ifdef AVO_USE_VTK
#include <avogadro/vtk/vtkglwidget.h>
#endif

#ifdef Q_OS_MAC
#include "qcocoamessagebox.h"
#define MESSAGEBOX QCocoaMessageBox
#else
#define MESSAGEBOX QMessageBox
#endif

namespace Avogadro {

#ifdef QTTESTING
class XMLEventObserver : public pqEventObserver
{
  QXmlStreamWriter* XMLStream;
  QString XMLString;

public:
  XMLEventObserver(QObject* p)
    : pqEventObserver(p)
  {
    this->XMLStream = nullptr;
  }
  ~XMLEventObserver() { delete this->XMLStream; }

protected:
  virtual void setStream(QTextStream* stream)
  {
    if (this->XMLStream) {
      this->XMLStream->writeEndElement();
      this->XMLStream->writeEndDocument();
      delete this->XMLStream;
      this->XMLStream = nullptr;
    }
    if (this->Stream)
      *this->Stream << this->XMLString;

    this->XMLString = QString();
    pqEventObserver::setStream(stream);
    if (this->Stream) {
      this->XMLStream = new QXmlStreamWriter(&this->XMLString);
      this->XMLStream->setAutoFormatting(true);
      this->XMLStream->writeStartDocument();
      this->XMLStream->writeStartElement("events");
    }
  }

  virtual void onRecordEvent(const QString& widget, const QString& command,
                             const QString& arguments)
  {
    if (this->XMLStream) {
      this->XMLStream->writeStartElement("event");
      this->XMLStream->writeAttribute("widget", widget);
      this->XMLStream->writeAttribute("command", command);
      this->XMLStream->writeAttribute("arguments", arguments);
      this->XMLStream->writeEndElement();
    }
  }
};

class XMLEventSource : public pqEventSource
{
  typedef pqEventSource Superclass;
  QXmlStreamReader* XMLStream;

public:
  XMLEventSource(QObject* p)
    : Superclass(p)
  {
    this->XMLStream = nullptr;
  }
  ~XMLEventSource() { delete this->XMLStream; }

protected:
  virtual void setContent(const QString& xmlfilename)
  {
    delete this->XMLStream;
    this->XMLStream = nullptr;

    QFile xml(xmlfilename);
    if (!xml.open(QIODevice::ReadOnly)) {
      qDebug() << "Failed to load " << xmlfilename;
      return;
    }
    QByteArray data = xml.readAll();
    this->XMLStream = new QXmlStreamReader(data);
  }

  int getNextEvent(QString& widget, QString& command, QString& arguments)
  {
    if (this->XMLStream->atEnd())
      return DONE;
    while (!this->XMLStream->atEnd()) {
      QXmlStreamReader::TokenType token = this->XMLStream->readNext();
      if (token == QXmlStreamReader::StartElement) {
        if (this->XMLStream->name() == "event")
          break;
      }
    }
    if (this->XMLStream->atEnd())
      return DONE;

    widget = this->XMLStream->attributes().value("widget").toString();
    command = this->XMLStream->attributes().value("command").toString();
    arguments = this->XMLStream->attributes().value("arguments").toString();
    return SUCCESS;
  }
};
#endif

using Io::FileFormat;
using Io::FileFormatManager;
using QtGui::CustomElementDialog;
using QtGui::ExtensionPlugin;
using QtGui::ExtensionPluginFactory;
using QtGui::FileFormatDialog;
using QtGui::Molecule;
using QtGui::MultiViewWidget;
using QtGui::RWMolecule;
using QtGui::ScenePlugin;
using QtGui::ScenePluginFactory;
using QtGui::ScenePluginModel;
using QtGui::ToolPlugin;
using QtGui::ToolPluginFactory;
using QtOpenGL::ActiveObjects;
using QtOpenGL::GLWidget;
using QtPlugins::PluginManager;
using std::string;
using std::vector;
#ifdef AVO_USE_VTK
using VTK::vtkGLWidget;
#endif

MainWindow::MainWindow(const QStringList& fileNames, bool disableSettings)
  : m_molecule(nullptr)
  , m_rwMolecule(nullptr)
  , m_moleculeModel(nullptr)
  , m_layerModel(nullptr)
  , m_activeScenePlugin(nullptr)
  , m_queuedFilesStarted(false)
  , m_menuBuilder(new MenuBuilder)
  , m_fileReadThread(nullptr)
  , m_fileWriteThread(nullptr)
  , m_threadedReader(nullptr)
  , m_threadedWriter(nullptr)
  , m_progressDialog(nullptr)
  , m_fileReadMolecule(nullptr)
  , m_fileToolBar(new QToolBar(this))
  , m_toolToolBar(new QToolBar(this))
  , m_moleculeDirty(false)
  , m_undo(nullptr)
  , m_redo(nullptr)
  , m_viewFactory(new ViewFactory)
  , m_ui(new Ui::MainWindow)
{
  m_ui->setupUi(this);

  // If disable settings, ensure we create a cleared QSettings object.
  if (disableSettings) {
    QSettings settings;
    settings.clear();
    settings.sync();
  }
  // The default settings will be used if everything was cleared.
  readSettings();

  // Now load the plugins.
  PluginManager* plugin = PluginManager::instance();
  plugin->load();

  // Call this a second time, not needed but ensures plugins only load once.
  plugin->load();

  QList<ExtensionPluginFactory*> extensions =
    plugin->pluginFactories<ExtensionPluginFactory>();
  qDebug() << "Extension plugins dynamically found..." << extensions.size();
  foreach (ExtensionPluginFactory* factory, extensions) {
    ExtensionPlugin* extension = factory->createInstance();
    if (extension) {
      extension->setParent(this);
      connect(this, &MainWindow::moleculeChanged, extension,
              &QtGui::ExtensionPlugin::setMolecule);
      connect(extension, &QtGui::ExtensionPlugin::moleculeReady, this,
              &MainWindow::moleculeReady);
      connect(extension, &QtGui::ExtensionPlugin::fileFormatsReady, this,
              &MainWindow::fileFormatsReady);
      connect(extension, &QtGui::ExtensionPlugin::requestActiveTool, this,
              &MainWindow::setActiveTool);
      connect(extension, &QtGui::ExtensionPlugin::requestActiveDisplayTypes,
              this, &MainWindow::setActiveDisplayTypes);
      buildMenu(extension);
      m_extensions.append(extension);
    }
  }

  // Now set up the interface.
  setupInterface();

  // Build up the standard menus, incorporate dynamic menus.
  buildMenu();
  updateRecentFiles();

  // Try to open the file(s) passed in.
  if (!fileNames.isEmpty()) {
    m_queuedFiles = fileNames;
    // Give the plugins 5 seconds before timing out queued files.
    QTimer::singleShot(5000, this, &MainWindow::clearQueuedFiles);
  } else {
    newMolecule();
  }

#ifdef Avogadro_ENABLE_RPC
  // Wait a few seconds to attempt registering with MoleQueue.
  QTimer::singleShot(3000, this, &MainWindow::registerMoleQueue);
#endif // Avogadro_ENABLE_RPC

  statusBar()->showMessage(tr("Ready..."), 2000);

  updateWindowTitle();
}

MainWindow::~MainWindow()
{
  writeSettings();
  delete m_molecule;
  delete m_menuBuilder;
  delete m_viewFactory;
  delete m_ui;
}

void MainWindow::setupInterface()
{
  // We take care of setting up the main interface here, along with any custom
  // pieces that might be added for saved settings etc.
  setAcceptDrops(true); // allow drag-and-drop of files
  QSettings settings;

  m_multiViewWidget = new QtGui::MultiViewWidget(this);
  m_multiViewWidget->setFactory(m_viewFactory);
  setCentralWidget(m_multiViewWidget);
  GLWidget* glWidget = new GLWidget(this);

  // set the background color (alpha channel default should be opaque)
  QColor color =
    settings.value("backgroundColor", QColor(0, 0, 0, 255)).value<QColor>();
  Vector4ub cColor;
  cColor[0] = static_cast<unsigned char>(color.red());
  cColor[1] = static_cast<unsigned char>(color.green());
  cColor[2] = static_cast<unsigned char>(color.blue());
  cColor[3] = static_cast<unsigned char>(color.alpha());

  Avogadro::Rendering::Scene* scene = &glWidget->renderer().scene();
  scene->setBackgroundColor(cColor);

  m_multiViewWidget->addWidget(glWidget);
  ActiveObjects::instance().setActiveGLWidget(glWidget);

  // Our tool dock.
  m_toolDock = new QDockWidget(tr("Tool"), this);
  addDockWidget(Qt::LeftDockWidgetArea, m_toolDock);

  // Our scene/view dock.
  m_sceneDock = new QDockWidget(tr("Display Types"), this);
  m_sceneDock->setFeatures(m_sceneDock->features() &
                           ~QDockWidget::AllDockWidgetFeatures);

  m_sceneTreeView = new QTreeView(m_sceneDock);
  m_sceneTreeView->setIndentation(0);
  m_sceneTreeView->setSelectionBehavior(QAbstractItemView::SelectRows);
  m_sceneTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
  m_sceneDock->setWidget(m_sceneTreeView);
  addDockWidget(Qt::LeftDockWidgetArea, m_sceneDock);
  // Our view dock.
  m_viewDock = new QDockWidget(tr("View Configuration"), this);
  addDockWidget(Qt::LeftDockWidgetArea, m_viewDock);
  m_viewDock->setFeatures(m_viewDock->features() &
                          ~QDockWidget::AllDockWidgetFeatures);

  // Our molecule dock.
  QDockWidget* moleculeDock = new QDockWidget(tr("Molecules"), this);
  m_moleculeTreeView = new QTreeView(moleculeDock);
  m_moleculeTreeView->setIndentation(0);
  m_moleculeTreeView->setSelectionBehavior(QAbstractItemView::SelectRows);
  m_moleculeTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
  moleculeDock->setWidget(m_moleculeTreeView);
  addDockWidget(Qt::LeftDockWidgetArea, moleculeDock);

  // Our molecule dock.
  QDockWidget* layerDock = new QDockWidget(tr("Layers"), this);
  m_layerTreeView = new QTreeView(layerDock);
  m_layerTreeView->setIndentation(0);
  m_layerTreeView->setSelectionBehavior(QAbstractItemView::SelectRows);
  m_layerTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
  layerDock->setWidget(m_layerTreeView);
  addDockWidget(Qt::LeftDockWidgetArea, layerDock);

  tabifyDockWidget(moleculeDock, layerDock);

  // Switch to our fallback icons if there are no platform-specific icons.
  if (!QIcon::hasThemeIcon("document-new"))
    QIcon::setThemeName("fallback");

  QIcon icon(":/icons/avogadro.png");
  setWindowIcon(icon);

#ifndef Q_OS_MAC
  m_fileToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
  m_fileToolBar->setWindowTitle(tr("File"));
  addToolBar(m_fileToolBar);
#endif
  m_toolToolBar->setWindowTitle(tr("Tools"));
  addToolBar(m_toolToolBar);

  // Create the scene plugin model
  m_sceneTreeView->setAlternatingRowColors(true);
  m_sceneTreeView->header()->stretchLastSection();
  m_sceneTreeView->header()->setVisible(false);
  connect(m_sceneTreeView, &QAbstractItemView::activated, this,
          &MainWindow::sceneItemActivated);
  connect(m_sceneTreeView, &QAbstractItemView::clicked, this,
          &MainWindow::sceneItemActivated);

  // Create the molecule model
  m_moleculeModel = new QtGui::MoleculeModel(this);
  m_moleculeTreeView->setModel(m_moleculeModel);
  m_moleculeTreeView->setSelectionBehavior(QAbstractItemView::SelectRows);
  m_moleculeTreeView->setAlternatingRowColors(true);
  m_moleculeTreeView->header()->setStretchLastSection(false);
  m_moleculeTreeView->header()->setVisible(false);
  m_moleculeTreeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
  m_moleculeTreeView->header()->setSectionResizeMode(1, QHeaderView::Fixed);
  m_moleculeTreeView->header()->resizeSection(1, 30);
  connect(m_moleculeTreeView, &QAbstractItemView::activated, this,
          &MainWindow::moleculeActivated);
  connect(m_moleculeTreeView, &QAbstractItemView::clicked, this,
          &MainWindow::moleculeActivated);

  m_layerModel = new QtGui::LayerModel(this);
  m_layerTreeView->setModel(m_layerModel);
  m_layerTreeView->setSelectionBehavior(QAbstractItemView::SelectRows);
  m_layerTreeView->setAlternatingRowColors(true);
  m_layerTreeView->header()->setStretchLastSection(false);
  m_layerTreeView->header()->setVisible(false);
  m_layerTreeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
  for (int i = 1; i < m_layerModel->columnCount(QModelIndex()); ++i) {
    m_layerTreeView->header()->setSectionResizeMode(i, QHeaderView::Fixed);
    m_layerTreeView->header()->resizeSection(i, 25);
  }
  connect(m_layerTreeView, &QAbstractItemView::activated, this,
          &MainWindow::layerActivated);
  connect(m_layerTreeView, &QAbstractItemView::clicked, this,
          &MainWindow::layerActivated);
  connect(m_sceneTreeView, &QAbstractItemView::clicked, m_layerModel,
          &QtGui::LayerModel::updateRows);

  viewActivated(glWidget);
  buildTools();
  // Connect to the invalid context signal, check whether GL is initialized.
  // connect(m_glWidget, SIGNAL(rendererInvalid()), SLOT(rendererInvalid()));
  connect(m_multiViewWidget, &QtGui::MultiViewWidget::activeWidgetChanged, this,
          &MainWindow::viewActivated);
}

void MainWindow::closeEvent(QCloseEvent* e)
{
  writeSettings();
  if (!saveFileIfNeeded()) {
    e->ignore();
    return;
  }
  QMainWindow::closeEvent(e);
}

void MainWindow::dragEnterEvent(QDragEnterEvent* event)
{
  if (event->mimeData()->hasUrls())
    event->acceptProposedAction();
  else
    event->ignore();
}

void MainWindow::dropEvent(QDropEvent* event)
{
  if (event->mimeData()->hasUrls()) {
    // TODO: check for ZIP, TAR, PY scripts (plugins)
    foreach (const QUrl& url, event->mimeData()->urls()) {
      if (url.isLocalFile()) {
        QString fileName = url.toLocalFile();
        QFileInfo info(fileName);
        QString extension = info.completeSuffix(); // e.g. .tar.gz or .pdb.gz

        if (extension == "py")
          addScript(fileName);
        else
          openFile(fileName);
      }
    }
    event->acceptProposedAction();
  } else
    event->ignore();
}

void MainWindow::moleculeReady(int)
{
  ExtensionPlugin* extension = qobject_cast<ExtensionPlugin*>(sender());
  if (extension) {
    Molecule* mol = new Molecule(this);
    if (extension->readMolecule(*mol))
      setMolecule(mol);
  }
}

void MainWindow::newMolecule()
{
  setMolecule(new Molecule(this));
}

template<class T, class M>
void setWidgetMolecule(T* glWidget, M* mol)
{
  glWidget->setMolecule(mol);
  glWidget->updateScene();
  glWidget->resetCamera();
}

void setDefaultViews(MultiViewWidget* viewWidget)
{
  QSettings settings;
  // save the enabled scene / render plugins
  if (GLWidget* glWidget =
        qobject_cast<GLWidget*>(viewWidget->activeWidget())) {

    const ScenePluginModel* sceneModel = &glWidget->sceneModel();
    for (auto plugin : sceneModel->scenePlugins()) {
      QString settingsKey("MainWindow/" + plugin->objectName());
      bool enabled = settings.value(settingsKey, plugin->isEnabled()).toBool();
      plugin->setEnabled(enabled);
    }
  }
}
void MainWindow::setMolecule(Molecule* mol)
{
  if (!mol)
    return;

  // Set the new molecule, ensure both molecules are in the model.
  if (m_molecule && !m_moleculeModel->molecules().contains(m_molecule)) {
    m_moleculeModel->addItem(m_molecule);
  }
  if (!m_moleculeModel->molecules().contains(mol)) {
    m_moleculeModel->addItem(mol);
  }

  Molecule* oldMolecule(m_molecule);
  m_molecule = mol;

  // If the molecule is empty, make the editor active. Otherwise, use the
  // navigator tool.
  if (m_molecule) {
    QString targetToolName =
      m_molecule->atomCount() > 0 ? "Navigator" : "Editor";
    setActiveTool(targetToolName);
    connect(m_molecule, &QtGui::Molecule::changed, this,
            &MainWindow::markMoleculeDirty);
  }

  emit moleculeChanged(m_molecule);
  markMoleculeClean();
  updateWindowTitle();
  m_moleculeModel->setActiveMolecule(m_molecule);
  m_layerModel->addMolecule(m_molecule);
  // only set the default values if there is nothing setup
  // 1 layer (layerCount) and 1º layer + add items
  if (m_layerModel->layerCount() == 1 && m_layerModel->items() == 2) {
    setDefaultViews(m_multiViewWidget);
    refreshDisplayTypes();
  }

  ActiveObjects::instance().setActiveMolecule(m_molecule);

  if (oldMolecule)
    oldMolecule->disconnect(this);

  // Check if the molecule needs to update the current one.
  QWidget* w = m_multiViewWidget->activeWidget();
  if (GLWidget* glWidget = qobject_cast<QtOpenGL::GLWidget*>(w)) {
    setWidgetMolecule(glWidget, mol);
  }
#ifdef AVO_USE_VTK
  else if (vtkGLWidget* vtkWidget = qobject_cast<vtkGLWidget*>(w)) {
    setWidgetMolecule(vtkWidget, mol);
  }
#endif
}

void MainWindow::markMoleculeDirty()
{
  activeMoleculeEdited();
  if (!m_moleculeDirty) {
    m_moleculeDirty = true;
    updateWindowTitle();
  }
}

void MainWindow::refreshDisplayTypes()
{
  m_layerModel->updateRows();
  // force m_sceneTreeView update (update doesn't update the checkbox)
  m_sceneTreeView->setFocus();
  if (m_activeScenePlugin != nullptr) {
    m_viewDock->setWidget(m_activeScenePlugin->setupWidget());
  }
  // reset focus to m_layerTreeView
  m_layerTreeView->setFocus();
}

void MainWindow::markMoleculeClean()
{
  if (m_moleculeDirty) {
    m_moleculeDirty = false;
    updateWindowTitle();
  }
}

void MainWindow::updateWindowTitle()
{
  QString fileName = tr("Untitled");

  if (m_molecule && m_molecule->hasData("fileName"))
    fileName = QString::fromStdString(m_molecule->data("fileName").toString());

  setWindowTitle(tr("%1%2 - Avogadro %3")
                   .arg(QFileInfo(fileName).fileName())
                   .arg(m_moleculeDirty ? "*" : "")
                   .arg(AvogadroApp_VERSION));
}

#ifdef QTTESTING
void MainWindow::playTest(const QString& fileName, bool exit)
{
  m_testFile = fileName;
  m_testExit = exit;

  QTimer::singleShot(200, this, SLOT(playTest()));
}
#endif

void MainWindow::writeSettings()
{
  QSettings settings;
  settings.beginGroup("MainWindow");
  settings.setValue("size", size());
  settings.setValue("pos", pos());
  settings.setValue("perspective", m_viewPerspective->isChecked());
  settings.endGroup();
  settings.setValue("recentFiles", m_recentFiles);

  // save the enabled scene / render plugins
  if (GLWidget* glWidget =
        qobject_cast<GLWidget*>(m_multiViewWidget->activeWidget())) {

    const ScenePluginModel* sceneModel = &glWidget->sceneModel();
    for (auto plugin : sceneModel->scenePlugins()) {
      QString settingsKey("MainWindow/" + plugin->objectName());
      settings.setValue(settingsKey, plugin->isEnabled());
    }
  }
}

void MainWindow::readSettings()
{
  QSettings settings;
  settings.beginGroup("MainWindow");
  resize(settings.value("size", QSize(800, 600)).toSize());
  move(settings.value("pos", QPoint(20, 20)).toPoint());
  settings.endGroup();
  m_recentFiles = settings.value("recentFiles", QStringList()).toStringList();
}

void MainWindow::openFile()
{
  if (!saveFileIfNeeded())
    return;

  QString filter(QString("%1 (*.cml);;%2 (*.cjson)")
                   .arg(tr("Chemical Markup Language"))
                   .arg(tr("Chemical JSON")));

  QSettings settings;
  QString dir = settings.value("MainWindow/lastOpenDir").toString();

  QString fileName =
    QFileDialog::getOpenFileName(this, tr("Open chemical file"), dir, filter);

  if (fileName.isEmpty()) // user cancel
    return;

  QFileInfo info(fileName);
  dir = info.absoluteDir().absolutePath();
  settings.setValue("MainWindow/lastOpenDir", dir);

  // Create one of our readers to read the file:
  QString extension = info.suffix().toLower();
  FileFormat* reader = nullptr;
  if (extension == "cml")
    reader = new Io::CmlFormat;
  else if (extension == "cjson")
    reader = new Io::CjsonFormat;

  if (!openFile(fileName, reader)) {
    MESSAGEBOX::information(this, tr("Cannot open file"),
                            tr("Can't open supplied file %1").arg(fileName));
  }
}

void MainWindow::importFile()
{
  if (!saveFileIfNeeded())
    return;

  QSettings settings;
  QString dir = settings.value("MainWindow/lastOpenDir").toString();

  FileFormatDialog::FormatFilePair reply =
    QtGui::FileFormatDialog::fileToRead(this, tr("Open Molecule"), dir);

  if (reply.first == nullptr) // user cancel
    return;

  dir = QFileInfo(reply.second).absoluteDir().absolutePath();
  settings.setValue("MainWindow/lastOpenDir", dir);

  if (!openFile(reply.second, reply.first->newInstance())) {
    MESSAGEBOX::information(
      this, tr("Cannot open file"),
      tr("Can't open supplied file %1").arg(reply.second));
  }
}

bool MainWindow::addScript(const QString& filePath)
{
  if (filePath.isEmpty()) {
    return false;
  }

  // Ask the user what type of script this is
  // TODO: add some sort of warning?
  QStringList types;
  types << tr("Commands") << tr("Input Generators") << tr("File Formats");

  bool ok;
  QString item =
    QInputDialog::getItem(this, tr("Install Plugin Script"), tr("Script Type:"),
                          types, 0, false, &ok);

  if (!ok || item.isEmpty())
    return false;

  QString typePath;

  int index = types.indexOf(item);
  // don't translate these
  switch (index) {
    case 0: // commands
      typePath = "commands";
      break;
    case 1:
      typePath = "inputGenerators";
      break;
    case 2:
      typePath = "formatScripts";
      break;
    default:
      typePath = "other";
  }

  QStringList stdPaths =
    QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation);

  QFileInfo info(filePath);

  QString destinationPath(stdPaths[0] + '/' + typePath + '/' + info.fileName());
  qDebug() << " copying " << filePath << " to " << destinationPath;
  QFile::remove(destinationPath); // silently fail if there's nothing to remove
  QFile::copy(filePath, destinationPath);

  // TODO: Ask that type of plugin script to reload?

  return true;
}

bool MainWindow::openFile(const QString& fileName, Io::FileFormat* reader)
{
  if (fileName.isEmpty()) {
    delete reader;
    return false;
  }

  if (reader == nullptr) {
    const Io::FileFormat* format = QtGui::FileFormatDialog::findFileFormat(
      this, tr("Select file reader"), fileName,
      FileFormat::File | FileFormat::Read, "Avogadro:");
    if (format)
      reader = format->newInstance();
  }
  if (!reader)
    return false;

  QString ident = QString::fromStdString(reader->identifier());

  // Prepare the background thread to read in the selected file.
  if (!m_fileReadThread)
    m_fileReadThread = new QThread(this);
  if (m_threadedReader)
    m_threadedReader->deleteLater();
  m_threadedReader = new BackgroundFileFormat(reader);
  if (m_fileReadMolecule)
    m_fileReadMolecule->deleteLater();
  m_fileReadMolecule = new Molecule(this);
  m_fileReadMolecule->setData("fileName", fileName.toLocal8Bit().data());
  m_threadedReader->moveToThread(m_fileReadThread);
  m_threadedReader->setMolecule(m_fileReadMolecule);
  m_threadedReader->setFileName(fileName);

  // Setup a progress dialog in case file loading is slow
  m_progressDialog = new QProgressDialog(this);
  m_progressDialog->setRange(0, 0);
  m_progressDialog->setValue(0);
  m_progressDialog->setMinimumDuration(750);
  m_progressDialog->setWindowTitle(tr("Reading File"));
  m_progressDialog->setLabelText(
    tr("Opening file '%1'\nwith '%2'").arg(fileName).arg(ident));
  /// @todo Add API to abort file ops
  m_progressDialog->setCancelButton(nullptr);
  connect(m_fileReadThread, &QThread::started, m_threadedReader,
          &BackgroundFileFormat::read);
  connect(m_threadedReader, &BackgroundFileFormat::finished, m_fileReadThread,
          &QThread::quit);
  connect(m_threadedReader, &BackgroundFileFormat::finished, this,
          &MainWindow::backgroundReaderFinished);

  // Start the file operation
  m_fileReadThread->start();
  m_progressDialog->show();

  return true;
}

void MainWindow::backgroundReaderFinished()
{
  QString fileName = m_threadedReader->fileName();
  if (m_progressDialog->wasCanceled()) {
    delete m_fileReadMolecule;
  } else if (m_threadedReader->success()) {
    if (!fileName.isEmpty()) {
      m_fileReadMolecule->setData("fileName", fileName.toLocal8Bit().data());
      m_recentFiles.prepend(fileName);
      updateRecentFiles();
    } else {
      m_fileReadMolecule->setData("fileName", Core::Variant());
    }
    setMolecule(m_fileReadMolecule);
    statusBar()->showMessage(tr("Molecule loaded (%1 atoms, %2 bonds)")
                               .arg(m_molecule->atomCount())
                               .arg(m_molecule->bondCount()),
                             5000);
  } else {
    MESSAGEBOX::critical(this, tr("File error"),
                         tr("Error while reading file '%1':\n%2")
                           .arg(fileName)
                           .arg(m_threadedReader->error()));
    delete m_fileReadMolecule;
  }
  m_fileReadThread->deleteLater();
  m_fileReadThread = nullptr;
  m_threadedReader->deleteLater();
  m_threadedReader = nullptr;
  m_fileReadMolecule = nullptr;
  m_progressDialog->hide();
  m_progressDialog->deleteLater();
  m_progressDialog = nullptr;

  reassignCustomElements();

  if (!m_queuedFiles.empty())
    readQueuedFiles();
}

bool MainWindow::backgroundWriterFinished()
{
  QString fileName = m_threadedWriter->fileName();
  bool success = false;
  if (!m_progressDialog->wasCanceled()) {
    if (m_threadedWriter->success()) {
      statusBar()->showMessage(tr("File written: %1").arg(fileName));
      m_threadedWriter->molecule()->setData("fileName",
                                            fileName.toLocal8Bit().data());
      markMoleculeClean();
      updateWindowTitle();
      success = true;
    } else {
      MESSAGEBOX::critical(this, tr("File error"),
                           tr("Error while writing file '%1':\n%2")
                             .arg(fileName)
                             .arg(m_threadedWriter->error()));
    }
  }
  m_fileWriteThread->deleteLater();
  m_fileWriteThread = nullptr;
  m_threadedWriter->deleteLater();
  m_threadedWriter = nullptr;
  m_progressDialog->deleteLater();
  m_progressDialog = nullptr;
  return success;
}

void MainWindow::toolActivated()
{
  if (QAction* action = qobject_cast<QAction*>(sender())) {
    if (GLWidget* glWidget =
          qobject_cast<GLWidget*>(m_multiViewWidget->activeWidget())) {
      glWidget->setActiveTool(action->data().toString());
      if (glWidget->activeTool()) {
        m_toolDock->setWidget(glWidget->activeTool()->toolWidget());
        m_toolDock->setWindowTitle(action->text());

        // uncheck the toolbar
        foreach (QAction* barAction, m_toolToolBar->actions()) {
          if (action->data().toString() != barAction->data().toString())
            barAction->setChecked(false);
        }
      }
    }
  }
}

void MainWindow::viewConfigActivated() {}

void MainWindow::rendererInvalid()
{
  GLWidget* widget = qobject_cast<GLWidget*>(sender());
  MESSAGEBOX::warning(this, tr("Error: Failed to initialize OpenGL context"),
                      tr("OpenGL 2.0 or greater required, exiting.\n\n%1")
                        .arg(widget ? widget->error() : tr("Unknown error")));
  // Process events, and then set a single shot timer. This is needed to ensure
  // the RPC server also exits cleanly.
  QApplication::processEvents();
  QTimer::singleShot(500, this, &QWidget::close);
}

void MainWindow::layerActivated(const QModelIndex& idx)
{
  m_layerModel->updateRows();
  if (idx.row() == m_layerModel->items() - 1) {
    m_layerModel->addLayer(m_molecule->undoMolecule());
  } else {
    bool updateGL = false;
    if (idx.column() == QtGui::LayerModel::ColumnType::Name) {
      m_layerModel->setActiveLayer(idx.row(), m_molecule->undoMolecule());
    } else if (idx.column() == QtGui::LayerModel::ColumnType::Remove) {
      if (m_layerModel->layerCount() > 1) {
        m_layerModel->removeItem(idx.row(), m_molecule->undoMolecule());
        updateGL = true;
      }
    } else if (idx.column() == QtGui::LayerModel::ColumnType::Lock) {
      m_layerModel->flipLocked(idx.row());
    } else if (idx.column() == QtGui::LayerModel::ColumnType::Visible) {
      m_layerModel->flipVisible(idx.row());
      updateGL = true;
    } else if (idx.column() == QtGui::LayerModel::ColumnType::Menu) {
      m_layerModel->setActiveLayer(idx.row(), m_molecule->undoMolecule());
      if (m_sceneDock->isHidden()) {
        m_sceneDock->show();
        m_viewDock->show();
        resizeDocks({ m_sceneDock, m_viewDock }, { 250, 50 }, Qt::Vertical);
      } else {
        m_sceneDock->hide();
        m_viewDock->hide();
      }
    }

    if (updateGL) {
      QWidget* w = m_multiViewWidget->activeWidget();
      if (GLWidget* glWidget = qobject_cast<QtOpenGL::GLWidget*>(w)) {
        glWidget->updateScene();
      }
#ifdef AVO_USE_VTK
      else if (vtkGLWidget* vtkWidget = qobject_cast<vtkGLWidget*>(w)) {
        vtkWidget->updateScene();
      }
#endif
    }
  }
  refreshDisplayTypes();
}

void MainWindow::moleculeActivated(const QModelIndex& idx)
{
  QObject* obj = static_cast<QObject*>(idx.internalPointer());
  if (Molecule* mol = qobject_cast<Molecule*>(obj)) {
    if (idx.column() == 0)
      setMolecule(mol);

    // Deleting a molecule, we must also create a new one if it is the last.
    if (idx.column() == 1) {
      if (m_molecule == mol) {
        QList<Molecule*> molecules = m_moleculeModel->molecules();
        int molIdx = molecules.indexOf(mol);
        if (molIdx > 0)
          setMolecule(molecules[molIdx - 1]);
        else if (molIdx == 0 && molecules.size() > 1) {
          setMolecule(molecules[1]);
        } else {
          newMolecule();
        }
      }
      m_moleculeModel->removeItem(mol);
    }
  }
}

void MainWindow::sceneItemActivated(const QModelIndex& idx)
{
  if (!idx.isValid())
    return;
  QObject* obj = static_cast<QObject*>(idx.internalPointer());
  if (ScenePlugin* scene = qobject_cast<ScenePlugin*>(obj)) {
    m_viewDock->setWidget(scene->setupWidget());
    m_activeScenePlugin = scene;
  }
}

bool populatePluginModel(ScenePluginModel& model, bool editOnly = false)
{
  if (!model.scenePlugins().empty())
    return false;

  QSettings settings;
  PluginManager* plugin = PluginManager::instance();
  QList<ScenePluginFactory*> scenePluginFactories =
    plugin->pluginFactories<ScenePluginFactory>();
  foreach (ScenePluginFactory* factory, scenePluginFactories) {
    ScenePlugin* scenePlugin = factory->createInstance();
    if (editOnly && scenePlugin) {
      if (scenePlugin->objectName() == "BallStick") {
        model.addItem(scenePlugin);
      } else {
        delete scenePlugin;
      }
    } else if (scenePlugin) {
      model.addItem(scenePlugin);
    }
  }
  return true;
}

template<class T>
bool populateTools(T* glWidget)
{
  if (!glWidget->tools().isEmpty())
    return false;
  PluginManager* plugin = PluginManager::instance();
  QList<ToolPluginFactory*> toolPluginFactories =
    plugin->pluginFactories<ToolPluginFactory>();
  foreach (ToolPluginFactory* factory, toolPluginFactories) {
    ToolPlugin* tool = factory->createInstance();
    if (tool)
      glWidget->addTool(tool);
  }
  glWidget->setDefaultTool(QObject::tr("Navigate tool"));
  glWidget->setActiveTool(QObject::tr("Navigate tool"));
  return true;
}

void MainWindow::viewActivated(QWidget* widget)
{
  ActiveObjects::instance().setActiveWidget(widget);
  if (GLWidget* glWidget = qobject_cast<GLWidget*>(widget)) {
    bool firstRun = populatePluginModel(glWidget->sceneModel());
    m_sceneTreeView->setModel(&glWidget->sceneModel());
    populateTools(glWidget);

    foreach (ExtensionPlugin* extension, m_extensions) {
      extension->setScene(&glWidget->renderer().scene());
      extension->setCamera(&glWidget->renderer().camera());
      extension->setActiveWidget(glWidget);
    }

    if (firstRun) {
      setActiveTool("Navigator");
      glWidget->updateScene();
    } else {
      m_moleculeModel->setActiveMolecule(glWidget->molecule());
      m_layerModel->addMolecule(m_molecule);
      // Figure out the active tool - reflect this in the toolbar.
      ToolPlugin* tool = glWidget->activeTool();
      if (tool) {
        QString name = tool->objectName();
        foreach (QAction* action, m_toolToolBar->actions()) {
          action->setChecked(action->data().toString() == name);
        }
      }
    }
    if (m_molecule != glWidget->molecule() && glWidget->molecule()) {
      m_rwMolecule = 0;
      m_molecule = glWidget->molecule();
      emit moleculeChanged(m_molecule);
      m_moleculeModel->setActiveMolecule(m_molecule);
      m_layerModel->addMolecule(m_molecule);
    }
    ActiveObjects::instance().setActiveGLWidget(glWidget);
  }
#ifdef AVO_USE_VTK
  else if (vtkGLWidget* vtkWidget = qobject_cast<vtkGLWidget*>(widget)) {
    bool firstRun = populatePluginModel(vtkWidget->sceneModel());
    m_sceneTreeView->setModel(&vtkWidget->sceneModel());

    if (firstRun) {
      setActiveTool("Navigator");
      vtkWidget->updateScene();
    } else {
      m_moleculeModel->setActiveMolecule(vtkWidget->molecule());
      m_layerModel->addMolecule(m_molecule);
    }
    if (m_molecule != vtkWidget->molecule() && vtkWidget->molecule()) {
      m_rwMolecule = nullptr;
      m_molecule = vtkWidget->molecule();
      emit moleculeChanged(m_molecule);
      m_moleculeModel->setActiveMolecule(m_molecule);
      m_layerModel->addMolecule(m_molecule);
    }
  }
#endif
  updateWindowTitle();
  activeMoleculeEdited();
}

void MainWindow::exportGraphics()
{
  QOpenGLWidget* glWidget =
    qobject_cast<QOpenGLWidget*>(m_multiViewWidget->activeWidget());
  QStringList filters;
// Omit "common image formats" on Mac
#ifdef Q_OS_MAC
  filters
#else
  filters << tr("Common image formats") + " (*.png *.jpg *.jpeg)"
#endif
    << tr("All files") + " (* *.*)" << tr("BMP") + " (*.bmp)"
    << tr("PNG") + " (*.png)" << tr("JPEG") + " (*.jpg *.jpeg)";

  // Use QFileInfo to get the parts of the path we want
  QString baseFileName;
  if (m_molecule)
    baseFileName = m_molecule->data("fileName").toString().c_str();
  QFileInfo info(baseFileName);

  QString fileName = QFileDialog::getSaveFileName(
    this, tr("Export Bitmap Graphics"), "", "PNG (*.png)");

  if (fileName.isEmpty())
    return;
  if (QFileInfo(fileName).suffix().isEmpty())
    fileName += ".png";

  // render it (with alpha channel)
  Rendering::Scene* scene(nullptr);
  GLWidget* viewWidget(nullptr);
  if ((viewWidget =
         qobject_cast<GLWidget*>(m_multiViewWidget->activeWidget()))) {
    scene = &viewWidget->renderer().scene();
  }
  Vector4ub cColor = scene->backgroundColor();
  unsigned char alpha = cColor[3];
  cColor[3] = 0; // 100% transparent for export
  scene->setBackgroundColor(cColor);

  QImage exportImage;
  glWidget->raise();
  glWidget->repaint();
  if (QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()) {
    exportImage = glWidget->grabFramebuffer();
  } else {
    QPixmap pixmap = QPixmap::grabWindow(glWidget->winId());
    exportImage = pixmap.toImage();
  }

  // Now we embed molecular information into the file, if possible
  if (m_molecule && m_molecule->atomCount() < 1000) {
    string tmpCml;
    bool ok =
      FileFormatManager::instance().writeString(*m_molecule, tmpCml, "cml");
    if (ok)
      exportImage.setText("CML", tmpCml.c_str());
  }

  if (!exportImage.save(fileName)) {
    MESSAGEBOX::warning(this, tr("Avogadro"),
                        tr("Cannot save file %1.").arg(fileName));
  }

  // set the GL widget back to the right background color (i.e., not 100%
  // transparent)
  cColor[3] = alpha; // previous color
  scene->setBackgroundColor(cColor);
  glWidget->repaint();
}

void MainWindow::reassignCustomElements()
{
  if (m_molecule && m_molecule->hasCustomElements())
    CustomElementDialog::resolve(this, *m_molecule);
}

void MainWindow::openRecentFile()
{
  if (!saveFileIfNeeded())
    return;

  QAction* action = qobject_cast<QAction*>(sender());
  if (action) {
    QString fileName = action->data().toString();

    const FileFormat* format =
      FileFormatDialog::findFileFormat(this, tr("Select file reader"), fileName,
                                       FileFormat::File | FileFormat::Read);

    if (!openFile(fileName, format ? format->newInstance() : nullptr)) {
      MESSAGEBOX::information(this, tr("Cannot open file"),
                              tr("Can't open supplied file %1").arg(fileName));
    }
  }
}

void MainWindow::updateRecentFiles()
{
  m_recentFiles.removeDuplicates();
  while (m_recentFiles.size() > 10)
    m_recentFiles.removeLast();

  int i = 0;
  foreach (const QString& file, m_recentFiles) {
    QFileInfo fileInfo(file);
    QAction* recentFile = m_actionRecentFiles[i++];
    recentFile->setText(fileInfo.fileName());
    recentFile->setData(file);
    recentFile->setVisible(true);
    recentFile->setEnabled(true);
  }
  for (; i < 10; ++i)
    m_actionRecentFiles[i]->setVisible(false);
}

bool MainWindow::saveFile(bool async)
{
  QObject* molObj = m_moleculeModel->activeMolecule();

  if (!molObj)
    return false;

  Molecule* mol = qobject_cast<Molecule*>(molObj);
  if (!mol) {
    RWMolecule* rwMol = qobject_cast<RWMolecule*>(molObj);
    if (!rwMol)
      return false;
    return saveFileAs(async);
  }

  if (!mol->hasData("fileName"))
    return saveFileAs(async);

  string fileName = mol->data("fileName").toString();
  QString extension =
    QFileInfo(QString::fromStdString(fileName)).suffix().toLower();

  // Was the original format standard, or imported?
  if (extension == QLatin1String("cml")) {
    return saveFileAs(QString::fromStdString(fileName), new Io::CmlFormat,
                      async);
  } else if (extension == QLatin1String("cjson")) {
    return saveFileAs(QString::fromStdString(fileName), new Io::CjsonFormat,
                      async);
  }

  // is the imported format writable?
  bool writable =
    !FileFormatManager::instance()
       .fileFormatsFromFileExtension(extension.toStdString(),
                                     FileFormat::File | FileFormat::Read)
       .empty();
  if (writable) {
    // Warn the user that the format may lose data.
    MESSAGEBOX box(this);
    box.setModal(true);
    box.setWindowTitle(tr("Avogadro"));
    box.setText(tr("This file was imported from a non-standard format which "
                   "may not be able to write all of the information in the "
                   "molecule.\n\nWould you like to export to the current "
                   "format, or save in a standard format?"));
    QPushButton* saveButton(box.addButton(QMessageBox::Save));
    QPushButton* cancelButton(box.addButton(QMessageBox::Cancel));
    QPushButton* exportButton(
      box.addButton(tr("Export"), QMessageBox::DestructiveRole));
    box.setDefaultButton(saveButton);
    box.exec();
    if (box.clickedButton() == saveButton)
      return saveFileAs(async);
    else if (box.clickedButton() == cancelButton)
      return false;
    else if (box.clickedButton() == exportButton)
      return exportFile(async);
  }

  // Otherwise, prompt for a new valid format.
  return saveFileAs(async);
}

bool MainWindow::saveFileAs(bool async)
{
  QString filter(QString("%1 (*.cjson);;%2 (*.cml)")
                   .arg(tr("Chemical JSON"))
                   .arg(tr("Chemical Markup Language")));

  QSettings settings;
  QString dir = settings.value("MainWindow/lastSaveDir").toString();

  QString fileName =
    QFileDialog::getSaveFileName(this, tr("Save chemical file"), dir, filter);

  if (fileName.isEmpty()) // user cancel
    return false;

  QFileInfo info(fileName);
  dir = info.absoluteDir().absolutePath();
  settings.setValue("MainWindow/lastSaveDir", dir);

  // Create one of our writers to save the file:
  QString extension = info.suffix().toLower();
  FileFormat* writer = nullptr;
  if (extension == "cjson" || extension.isEmpty())
    writer = new Io::CjsonFormat;
  else if (extension == "cml")
    writer = new Io::CmlFormat;
  if (extension.isEmpty())
    fileName += ".cml";

  return saveFileAs(fileName, writer, async);
}

bool MainWindow::exportFile(bool async)
{
  QSettings settings;
  QString dir = settings.value("MainWindow/lastSaveDir").toString();

  FileFormatDialog::FormatFilePair reply =
    QtGui::FileFormatDialog::fileToWrite(this, tr("Export Molecule"), dir);

  if (reply.first == nullptr) // user cancel
    return false;

  dir = QFileInfo(reply.second).absoluteDir().absolutePath();
  settings.setValue("MainWindow/lastSaveDir", dir);

  return saveFileAs(reply.second, reply.first->newInstance(), async);
}

bool MainWindow::saveFileAs(const QString& fileName, Io::FileFormat* writer,
                            bool async)
{
  if (fileName.isEmpty() || writer == nullptr) {
    delete writer;
    return false;
  }

  QString ident = QString::fromStdString(writer->identifier());

  // Figure out what molecule willl be saved, perform conversion if necessary.
  QObject* molObj = m_moleculeModel->activeMolecule();

  if (!molObj) {
    delete writer;
    return false;
  }

  // Initialize out writer.
  if (!m_fileWriteThread)
    m_fileWriteThread = new QThread(this);
  if (m_threadedWriter)
    m_threadedWriter->deleteLater();
  m_threadedWriter = new BackgroundFileFormat(writer);

  Molecule* mol = qobject_cast<Molecule*>(molObj);
  if (!mol) {
    delete writer;
    return false;
  }

  // Prepare the background thread to write the selected file.
  m_threadedWriter->moveToThread(m_fileWriteThread);
  m_threadedWriter->setMolecule(mol);
  m_threadedWriter->setFileName(fileName);

  // Setup a progress dialog in case file loading is slow
  m_progressDialog = new QProgressDialog(this);
  m_progressDialog->setRange(0, 0);
  m_progressDialog->setValue(0);
  m_progressDialog->setMinimumDuration(750);
  m_progressDialog->setWindowTitle(tr("Writing File"));
  m_progressDialog->setLabelText(
    tr("Writing file '%1'\nwith '%2'").arg(fileName).arg(ident));
  /// @todo Add API to abort file ops
  m_progressDialog->setCancelButton(nullptr);
  connect(m_fileWriteThread, &QThread::started, m_threadedWriter,
          &BackgroundFileFormat::write);
  connect(m_threadedWriter, &BackgroundFileFormat::finished, m_fileWriteThread,
          &QThread::quit);

  // Start the file operation
  m_progressDialog->show();
  if (async) {
    connect(m_threadedWriter, &BackgroundFileFormat::finished, this,
            &MainWindow::backgroundWriterFinished);
    m_fileWriteThread->start();
    return true;
  } else {
    QTimer::singleShot(0, m_fileWriteThread, SLOT(start()));
    QEventLoop loop;
    connect(m_fileWriteThread, &QThread::finished, &loop, &QEventLoop::quit);
    loop.exec();
    return backgroundWriterFinished();
  }
}

void MainWindow::setActiveTool(QString toolName)
{
  if (GLWidget* glWidget =
        qobject_cast<GLWidget*>(m_multiViewWidget->activeWidget())) {
    foreach (ToolPlugin* toolPlugin, glWidget->tools()) {
      if (toolPlugin->objectName() == toolName) {
        toolPlugin->activateAction()->triggered();
        glWidget->setActiveTool(toolPlugin);

        // update the settings widget
        m_toolDock->setWidget(toolPlugin->toolWidget());
        m_toolDock->setWindowTitle(toolPlugin->activateAction()->text());
      }
    }
  }

  if (!toolName.isEmpty()) {
    // check view tools
    foreach (QAction* action, m_toolToolBar->actions()) {
      if (action->data().toString() == toolName)
        action->setChecked(true);
      else
        action->setChecked(false);
    }
  }
}

void MainWindow::setActiveDisplayTypes(QStringList displayTypes)
{
  ScenePluginModel* scenePluginModel(nullptr);
  GLWidget* glWidget(nullptr);
#ifdef AVO_USE_VTK
  VTK::vtkGLWidget* vtkWidget(nullptr);
#endif
  if ((glWidget = qobject_cast<GLWidget*>(m_multiViewWidget->activeWidget()))) {
    scenePluginModel = &glWidget->sceneModel();
  }
#ifdef AVO_USE_VTK
  else if ((vtkWidget = qobject_cast<VTK::vtkGLWidget*>(
              m_multiViewWidget->activeWidget()))) {
    scenePluginModel = &vtkWidget->sceneModel();
  }
#endif

  foreach (ScenePlugin* scene, scenePluginModel->scenePlugins())
    scene->setEnabled(false);
  foreach (ScenePlugin* scene, scenePluginModel->scenePlugins())
    foreach (const QString& name, displayTypes)
      if (scene->objectName() == name)
        scene->setEnabled(true);
  if (glWidget)
    glWidget->updateScene();
#ifdef AVO_USE_VTK
  else if (vtkWidget)
    vtkWidget->updateScene();
#endif
}

void MainWindow::undoEdit()
{
  if (m_molecule) {
    m_molecule->undoMolecule()->undoStack().undo();
    m_molecule->emitChanged(Molecule::Atoms | Molecule::Added);
    m_layerModel->updateRows();
    activeMoleculeEdited();
  }
}

void MainWindow::redoEdit()
{
  if (m_molecule) {
    m_molecule->undoMolecule()->undoStack().redo();
    m_molecule->emitChanged(Molecule::Atoms | Molecule::Added);
    m_layerModel->updateRows();
    activeMoleculeEdited();
  }
}

void MainWindow::activeMoleculeEdited()
{
  if (!m_undo || !m_redo)
    return;
  if (m_molecule) {
    if (m_molecule->undoMolecule()->undoStack().canUndo()) {
      m_undo->setEnabled(true);
      m_undo->setText(
        tr("&Undo %1").arg(m_molecule->undoMolecule()->undoStack().undoText()));
    } else {
      m_undo->setEnabled(false);
      m_undo->setText(tr("&Undo"));
    }
    if (m_molecule->undoMolecule()->undoStack().canRedo()) {
      m_redo->setEnabled(true);
      m_redo->setText(
        tr("&Redo %1").arg(m_molecule->undoMolecule()->undoStack().redoText()));
    } else {
      m_redo->setEnabled(false);
      m_redo->setText(tr("&Redo"));
    }
  } else {
    m_undo->setEnabled(false);
    m_redo->setEnabled(false);
  }
}

void MainWindow::setBackgroundColor()
{
  Rendering::Scene* scene(nullptr);
  GLWidget* glWidget(nullptr);
  if ((glWidget = qobject_cast<GLWidget*>(m_multiViewWidget->activeWidget())))
    scene = &glWidget->renderer().scene();
  if (scene) {
    Vector4ub cColor = scene->backgroundColor();
    QColor qtColor(cColor[0], cColor[1], cColor[2], cColor[3]);
    QColor color = QColorDialog::getColor(qtColor, this);
    if (color.isValid()) {
      cColor[0] = static_cast<unsigned char>(color.red());
      cColor[1] = static_cast<unsigned char>(color.green());
      cColor[2] = static_cast<unsigned char>(color.blue());
      cColor[3] = static_cast<unsigned char>(color.alpha());
      scene->setBackgroundColor(cColor);
      if (glWidget)
        glWidget->update();

      QSettings settings;
      settings.setValue("backgroundColor", color);
    }
  }
}

void MainWindow::setProjectionPerspective()
{
  Rendering::GLRenderer* renderer(nullptr);
  GLWidget* glWidget(nullptr);
  if ((glWidget = qobject_cast<GLWidget*>(m_multiViewWidget->activeWidget())))
    renderer = &glWidget->renderer();
  if (renderer) {
    renderer->camera().setProjectionType(Rendering::Perspective);
    if (glWidget)
      glWidget->update();
  }
}

void MainWindow::setProjectionOrthographic()
{
  Rendering::GLRenderer* renderer(nullptr);
  GLWidget* glWidget(nullptr);
  if ((glWidget = qobject_cast<GLWidget*>(m_multiViewWidget->activeWidget())))
    renderer = &glWidget->renderer();
  if (renderer) {
    renderer->camera().setProjectionType(Rendering::Orthographic);
    if (glWidget)
      glWidget->update();
  }
}

#ifdef QTTESTING
void MainWindow::record()
{
  QString fileName = QFileDialog::getSaveFileName(
    this, "Test file name", QString(), "XML Files (*.xml)");
  if (!fileName.isEmpty())
    m_testUtility->recordTests(fileName);
}

void MainWindow::play()
{
  QString fileName = QFileDialog::getOpenFileName(
    this, "Test file name", QString(), "XML Files (*.xml)");
  if (!fileName.isEmpty())
    m_testUtility->playTests(fileName);
}

void MainWindow::playTest()
{
  if (!m_testFile.isEmpty()) {
    m_testUtility->playTests(m_testFile);
    if (m_testExit)
      close();
  }
}

void MainWindow::popup()
{
  QDialog dialog;
  QHBoxLayout* hbox = new QHBoxLayout(&dialog);
  QPushButton button("Click to Close", &dialog);
  hbox->addWidget(&button);
  QObject::connect(&button, SIGNAL(clicked()), &dialog, SLOT(accept()));
  dialog.exec();
}
#endif

void MainWindow::buildMenu()
{
#ifdef QTTESTING
  QStringList testingPath;
  testingPath << tr("&Testing");
  QAction* actionRecord = new QAction(this);
  actionRecord->setText(tr("Record test..."));
  m_menuBuilder->addAction(testingPath, actionRecord, 10);
  QAction* actionPlay = new QAction(this);
  actionPlay->setText(tr("Play test..."));
  m_menuBuilder->addAction(testingPath, actionPlay, 5);

  connect(actionRecord, SIGNAL(triggered()), SLOT(record()));
  connect(actionPlay, SIGNAL(triggered()), SLOT(play()));

  m_testUtility = new pqTestUtility(this);
  m_testUtility->addEventObserver("xml", new XMLEventObserver(this));
  m_testUtility->addEventSource("xml", new XMLEventSource(this));

  m_testExit = true;
#endif

  QStringList path;
  path << "&File";
  // New
  QAction* action = new QAction(tr("&New"), this);
  action->setShortcut(QKeySequence::New);
#ifndef Q_OS_MAC
  action->setIcon(QIcon::fromTheme("document-new"));
#endif
  m_menuBuilder->addAction(path, action, 999);
  m_fileToolBar->addAction(action);
  connect(action, &QAction::triggered, this, &MainWindow::newMolecule);
  // Open
  action = new QAction(tr("&Open..."), this);
  action->setShortcut(QKeySequence::Open);
#ifndef Q_OS_MAC
  action->setIcon(QIcon::fromTheme("document-open"));
#endif
  m_menuBuilder->addAction(path, action, 998);
  m_fileToolBar->addAction(action);
  connect(action, &QAction::triggered, this, &MainWindow::importFile);

  action = new QAction(tr("&Close"), this);
  action->setShortcut(QKeySequence::Close);
#ifndef Q_OS_MAC
  action->setIcon(QIcon::fromTheme("document-close"));
#endif
  m_menuBuilder->addAction(path, action, 981);
  m_fileToolBar->addAction(action);
  connect(action, &QAction::triggered, this, &QWidget::close);

  // Separator (after open recent)
  action = new QAction("", this);
  action->setSeparator(true);
  m_menuBuilder->addAction(path, action, 980);
  // Save
  action = new QAction(tr("&Save"), this);
  action->setShortcut(QKeySequence::Save);
#ifndef Q_OS_MAC
  action->setIcon(QIcon::fromTheme("document-save"));
#endif
  m_menuBuilder->addAction(path, action, 965);
  m_fileToolBar->addAction(action);
  connect(action, &QAction::triggered, this, &MainWindow::saveFile);
  // Save As
  action = new QAction(tr("Save &As..."), this);
  action->setShortcut(QKeySequence::SaveAs);
#ifndef Q_OS_MAC
  action->setIcon(QIcon::fromTheme("document-save-as"));
#endif
  m_menuBuilder->addAction(path, action, 960);
  m_fileToolBar->addAction(action);
  connect(action, SIGNAL(triggered()), SLOT(saveFileAs()));

  // Export
  QStringList exportPath = path;
  exportPath << tr("&Export");
  action = new QAction(tr("&Molecule..."), this);
  m_menuBuilder->addAction(exportPath, action, 10);
  m_fileToolBar->addAction(action);
#ifndef Q_OS_MAC
  action->setIcon(QIcon::fromTheme("document-export"));
#endif
  connect(action, &QAction::triggered, this, &MainWindow::exportFile);
  // Export graphics
  action = new QAction(tr("&Graphics..."), this);
  m_menuBuilder->addAction(exportPath, action, 10);
#ifndef Q_OS_MAC
  action->setIcon(QIcon::fromTheme("document-export"));
#endif
  connect(action, &QAction::triggered, this, &MainWindow::exportGraphics);

  // Quit
  action = new QAction(tr("&Quit"), this);
  action->setShortcut(QKeySequence::Quit);
#ifndef Q_OS_MAC
  action->setIcon(QIcon::fromTheme("application-exit"));
#endif
  m_menuBuilder->addAction(path, action, -200);
  connect(action, &QAction::triggered, this, &QWidget::close);

  // open recent 995 - 985
  // Populate the recent file actions list.
  path << "Open Recent";
  // TODO: Check if files exist and if we actually have 10 items
  for (int i = 0; i < 10; ++i) {
    action = new QAction(QString::number(i), this);
    m_actionRecentFiles.push_back(action);
#ifndef Q_OS_MAC
    action->setIcon(QIcon::fromTheme("document-open-recent"));
#endif
    action->setVisible(false);
    m_menuBuilder->addAction(path, action, 995 - i);
    connect(action, &QAction::triggered, this, &MainWindow::openRecentFile);
  }
  m_actionRecentFiles[0]->setText(tr("No recent files"));
  m_actionRecentFiles[0]->setVisible(true);
  m_actionRecentFiles[0]->setEnabled(false);

  // Undo/redo
  QStringList editPath;
  editPath << tr("&Edit");
  m_undo = new QAction(tr("&Undo"), this);
#ifndef Q_OS_MAC
  m_undo->setIcon(QIcon::fromTheme("edit-undo"));
#endif
  m_undo->setShortcut(QKeySequence::Undo);
  m_redo = new QAction(tr("&Redo"), this);
#ifndef Q_OS_MAC
  m_redo->setIcon(QIcon::fromTheme("edit-redo"));
#endif
  m_redo->setShortcut(QKeySequence::Redo);
  m_undo->setEnabled(false);
  m_redo->setEnabled(false);
  connect(m_undo, &QAction::triggered, this, &MainWindow::undoEdit);
  connect(m_redo, &QAction::triggered, this, &MainWindow::redoEdit);
  m_menuBuilder->addAction(editPath, m_undo, 1);
  m_menuBuilder->addAction(editPath, m_redo, 0);

  // View menu
  QStringList viewPath;
  viewPath << tr("&View");
  action = new QAction(tr("Set background color..."), this);
  m_menuBuilder->addAction(viewPath, action, 100);
  connect(action, &QAction::triggered, this, &MainWindow::setBackgroundColor);

  // set default projection
  QSettings settings;
  bool perspective = settings.value("MainWindow/perspective", true).toBool();

  viewPath << tr("Projection");
  m_viewPerspective = new QAction(tr("Perspective"), this);
  m_viewPerspective->setCheckable(true);
  m_viewPerspective->setChecked(perspective);
  m_menuBuilder->addAction(viewPath, m_viewPerspective, 10);
  connect(m_viewPerspective, &QAction::triggered, this,
          &MainWindow::setProjectionPerspective);

  m_viewOrthographic = new QAction(tr("Orthographic"), this);
  m_viewOrthographic->setCheckable(true);
  m_viewOrthographic->setChecked(!perspective);
  m_menuBuilder->addAction(viewPath, m_viewOrthographic, 10);
  connect(m_viewOrthographic, &QAction::triggered, this,
          &MainWindow::setProjectionOrthographic);

  connect(m_viewPerspective, &QAction::triggered, m_viewOrthographic,
          &QAction::toggle);
  connect(m_viewOrthographic, &QAction::triggered, m_viewPerspective,
          &QAction::toggle);

  if (perspective)
    setProjectionPerspective();
  else
    setProjectionOrthographic();

  // Periodic table.
  QStringList extensionsPath;
  extensionsPath << tr("&Extensions");
  action = new QAction("&Periodic Table", this);
  m_menuBuilder->addAction(extensionsPath, action, 0);
  QtGui::PeriodicTableView* periodicTable = new QtGui::PeriodicTableView(this);
  connect(action, &QAction::triggered, periodicTable, &QWidget::show);

  QStringList helpPath;
  helpPath << tr("&Help");
  QAction* about = new QAction("&About", this);
#ifndef Q_OS_MAC
  about->setIcon(QIcon::fromTheme("help-about"));
#endif
  m_menuBuilder->addAction(helpPath, about, 20);
  connect(about, &QAction::triggered, this, &MainWindow::showAboutDialog);

  // Now actually add all menu entries.
  m_menuBuilder->buildMenuBar(menuBar());
}

void MainWindow::buildMenu(QtGui::ExtensionPlugin* extension)
{
  foreach (QAction* action, extension->actions())
    m_menuBuilder->addAction(extension->menuPath(action), action);
}

// TODO: this would be a lovely C++11 lambda
bool ToolSort(const ToolPlugin* a, const ToolPlugin* b)
{
  return a->priority() < b->priority();
}

void MainWindow::buildTools()
{
  PluginManager* plugin = PluginManager::instance();

  // Now the tool plugins need to be built/added.
  QList<ToolPluginFactory*> toolPluginFactories =
    plugin->pluginFactories<ToolPluginFactory>();
  foreach (ToolPluginFactory* factory, toolPluginFactories) {
    ToolPlugin* tool = factory->createInstance();
    if (tool)
      m_tools << tool;
  }

  // sort them based on priority
  std::sort(m_tools.begin(), m_tools.end(), ToolSort);

  QActionGroup* toolActions = new QActionGroup(this);
  int index = 1;
  foreach (ToolPlugin* toolPlugin, m_tools) {
    // Add action to toolbar.
    toolPlugin->setParent(this);
    QAction* action = toolPlugin->activateAction();
    action->setParent(toolPlugin);
    action->setCheckable(true);
    if (index < 10)
      action->setShortcut(QKeySequence(QString("Ctrl+%1").arg(index)));
    action->setData(toolPlugin->objectName());
    qDebug() << toolPlugin->objectName() << "added";
    toolActions->addAction(action);
    connect(action, &QAction::triggered, this, &MainWindow::toolActivated);
    ++index;
  }

  m_toolToolBar->addActions(toolActions->actions());
}

QString MainWindow::extensionToWildCard(const QString& extension)
{
  // This is a list of "extensions" returned by OB that are not actually
  // file extensions, but rather the full filename of the file. These
  // will be used as-is in the filter string, while others will be prepended
  // with "*.".
  static QStringList nonExtensions;
  if (nonExtensions.empty()) {
    nonExtensions << "POSCAR"  // VASP input geometry
                  << "CONTCAR" // VASP output geometry
                  << "HISTORY" // DL-POLY history file
                  << "CONFIG"  // DL-POLY config file
      ;
  }

  if (nonExtensions.contains(extension))
    return extension;
  return QString("*.%1").arg(extension);
}

QString MainWindow::generateFilterString(
  const std::vector<const Io::FileFormat*>& formats, bool addAllEntry)
{
  QString result;

  // Create a map that groups the file extensions by name:
  QMap<QString, QString> formatMap;
  for (vector<const FileFormat*>::const_iterator it = formats.begin(),
                                                 itEnd = formats.end();
       it != itEnd; ++it) {
    QString name(QString::fromStdString((*it)->name()));
    vector<string> exts = (*it)->fileExtensions();
    for (vector<string>::const_iterator eit = exts.begin(), eitEnd = exts.end();
         eit != eitEnd; ++eit) {
      QString ext(QString::fromStdString(*eit));
      if (!formatMap.values(name).contains(ext)) {
        formatMap.insertMulti(name, ext);
      }
    }
  }

  // This holds all known extensions:
  QStringList allExtensions;

  foreach (const QString& desc, formatMap.uniqueKeys()) {
    QStringList extensions;
    foreach (QString extension, formatMap.values(desc))
      extensions << extensionToWildCard(extension);
    allExtensions << extensions;
    result += QString("%1 (%2);;").arg(desc, extensions.join(" "));
  }

  if (addAllEntry) {
    result.prepend(tr("All supported formats (%1);;All files (*);;")
                     .arg(allExtensions.join(" ")));
  }

  return result;
}

bool MainWindow::saveFileIfNeeded()
{
  if (m_moleculeDirty) {
    // We're using the property interface to QMessageBox, rather than
    // the static functions. This is more work, but gives us some nice
    // fine-grain control. This helps both on Windows and Mac
    // look more "native."
    QPointer<MESSAGEBOX> msgBox = new MESSAGEBOX(
      QMessageBox::Warning, tr("Avogadro"),
      tr("Do you want to save the changes you made in the document?"),
      QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, this);

    // On Mac, this will make a sheet relative to the window
    // Unfortunately, it also closes the window when the box disappears!
    // msgBox->setWindowModality(Qt::WindowModal);
    // second line of text
    msgBox->setInformativeText(
      tr("Your changes will be lost if you don't save them."));
    msgBox->setDefaultButton(QMessageBox::Save);
#ifdef Q_OS_MAC
    msgBox->setWindowModality(Qt::WindowModal);
#endif

    // OK, now add shortcuts for save and discard
    msgBox->button(QMessageBox::Save)
      ->setShortcut(QKeySequence(tr("Ctrl+S", "Save")));
    msgBox->button(QMessageBox::Discard)
      ->setShortcut(QKeySequence(tr("Ctrl+D", "Discard")));
    //    msgBox->setButtonText(QMessageBox::Save,
    //                          isDefaultFileName(d->fileName) ? tr("Save...") :
    //                          tr("Save"));

    int response = msgBox->exec();

    switch (static_cast<QMessageBox::StandardButton>(response)) {
      case QMessageBox::Save:
        // Synchronous save -- needed so that we don't lose changes if the
        // writer
        // fails:
        return saveFile(/*async=*/false);
      case QMessageBox::Discard:
        markMoleculeClean();
        return true;
      default:
      case QMessageBox::Cancel:
        return false;
    }
  }

  return true;
}

void MainWindow::registerMoleQueue()
{
#ifdef Avogadro_ENABLE_RPC
  MoleQueue::Client client;
  if (!client.connectToServer() || !client.isConnected())
    return;

  // Get all extensions;
  typedef std::vector<std::string> StringList;
  FileFormatManager& ffm = FileFormatManager::instance();
  StringList exts = ffm.fileExtensions(FileFormat::Read | FileFormat::File);

  // Create patterns list
  QList<QRegExp> patterns;
  for (StringList::const_iterator it = exts.begin(), itEnd = exts.end();
       it != itEnd; ++it) {
    patterns << QRegExp(extensionToWildCard(QString::fromStdString(*it)),
                        Qt::CaseInsensitive, QRegExp::Wildcard);
  }

  // Register the executable:
  client.registerOpenWith("Avogadro2 (new)", qApp->applicationFilePath(),
                          patterns);

  client.registerOpenWith("Avogadro2 (running)", "avogadro", "openFile",
                          patterns);
#endif // Avogadro_ENABLE_RPC
}

void MainWindow::showAboutDialog()
{
  AboutDialog about(this);
  about.exec();
}

void MainWindow::fileFormatsReady()
{
  ExtensionPlugin* extension(qobject_cast<ExtensionPlugin*>(sender()));
  if (!extension)
    return;
  foreach (FileFormat* format, extension->fileFormats()) {
    if (!FileFormatManager::registerFormat(format)) {
      qWarning() << tr("Error while loading FileFormat with id '%1'.")
                      .arg(QString::fromStdString(format->identifier()));
      // Need to delete the format if the manager didn't take ownership:
      delete format;
    }
  }
  readQueuedFiles();
}

void MainWindow::readQueuedFiles()
{
  m_queuedFilesStarted = true;
  if (!m_queuedFiles.empty()) {
    QString file = m_queuedFiles.first();
    m_queuedFiles.removeFirst();
    const FileFormat* format = QtGui::FileFormatDialog::findFileFormat(
      this, tr("Select file reader"), file, FileFormat::File | FileFormat::Read,
      "Avogadro:");

    if (!openFile(file, format ? format->newInstance() : nullptr)) {
      MESSAGEBOX::warning(this, tr("Cannot open file"),
                          tr("Avogadro timed out and doesn't know how to open"
                             " '%1'.")
                            .arg(file));
    }
  }
}

void MainWindow::clearQueuedFiles()
{
  if (!m_queuedFilesStarted && !m_queuedFiles.isEmpty()) {
    MESSAGEBOX::warning(this, tr("Cannot open files"),
                        tr("Avogadro timed out and cannot open"
                           " '%1'.")
                          .arg(m_queuedFiles.join("\n")));
    m_queuedFiles.clear();
  }
}

} // End of Avogadro namespace
