/******************************************************************************
* NOMAD -  Nonlinear Optimization for Mixed vAriables and Derivatives.        *
* Copyright (C) 2001-2004  Gilles Couture - Ecole Polytechnique,              *
*                          Charles Audet - Ecole Polytechnique,               *
*		           John Dennis - Rice University.                     *
*                                                                             *
* Author: Gilles Couture                                                      *
* École Polytechnique de Montréal - GERAD                                     *
* C.P. 6079, Succ. Centre-ville, Montréal (Québec) H3C 3A7 Canada             *
* Tel:  (514) 340-6053 x6928                                                  *
* Email: Gilles.Couture@gerad.ca                                              *
* Fax: (514) 340-5665                                                         *
*                                                                             *
* 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. You can find the              *
* GNU General Public License at:                                              *
*             http://www.gnu.org/copyleft/gpl.html                            *
*                                                                             *
* 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, write to the Free Software                 *
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. *
*                                                                             *
* You can find information on the NOMAD software at:                          *
*             http://www.gerad.ca/NOMAD/                                      *
******************************************************************************/

// mainapplication.cpp -- class methods for MainApplication.

#include <fstream>
#include <iostream>
#include <QAction>
#include <QApplication>
#include <QDialog>
#include <QFile>
#include <QFileDialog>
#include <QGroupBox>
#include <QLabel>
#include <QLayout>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QString>
#include <QValidator>
using namespace std;
#include "descriptionwindow.h"
#include "mainapplication.h"
#include "parameterwindow.h"
#include "preferencewindow.h"
#include "resultwindow.h"
#include "solver.h"

// Private methods:
// ****************************************************************************

// This method allows users to do a new run, either with the same or with a new
// problem.
void MainApplication::newRun()
{
  // First the problem outputs are cleared.
  result_win->clearOutputs();
  // The Solver object is destroyed, then recreated.
  delete solver;
  solver = new Solver(description_win->getDescriptionPtr(),
		      parameter_win->getParametersPtr());
}

// This method checks if the problem parameters are correct, then tells Solver
// to solve the current problem.
void MainApplication::solveProblem()
{
  // We check if there's an error with the parameters file. If everything is
  // ok...
  if (parameter_win->readyToSolve())
    {
      // First the fields in the parameters' window are sent to Parameters.
      parameter_win->sendParameters();
      // The preferences are sent to Solver.
      preference_win->capture(*solver, description_win->getName());
      // The starting point is evaluated. If everything is ok ...
      solver->evaluateStartingPoint();
      // 'fout' is the results file for the current run.
      ofstream fout(description_win->getResultsFile());
      // The problem description is written to file.
      description_win->showDescription(fout);
      // The parameters are written to file.
      parameter_win->showParameters(fout);
      // The problem input is written in 'fout'.
      solver->showInput(fout);
      // The problem is solved by the optimization module.
      solver->solveProblem(fout);
      // The solution is displayed on the GUI.
      result_win->outputSolution(description_win->getDimension(),
				description_win->getGeneralConstraintsNumber(),
				 *solver);
      // The results file is closed.
      fout.close();
      // 'Solver' is told that the run is over (the caches will be closed).
      solver->endProgram();
      // The 'Preferences' object must be alerted that the run is over.
      preference_win->endOfRun();
      // This boolean variable is now true.
      run_executed = true;
    }
  else   // We alert the user that something's wrong with the parameters.
    {
      switch(QMessageBox::critical(this, "NOMAD",
				   "Something is wrong with the parameters!\n"
				   "Please correct before running NOMAD.\n"
				   "See the program documentation in the\n"
				   "DOC directory."))
	{
	  case QMessageBox::Ok : close();
				 break;
	}
    }
}

// Public methods:
// ****************************************************************************

// Class constructor. In the QWidget constructor we have the following things:
// - The parent object, which is the MainWindow object (see "mainwindow.cpp").
MainApplication::MainApplication(QWidget * parent, int argc, char * argv[])
  : QWidget(parent)
{
  // This boolean starts out false.
  run_executed = false;
  // The DescriptionWindow object is created (but not shown at the start).
  if (argc > 1)
    // A Description file was given in the command line.
    description_win = new DescriptionWindow(argv[1]);
  else
    // No Description file.
    description_win = new DescriptionWindow();

  // The PreferenceWindow object is created, but not shown.
  preference_win = new PreferenceWindow(this);

  // The Solver object is null at first: it is NOMAD's controller.
  solver = NULL;

  /************/
  /* Menu bar */
  /************/

  // In this section we build the items that will be on the menu bar.
  // The problem description menu:

  new_act = new QAction(tr("&New"), this);
  new_act->setShortcut(tr("Ctrl+N"));
  // new_act->setStatusTip(tr("Create a new description file"));
  connect(new_act, SIGNAL(triggered()), this, SLOT(newDescription()));

  open_act = new QAction(tr("&Open"), this);
  open_act->setShortcut(tr("Ctrl+O"));
  // open_act->setStatusTip(tr("Open a description file"));
  connect(open_act, SIGNAL(triggered()), this, SLOT(openDescription()));

  edit_act = new QAction(tr("&Edit"), this);
  edit_act->setShortcut(tr("Ctrl+E"));
  // edit_act->setStatusTip(tr("Edit a description file"));
  connect(edit_act, SIGNAL(triggered()), this, SLOT(editDescription()));

  quit_act = new QAction(tr("&Quit"), this);
  quit_act->setShortcut(tr("Ctrl+Q"));
  // quit_act->setStatusTip(tr("Quit program"));
  connect(quit_act, SIGNAL(triggered()), this, SLOT(quitProgram()));

  description_m = new QMenu(tr("&Description"), this);
  description_m->addAction(new_act);
  description_m->addAction(open_act);
  description_m->addAction(edit_act);
  description_m->addSeparator();
  description_m->addAction(quit_act);

  // The Parameters menu:

  reset_act = new QAction(tr("Reset"), this);
  connect(reset_act, SIGNAL(triggered()), this, SLOT(resetParameters()));

  load_act = new QAction(tr("&Load"), this);
  load_act->setShortcut(tr("Ctrl+L"));
  connect(load_act, SIGNAL(triggered()), this, SLOT(loadParameters()));

  save_act = new QAction(tr("&Save"), this);
  save_act->setShortcut(tr("Ctrl+S"));
  connect(save_act, SIGNAL(triggered()), this, SLOT(save()));

  save_as_act = new QAction(tr("&Save as"), this);
  connect(save_as_act, SIGNAL(triggered()), this, SLOT(saveAs()));

  parameter_m = new QMenu(tr("&Parameters"), this);
  parameter_m->addAction(reset_act);
  parameter_m->addSeparator();
  parameter_m->addAction(load_act);
  parameter_m->addSeparator();
  parameter_m->addAction(save_act);
  parameter_m->addAction(save_as_act);

  // A signal is sent when the parameter menu is about to be shown to the user.
  connect(parameter_m, SIGNAL(aboutToShow()),
	  this, SLOT(parameterMenuAboutToShow()));

  // The Preferences menu:

  preferences_act = new QAction(tr("Edit"), this);
  preferences_act->setShortcut(tr("Ctrl+P"));
  connect(preferences_act, SIGNAL(triggered()), this, SLOT(editPreferences()));

  preferences_m = new QMenu(tr("&Preferences"), this);
  preferences_m->addAction(preferences_act);

  // The Run menu:

  solve_act = new QAction(tr("Solve"), this);
  connect(solve_act, SIGNAL(triggered()), this, SLOT(solve()));

  continue_act = new QAction(tr("Continue"), this);
  connect(continue_act, SIGNAL(triggered()), this, SLOT(continueRun()));

  run_m = new QMenu(tr("&Run"), this);
  run_m->addAction(solve_act);
  run_m->addSeparator();
  run_m->addAction(continue_act);

  // A signal is sent when the run menu is about to be shown to the user.
  connect(run_m, SIGNAL(aboutToShow()), this, SLOT(runMenuAboutToShow()));

  // The Results menu.

  view_act = new QAction(tr("View"), this);
  connect(view_act, SIGNAL(triggered()), this, SLOT(viewResults()));

  results_m = new QMenu(tr("Results"), this);
  results_m->addAction(view_act);

  // We put the items on the menu bar.
  menu = new QMenuBar(this);
  menu->addMenu(description_m);
  menu->addMenu(parameter_m);
  menu->addMenu(preferences_m);
  menu->addMenu(run_m);
  menu->addMenu(results_m);

  // A 1x1 grid is created. Its parent is "this" object (the "main" widget).
  grid = new QGridLayout(this);
  // The grid has to be aware of the menu bar to place its objects properly.
  grid->setMenuBar(menu);

  /*************/
  /* LEFT SIDE */
  /*************/

  // The ParameterWindow object is created and put on the grid..
  if (argc > 2)
    {
      // A Parameters file was given in the command line.
      parameter_win = new ParameterWindow
	(this, description_win->getSurrogateUse(),
	 description_win->getGeneralConstraintsNumber(), argv[2]);
    }
  else
    // No Parameters file.
    parameter_win = new ParameterWindow(this, false, 0);
  // grid->addMultiCellWidget(parameter_win, 0, 20, 0, 3);
  grid->addWidget(parameter_win, 0, 0, 21, 4);

  // Vertical separator between the left and the right side.
  QGroupBox * separator = new QGroupBox(this);
  separator->setBackgroundRole(QPalette::Background);
  separator->setMinimumWidth(15);
  separator->setMaximumWidth(15);
  // grid->addMultiCellWidget(separator, 0, 20, 4, 4);
  grid->addWidget(separator, 0, 4, 21, 1);

  /**************/
  /* RIGHT SIDE */
  /**************/

  // The ResultWindow object is created and put on the grid.
  result_win = new ResultWindow(this);
  // grid->addMultiCellWidget(result_win, 0, 20, 5, 8);
  grid->addWidget(result_win, 0, 5, 21, 4);

  // We tell the application to resize itself.
  resize(minimumSizeHint());
}

// Destructor. All child objects are destroyed automatically.
MainApplication::~MainApplication()
{
  delete description_win;
  delete parameter_win;
  delete preference_win;
  delete result_win;
  delete solver;
}

//*****************************************************************************
// Private slots: These methods are called when an event happens in the GUI
//                (the user clicks a button for example).
//*****************************************************************************

// This method allows users to continue a run.
void MainApplication::continueRun()
{
  // First the problem outputs are cleared.
  result_win->clearOutputs();
  // We tell 'Solver' that there's a new run.
  solver->continueRun();
  // The problem is solved.
  solveProblem();
}

// This method lets the user edit a problem file in the problem dialog window.
void MainApplication::editDescription()
{
  description_win->load();
}

// This method lets the user edit the program preferences.
void MainApplication::editPreferences()
{
  preference_win->show();
}

// This method opens a file dialog window, so the user can choose a parameters
// file.
void MainApplication::loadParameters()
{
  // The function 'getOpenFileName(...)' won't let the user choose a file
  // name that doesn't exist.
  QString file = QFileDialog::getOpenFileName(this);
  // The parameters file is loaded.
  if (file != NULL)
    parameter_win->load(description_win->getSurrogateUse(),
			description_win->getGeneralConstraintsNumber(),
			file.toLatin1());
}

// This method creates a new problem description.
void MainApplication::newDescription()
{
  description_win->newDescription();
}

// This method opens a file dialog window, so the user can choose a problem
// file.
void MainApplication::openDescription()
{
  // The function 'getOpenFileName(...)' won't let the user choose a file
  // name that doesn't exist.
  QString file = QFileDialog::getOpenFileName(this);
  // The problem file is loaded.
  if (file != NULL)
    description_win->load(file.toLatin1());
}

// The parameter menu is about to be shown: we check if the 'Save' and
// 'Save as' menu items must be enabled or disabled.
void MainApplication::parameterMenuAboutToShow()
{
  // The items are enabled or disabled according to 'enable_items'.
  bool enable_items = parameter_win->enable();
  save_act->setEnabled(enable_items);
  save_as_act->setEnabled(enable_items);
}

// This method is called when the user pushes the 'Quit' button.
void MainApplication::quitProgram()
{
  // Closing the parent widget terminates the program.
  parentWidget()->close();
}

// This method resets the problem parameters to defaults.
void MainApplication::resetParameters()
{
  parameter_win->reset();
}

// The run menu is about to be shown: we check if the 'Solve' and 'Continue'
// menu items should be enabled or disabled.
void MainApplication::runMenuAboutToShow()
{
  // The 'Solve' menu item is enabled if these booleans are true, disabled
  // otherwise.
  if ((description_win->readyToSolve()) && (parameter_win->readyToSolve()))
    solve_act->setEnabled(true);
  else
    solve_act->setEnabled(false);
  // The 'Continue' menu item is enabled or disabled according to the
  // 'run_executed' variable.
  continue_act->setEnabled(run_executed);
}

// The message is forwarded to the 'ParameterWindow' object.
void MainApplication::save()
{
  parameter_win->save();
}

// The message is forwarded to the 'ParameterWindow' object.
void MainApplication::saveAs()
{
  parameter_win->saveAs();
}

// The user wants to solve the problem.
void MainApplication::solve()
{
  // The problem arrays are built: starting point, simple bounds, scales. This
  // condition checks if the starting point is inside the bounds.
  if (description_win->buildArrays())
    {
      // This other condition checks the state of the description file.
      if (description_win->readyToSolve())
	{
	  // This function clears the outputs and creates the 'Solver' object.
	  newRun();
	  // The problem is solved.
	  solveProblem();
	}
      else   // The description file is incomplete: the user is alerted.
	{
	  switch(QMessageBox::critical(this, "NOMAD",
				       "The description file is incomplete!\n"
				   "Please complete it before running NOMAD.\n"
				       "See the program documentation in the\n"
				       "DOC directory."))
	    {
	      case QMessageBox::Ok : close();
		                     break;
	    }
	}
    }
  else   // The starting point is outside the bounds.
    {
      switch(QMessageBox::critical(this, "NOMAD",
				  "The starting point is outside the bounds!\n"
				   "Please correct before running NOMAD.\n"
				   "See the program documentation in the\n"
				   "DOC directory."))
	    {
	      case QMessageBox::Ok : close();
				     break;
	    }
    }
}

// The message is forwarded to the ResultWindow object.
void MainApplication::viewResults()
{
  result_win->viewResults(description_win->getResultsFile());
}
