/* massXpert - the true massist's program.
   -------------------------------------- 
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.filomace.org/massXpert

   This file is part of the massXpert project.
   
   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.
   
   
   This software 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 software; if not, write to the 

   Free Software Foundation, Inc.,
   
   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


/////////////////////// Qt includes
#include <QtGui>
#include <QtXml>
#include <QtGlobal>
#include <QAction>
#include <QtPlugin>
#include <QPluginLoader> 


/////////////////////// Local includes
#include "mainWindow.hpp"
#include "application.hpp"
#include "polChemDefWnd.hpp"
#include "calculatorWnd.hpp"
#include "sequenceEditorWnd.hpp"
#include "mzLabWnd.hpp"
#include "aboutDlg.hpp"
#include "plugin_interfaces.hpp"


namespace massXpert
{
  
  MainWindow::MainWindow()
  {
    createActions();
    createMenus();
    createStatusBar();
    loadPlugins();

    QPixmap pixmap(":/images/massxpert-icon-32.png");
    QIcon icon(pixmap);
    setWindowIcon(icon);
  
    QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
    restoreGeometry(settings.value("main_window/geometry").toByteArray());

    setWindowTitle("massXpert");

    // At this point, try to check if we should remind the user to
    // cite the paper.

    int runNumber = settings.value("globals/runNumber", 1).toInt();
    
    if (runNumber == 15)
      {
	AboutDlg *dlg = new AboutDlg(static_cast<QWidget *>(this));

	dlg->showHowToCiteTab();
	
	dlg->show();
	
	settings.setValue("globals/runNumber", 1);
      }
    else
      {
	settings.setValue("globals/runNumber", ++runNumber);
      }
  }


  void 
  MainWindow::openPolChemDef()
  {
    // We are asked to open a polymer chemistry definition file, which
    // we'll do inside a PolChemDefWnd.
    Application *application = static_cast<Application *>(qApp);
    PolChemDefWnd *polChemDefWnd = 0;
    QStringList stringList;
    QString filePath;
    bool ok;

    // Get the polchemdef catalogue contents.
    application->polChemDefCatStringList(stringList) ; 

    // Ask the user to select one file or to click Cancel to browse.
    filePath = 
      QInputDialog::getItem(this, tr("Select a polymer chemistry definition "
				       "or click Cancel to browse"),
			     tr("Polymer chemistry definition:"), 
			     stringList, 0, false, &ok);
  
    if (!ok || filePath.isEmpty())
      {
	filePath = 
	  QFileDialog::getOpenFileName(this, tr("Open definition file"),
					QDir::homePath(),
					tr("XML files(*.xml *.XML)"));
      
	if(filePath.isNull() || filePath.isEmpty())
	  return;
	
	if(!QFile::exists(filePath))
	  return;
      }

    polChemDefWnd = new PolChemDefWnd(filePath);

    application->polChemDefWndList()->append(polChemDefWnd);
  }


  void 
  MainWindow::newPolChemDef()
  {
    // We are asked to create a polymer chemistry definition file, which
    // we'll do inside a PolChemDefWnd.
    Application *application = static_cast<Application *>(qApp);

    PolChemDefWnd *polChemDefWnd = new PolChemDefWnd();
  
    application->polChemDefWndList()->append(polChemDefWnd);
  }


  void 
  MainWindow::newCalculator()
  {
    Application *application = static_cast<Application *>(qApp);
    QStringList stringList;
    QString filePath;
    bool ok;

    // Open a calculator window, making sure that a polymer chemistry
    // definition is offered to open to the caller.
  
    // Get the polchemdef catalogue contents.
    application->polChemDefCatStringList(stringList) ; 

    // Ask the user to select one file or to click Cancel to browse.
    filePath = 
      QInputDialog::getItem(this, tr("Select a polymer chemistry definition "
				       "or click Cancel to browse"),
			     tr("Polymer chemistry definition:"), 
			     stringList, 0, false, &ok);
  
    if (!ok || filePath.isEmpty())
      {
	// We could not get the filePath. Try by giving the user the
	// opportunity to select a file from the filesystem.
	filePath = 
	  QFileDialog::getOpenFileName(this, tr("Open definition file"),
					QDir::homePath(),
					tr("XML files(*.xml *.XML)"));
      
	if(filePath.isNull() || filePath.isEmpty())
	  return;
	
	if(!QFile::exists(filePath))
	  {
	    QMessageBox::warning 
	     (this, 
	       tr("massXpert"),
	       tr("File(%1) not found.")
	       .arg(filePath),
	       QMessageBox::Ok);
	    
	    return;
	  }
      }
  
    // Open a calculator window without any polymer chemistry definition.

    CalculatorWnd *calculatorWnd = new CalculatorWnd(filePath);

    // At this time we have a calculator window that is fully
    // initialized and functional. We can add its pointer to the list of
    // such windows that is stored in the application object.
    application->calculatorWndList()->append(calculatorWnd);
  }


  void 
  MainWindow::openSequence()
  {
    QString filePath;
    QString name;
  
    filePath = 
      QFileDialog::getOpenFileName(this, tr("Open sequence file"),
				    QDir::homePath(),
				    tr("Sequence files(*.mxp *.mXp *.MXP)"
				       ";; All files(*.*)"));
      
    if (filePath.isNull() || filePath.isEmpty())
      return;

    if (!QFile::exists(filePath))
      {
	QMessageBox::warning 
	 (this, 
	   tr("massXpert"),
	   tr("File(%1) not found.")
	   .arg(filePath),
	   QMessageBox::Ok);
	
	return;
      }
    
    SequenceEditorWnd *sequenceEditorWnd = new SequenceEditorWnd();
    
    if (!sequenceEditorWnd->openSequence(filePath))
      {
	QMessageBox::warning 
	 (this, 
	   tr("massXpert"),
	   tr("%1@%2\n"
	       "Failed to open sequence in the editor window.")
	   .arg(__FILE__)
	   .arg(__LINE__),
	   QMessageBox::Ok);
      
	sequenceEditorWnd->close();
      
	return;
      }
  
    // At this time we have a polymer chemistry definition window that
    // is fully initialized and functional. We can add its pointer to
    // the list of such windows that is stored in the application
    // object.
    Application *application = static_cast<Application *>(qApp);
    application->sequenceEditorWndList()->append(sequenceEditorWnd);
  }


  void 
  MainWindow::openSampleSequence()
  {
    // This menu is served to the user so that he can get immediate
    // access to the sample sequences even without knowing what the
    // structure of the data is on the filesystem. In particular, the
    // user might not be able to find the sample sequences on a UNIX
    // system or in a MacOSX bundle.

    Application *application = static_cast<Application *>(qApp);
    ConfigSettings *configurationSettings = application->configSettings();

    QStringList stringList;
    QString filePath;
    QString name;
    
    QDir systemDataDir(configurationSettings->systemDataDir() +
			QDir::separator() + "pol-seqs");
    
    QStringList filters;
    filters << "*.mxp" << "*.MXP";

    systemDataDir.setNameFilters(filters);

    QFileInfoList infoList = 
      systemDataDir.entryInfoList(filters, QDir::Files|QDir::Readable);
    
    for (int iter = 0; iter < infoList.size(); ++iter)
      {
	QFileInfo fileInfo(infoList.at(iter));
	stringList << fileInfo.absoluteFilePath();
      }
    
    bool ok = false;
    
    // Ask the user to select one file or to click Cancel to browse.
    filePath = 
      QInputDialog::getItem(this, tr("Select a sample sequence file "
				       "or click Cancel to browse"),
			     tr("Sample sequence file:"), 
			     stringList, 0, false, &ok);
  
    if (!ok || filePath.isEmpty())
      {
	// We could not get the filePath. Try by giving the user the
	// opportunity to select a file from the filesystem.
	filePath = 
	  QFileDialog::getOpenFileName(this, tr("Open sequence file"),
					QDir::homePath(),
					tr("mxp files(*.mxp *.MXP)"
					   ";; All files(*.*)"));
      }
    
    if (filePath.isNull() || filePath.isEmpty())
      return;
    
    if (!QFile::exists(filePath))
      {
	QMessageBox::warning 
	 (this, 
	   tr("massXpert"),
	   tr("File %1 not found.")
	   .arg(filePath),
	   QMessageBox::Ok);
	
	return;
      }
    
    SequenceEditorWnd *sequenceEditorWnd = new SequenceEditorWnd();
  
    if (!sequenceEditorWnd->openSequence(filePath))
      {
	QMessageBox::warning 
	 (this, 
	   tr("massXpert"),
	   tr("%1@%2\n"
	       "Failed to open sequence in the editor window.")
	   .arg(__FILE__)
	   .arg(__LINE__),
	   QMessageBox::Ok);
      
	sequenceEditorWnd->close();
      
	return;
      }
  
    // At this time we have a polymer chemistry definition window that
    // is fully initialized and functional. We can add its pointer to
    // the list of such windows that is stored in the application
    // object.
    application->sequenceEditorWndList()->append(sequenceEditorWnd);
  }


  void 
  MainWindow::newSequence()
  {
    Application *application = static_cast<Application *>(qApp);
    QStringList stringList;


    // The user should first tell of what polymer chemistry definition
    // the sequence should be. Let him choose amongst the available
    // polymer chemistry definitions:
    // Get the polchemdef catalogue contents.

    application->polChemDefCatStringList(stringList) ; 

    // Ask the user to select one file or to click Cancel to browse.
    bool ok;
    QString filePath = 
      QInputDialog::getItem(this, tr("Select a polymer chemistry definition "
				       "or click Cancel to browse"),
			     tr("Polymer chemistry definition:"), 
			     stringList, 0, false, &ok);
  
    if (!ok || filePath.isEmpty())
      {
	// We could not get the filePath. Try by giving the user the
	// opportunity to select a file from the filesystem.
	filePath = 
	  QFileDialog::getOpenFileName(this, tr("Open definition file"),
					QDir::homePath(),
					tr("XML files(*.xml *.XML)"
					   ";; All files(*.*)"));
	
	if(filePath.isNull() || filePath.isEmpty())
	  return;
	
	if(!QFile::exists(filePath))
	  {
	    QMessageBox::warning 
	     (this, 
	       tr("massXpert"),
	       tr("File(%1) not found.")
	       .arg(filePath),
	       QMessageBox::Ok);

	    return;
	  }
      }

    // At this stage we should have a proper polymer chemistry
    // definition filePath.

    SequenceEditorWnd *sequenceEditorWnd = new SequenceEditorWnd();

    if (!sequenceEditorWnd->newSequence(filePath))
      {
	QMessageBox::warning 
	 (this, 
	   tr("massXpert"),
	   tr("%1@%2\n"
	       "Failed to create sequence in the editor window.")
	   .arg(__FILE__)
	   .arg(__LINE__),
	   QMessageBox::Ok);
      
	sequenceEditorWnd->close();
      
	return;
      }

    // At this time we have a polymer chemistry definition window that
    // is fully initialized and functional. We can add its pointer to
    // the list of such windows that is stored in the application
    // object.
    application->sequenceEditorWndList()->append(sequenceEditorWnd);
  }


  void 
  MainWindow::mzLab()
  {

    ////////////// EXPERIMENTAL FEATURE ///////////////////
//     int ret = QMessageBox::warning(this,
// 				    tr("massXpert: mzLab"),
// 				    tr("This feature is experimental and "
// 					"only partially implemented. See "
// 					"the user manual for details.\n"
// 					"You might experiment crashes.\n"
// 					"Please, save your work NOW.\n\n"
// 					"Continue with experimental mzLab?"),
// 				    QMessageBox::Ok | QMessageBox::Cancel);
//     if (ret == QMessageBox::Cancel)
//       return;
    
    Application *application = static_cast<Application *>(qApp);
    QStringList stringList;
    QString filePath;
    bool ok;

    // Make sure that a polymer chemistry definition is offered to
    // open to the caller.
  
    // Get the polchemdef catalogue contents.
    application->polChemDefCatStringList(stringList) ; 

    // Ask the user to select one file or to click Cancel to browse.
    filePath = 
      QInputDialog::getItem(this, tr("Select a polymer chemistry definition "
				       "or click Cancel to browse"),
			     tr("Polymer chemistry definition:"), 
			     stringList, 0, false, &ok);
  
    if (!ok || filePath.isEmpty())
      {
	// We could not get the filePath. Try by giving the user the
	// opportunity to select a file from the filesystem.
	filePath = 
	  QFileDialog::getOpenFileName(this, tr("Open definition file"),
					QDir::homePath(),
					tr("XML files(*.xml *.XML)"));
      
	if(filePath.isNull() || filePath.isEmpty())
	  return;
	
	if(!QFile::exists(filePath))
	  {
	    QMessageBox::warning 
	     (this, 
	       tr("massXpert"),
	       tr("File(%1) not found.")
	       .arg(filePath),
	       QMessageBox::Ok);
	  
	    return;
	  }
      }
  
    MzLabWnd *mzLabWnd = new MzLabWnd(filePath);
    
    mzLabWnd->show();

    application->mzLabWndList()->append(mzLabWnd);
  }
  

  void 
  MainWindow::about()
  {
    AboutDlg *dlg = new AboutDlg(this);
  
    dlg->show();
  }


  void 
  MainWindow::createActions()
  {
    // File/Exit
    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcut(tr("Ctrl+Q"));
    exitAct->setStatusTip(tr("Exit the application"));
    connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));

    // XpertDef Menu/Open PolChemDef
    openPolChemDefAct = new QAction(QIcon(":/images/open.png"), 
				     tr("&Open..."), this); 
    openPolChemDefAct->setShortcut(tr("Ctrl+O"));
    openPolChemDefAct->setStatusTip 
     (tr("Open an existing polymer chemistry definition file"));
    connect(openPolChemDefAct, SIGNAL(triggered()), 
	     this, SLOT(openPolChemDef()));
  

    // XpertDef Menu/New PolChemDef
    newPolChemDefAct = new QAction(QIcon(":/images/new.png"), 
				    tr("&New..."), this);
    newPolChemDefAct->setShortcut(tr("Ctrl+N"));
    newPolChemDefAct->setStatusTip 
     (tr("Create a new polymer chemistry definition file"));
    connect(newPolChemDefAct, SIGNAL(triggered()), 
	     this, SLOT(newPolChemDef()));
  

    // XpertCalc Menu/New Calculator
    newCalculatorAct = 
      new QAction(QIcon(":/images/new.png"), 
		   tr("&Open calculator"), this);
    newCalculatorAct->setShortcut(tr("Ctrl+O"));
    newCalculatorAct->setStatusTip 
     (tr("Open a new calculator window"));
    connect(newCalculatorAct, SIGNAL(triggered()), 
	     this, SLOT(newCalculator()));
  

    // XpertEdit Menu/Open Sample Sequence
    openSampleSequenceAct = 
      new QAction(QIcon(":/images/new.png"), 
		   tr("&Open sample sequence"), this);
    openSampleSequenceAct->setShortcut(tr("Ctrl+O,S"));
    openSampleSequenceAct->setStatusTip 
     (tr("Open an sample polymer sequence file"));
    connect(openSampleSequenceAct, SIGNAL(triggered()), 
	     this, SLOT(openSampleSequence()));

    openSequenceAct = 
      new QAction(QIcon(":/images/new.png"), 
		   tr("&Open sequence"), this);
    openSequenceAct->setShortcut(tr("Ctrl+O"));
    openSequenceAct->setStatusTip 
     (tr("Open an existing polymer sequence file"));
    connect(openSequenceAct, SIGNAL(triggered()), 
	     this, SLOT(openSequence()));

    // XpertEdit Menu/New Sequence
    newSequenceAct = 
      new QAction(QIcon(":/images/new.png"), 
		   tr("&New sequence"), this);
    newSequenceAct->setShortcut(tr("Ctrl+N"));
    newSequenceAct->setStatusTip 
     (tr("Create a new polymer sequence file"));
    connect(newSequenceAct, SIGNAL(triggered()), 
	     this, SLOT(newSequence()));
  
    // XpertMiner Menu/mz Lab
    mzLabAct = 
      new QAction(QIcon(":/images/new.png"), 
		   tr("&mz Lab"), this);
    mzLabAct->setShortcut(tr("Ctrl+M,Z"));
    mzLabAct->setStatusTip 
     (tr("Open a new mz lab window"));
    connect(mzLabAct, SIGNAL(triggered()), 
	     this, SLOT(mzLab()));
    
    // Help
    aboutAct = new QAction(tr("&About"), this);
    aboutAct->setShortcut(tr("Ctrl+H"));
    aboutAct->setStatusTip(tr("Show the application's About box"));
    connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));

    aboutQtAct = new QAction(tr("About &Qt"), this);
    aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
    connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
  }

  void 
  MainWindow::createMenus()
  {
    fileMenu = menuBar()->addMenu(tr("&File"));
    fileMenu->addAction(exitAct);

    menuBar()->addSeparator();

    // XpertDef
    xpertDefMenu = menuBar()->addMenu(tr("Xpert&Def"));
    xpertDefMenu->addAction(openPolChemDefAct);
    xpertDefMenu->addAction(newPolChemDefAct);

    menuBar()->addSeparator();

    // XpertCalc
    xpertCalcMenu = menuBar()->addMenu(tr("Xpert&Calc"));
    xpertCalcMenu->addAction(newCalculatorAct);

    menuBar()->addSeparator();

    // XpertEdit
    xpertEditMenu = menuBar()->addMenu(tr("Xpert&Edit"));
    xpertEditMenu->addAction(openSequenceAct);
    xpertEditMenu->addAction(openSampleSequenceAct);
    xpertEditMenu->addAction(newSequenceAct);

    menuBar()->addSeparator();

    // XpertMiner
    xpertMinerMenu = menuBar()->addMenu(tr("Xpert&Miner"));
    xpertMinerMenu->addAction(mzLabAct);

    menuBar()->addSeparator();

    // Plugins
    pluginMenu = menuBar()->addMenu(tr("&Plugins"));
  
    menuBar()->addSeparator();

    // help
    helpMenu = menuBar()->addMenu(tr("&Help"));
    helpMenu->addAction(aboutAct);
    helpMenu->addAction(aboutQtAct);
  }


  void
  MainWindow::createStatusBar()
  {
    statusBar()->showMessage(tr("Ready"));
  }






  ////////////////// BEGIN PLUGIN STUFF //////////////////////
  ////////////////// BEGIN PLUGIN STUFF //////////////////////

  void 
  MainWindow::loadPlugins()
  {
    Application *application = static_cast<Application *>(qApp);

    // SYSTEM PLUGIN DIR FIRST
    QString pluginDir = application->configSettings()->systemPluginDir();
    
    QDir dir = QDir(pluginDir);
  
    if (dir.exists())
      {
	foreach(QString fileName, dir.entryList(QDir::Files))
	  {
	    QPluginLoader loader(dir.absoluteFilePath(fileName));
	    QObject *plugin = loader.instance();
	  
	    if (plugin) 
	      {
		populatePluginMenus(plugin);
		// qDebug() << "Loaded plugin:" << fileName;
	      }
	  }
      }
  
    // USER PLUGIN DIR SECOND
    pluginDir = application->configSettings()->userPluginDir();
  
    dir = QDir(pluginDir);
  
    if (dir.exists())
      {
	foreach(QString fileName, dir.entryList(QDir::Files))
	  {
	    QPluginLoader loader(dir.absoluteFilePath(fileName));
	    QObject *plugin = loader.instance();
      
	    if (plugin) 
	      {
		populatePluginMenus(plugin);
		// qDebug() << "Loaded plugin:" << fileName;
	      }
	  }
      }
  
    pluginMenu->setEnabled(!pluginMenu->actions().isEmpty());
  }


  void 
  MainWindow::populatePluginMenus(QObject *plugin)
  {
    SeqToolsPluginInterface *iSeqTools = 
      qobject_cast<SeqToolsPluginInterface *>(plugin);

    if (iSeqTools)
      {
	addToMenu(plugin, iSeqTools->tools(), 
		   pluginMenu, SLOT(seqToolsPlugin()));
      }

    MassListSorterPluginInterface *iMassListSorter = 
      qobject_cast<MassListSorterPluginInterface *>(plugin);

    if (iMassListSorter)
      {
	addToMenu(plugin, iMassListSorter->tools(), 
		   pluginMenu, SLOT(massListSorterPlugin()));
      }

    NumeralsLocaleConverterPluginInterface *iNumeralsLocaleConverter = 
      qobject_cast<NumeralsLocaleConverterPluginInterface *>(plugin);

    if (iNumeralsLocaleConverter)
      {
	addToMenu(plugin, iNumeralsLocaleConverter->tools(), 
		   pluginMenu, SLOT(numeralsLocaleConverterPlugin()));
      }
  }


  void 
  MainWindow::addToMenu(QObject *plugin, const QStringList &texts, 
			 QMenu *menu, const char *member)
  {
    foreach(QString text, texts) 
      {
	QAction *action = new QAction(text, plugin);
	connect(action, SIGNAL(triggered()), this, member);
	menu->addAction(action);
      }
  }


  void
  MainWindow::seqToolsPlugin()
  {
    QAction *action = qobject_cast<QAction *>(sender());
  
    SeqToolsPluginInterface *iSeqTools = 
      qobject_cast<SeqToolsPluginInterface *>(action->parent());

    iSeqTools->toolAtWork(action->text(), this);
  }


  void
  MainWindow::massListSorterPlugin()
  {
    QAction *action = qobject_cast<QAction *>(sender());
  
    MassListSorterPluginInterface *iMassListSorter = 
      qobject_cast<MassListSorterPluginInterface *>(action->parent());

    iMassListSorter->toolAtWork(action->text(), this);
  }


  void
  MainWindow::numeralsLocaleConverterPlugin()
  {
    QAction *action = qobject_cast<QAction *>(sender());
  
    NumeralsLocaleConverterPluginInterface *iNumeralsLocaleConverter = 
      qobject_cast<NumeralsLocaleConverterPluginInterface *> 
     (action->parent());

    iNumeralsLocaleConverter->toolAtWork(action->text(), this);
  }





  ////////////////// END PLUGIN STUFF //////////////////////



  void 
  MainWindow::closeEvent(QCloseEvent *event)
  {
    Application* application = static_cast<Application*>(qApp);

    if (!application->prepareShutdown())
      event->ignore();  
    else
      {
	QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
	settings.setValue("main_window/geometry", saveGeometry());
      
	emit aboutToClose();

	event->accept();
      }

    return;
  }

} // namespace massXpert
