/****************************************************************************
**
** Modifications by Romain Gaucher for Modelib
**   >> http://neuroo.kouette.com/modelib
**
** One line to give the program's name and a brief idea of what it does.
** Copyright (C) 2005      Romain Gaucher, Modelib::Project port
** Copyright (C) 2004-2005 Trolltech AS.
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 2 of the License, or (at your option)
** any later version.
**
** This program is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
** FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License along with
** this program; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
**
****************************************************************************/

#include <QtGui>

#include "mainwindow.h"
#include "resultviewer.h"

#include <iostream>
using namespace std;


MainWindow::MainWindow()
{
    init();
	setWindowTitle(tr("LPditor :: Modelib project"));
	setCurrentFile("");
}

MainWindow::MainWindow(const QString &fileName)
{
    init();
    loadFile(fileName);
}

//! overload of the QMainWindow::closeEvent (write settings or not)
void MainWindow::closeEvent(QCloseEvent *event)
{
	if (maybeSave()) {
        writeSettings();
        event->accept();
    } else {
        event->ignore();
    }
}

//! Position the new window if new file
void MainWindow::newFile()
{
    MainWindow *other = new MainWindow;
    other->move(x() + 40, y() + 40);
    other->show();
}

//! Open a file
void MainWindow::open()
{
    QString fileName = QFileDialog::getOpenFileName(this);
    if (!fileName.isEmpty()) {
        MainWindow *existing = findMainWindow(fileName);
        // if the file is currently open, show the window
		if (existing) {
            existing->show();
            existing->raise();
            existing->activateWindow();
            return;
        }

        if (isUntitled && textEdit->document()->isEmpty()
                && !isWindowModified()) {
            loadFile(fileName);
        } else {
            MainWindow *other = new MainWindow(fileName);
            if (other->isUntitled) {
                delete other;
                return;
            }
            other->move(x() + 40, y() + 40);
            other->show();
        }
    }
}

//! Call function for saving the file
bool MainWindow::save()
{
    if (isUntitled) {
        return saveAs();
    } else {
        return saveFile(curFile);
    }
}

bool MainWindow::saveAs()
{
	// read the path where you prefer to put it :-)
	QSettings settings("Modelib", "LPditor");
	QString fP = settings.value("favoritePath").toString();
	QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"),
                                                    fP + "/" + curFile,"LP File (*.lp *.txt *.*)");
    if (fileName.isEmpty())
        return false;

    return saveFile(fileName);
}

//! About dialog
void MainWindow::about()
{
   QMessageBox::about(this, tr("About LPditor"),
            tr("The <b>LPditor</b> soft is a tool provided by the Modelib's authors. It allows you to\
			write easily LP files.<br><br>As it's still in development, there is no many features, but we project to do:\
			<ul><li>Auto-completion</li>\
			<li>The integration of Modelib and solvers</li>\
			<li>Others facilities as many format (MPS, XML...) etc.</li></ul>\
			For any question: <ul><li>romain.gaucher@gmail.com</li><li>quentin.lequy@poste.isima.fr</li></ul><br>\
			Don't forget our website: <a href=\"http://neuroo.kouette.com/modelib\">http://neuroo.kouette.com/modelib</a>"));
}

//! About authors...
void MainWindow::aboutAuthors()
{
   QMessageBox::about(this, tr("About authors of Modelib"),
            tr("We are two students in an ingeneering school: ISIMA.<br>We curretly are in our last year in informatics,  calculus and modelisation.<br><br>For more information about ISIMA: <a href=\"http://www.isima.fr\">http://www.isima.fr</a><br><br><ul><li>romain.gaucher@gmail.com</li><li>quentin.lequy@poste.isima.fr</li></ul>"));
}

//! A document was modified if the title contains *
void MainWindow::documentWasModified()
{
    if (windowTitle().contains('*'))
		setWindowModified(true);
}

//! Initialisation process
void MainWindow::init()
{
    setAttribute(Qt::WA_DeleteOnClose);
	// construct the highlighter and the QTextEdit area
    highlighter = new Highlighter();
	textEdit = new QTextEdit(this);
	textEdit->setUndoRedoEnabled(true);
	setupHighlight();

	isUntitled = true;
	setCentralWidget(textEdit);

    // build the 'menus'
    createActions();
    createMenus();
    createToolBars();
    createStatusBar();

	readPlugins();
    readSettings();

    connect(textEdit->document(), SIGNAL(contentsChanged()),this, SLOT(documentWasModified()));
}

//! Setting up the Highlighter with saved values
void MainWindow::setupHighlight()
{
	// Get parameters
	QSettings settings("Modelib", "LPditor");
	QString fontFamily = settings.value("fontFamily").toString();
	if (fontFamily.isNull())
		fontFamily = "Helvetica";
	int fontSize = settings.value("fontSize").toInt();
	if (fontSize < 8 || fontSize > 72)
		fontSize = 10;
	QString colorOperator = settings.value("colorOperator").toString();
	if (colorOperator.isNull())
		colorOperator = "#C08646";
	QString colorName = settings.value("colorName").toString();
	if (colorName.isNull())
		colorName = "#308b2b";
	QString colorFunction = settings.value("colorFunction").toString();
	if (colorFunction.isNull())
		colorFunction = "#2266FF";
	QString colorComment = settings.value("colorComment").toString();
	if (colorComment.isNull())
		colorComment = "#AAAAAA";

    // default format (specs: font and size)
	QTextCharFormat defaultFormat;
	defaultFormat.setFontFamily(fontFamily);
    defaultFormat.setFontPointSize(fontSize);

    // operator format (specs: color, regexp, font and size)
    QTextCharFormat operatorFormat = defaultFormat;
	operatorFormat.setFontFamily(fontFamily);
	operatorFormat.setForeground(QColor(colorOperator));
    highlighter->addMapping("[<|>|=|*|\\-|+|/|,]", operatorFormat);

	// name of a function or constraint or oth. (specs: color, regexp, font and size)
    QTextCharFormat nameFormat = defaultFormat;
	nameFormat.setFontFamily(fontFamily);
    nameFormat.setFontItalic(true);
    nameFormat.setForeground(QColor(colorName));
    highlighter->addMapping(".*:", nameFormat);

    // real keywords... (specs: color, regexp, font and size)
	QTextCharFormat functionFormat = defaultFormat;
	functionFormat.setFontFamily(fontFamily);
    functionFormat.setForeground(QColor(colorFunction));

	// add the keywords knonw by Modelib lp-parser
	highlighter->addMapping("(\\bmax\\b)", functionFormat);
	highlighter->addMapping("(\\bmaximize\\b)", functionFormat);
	highlighter->addMapping("(\\bmin\\b)", functionFormat);
	highlighter->addMapping("(\\bminimize\\b)", functionFormat);
	highlighter->addMapping("(\\bsubject to\\b)", functionFormat);
	highlighter->addMapping("(\\bsubject\\b)", functionFormat);
	highlighter->addMapping("(\\bsuch that\\b)", functionFormat);
	highlighter->addMapping("(\\bst\\b)", functionFormat);
	highlighter->addMapping("(\\bst.\\b)", functionFormat);
	highlighter->addMapping("(\\bs.t.\\b)", functionFormat);
	highlighter->addMapping("(\\bbounds\\b)", functionFormat);
	highlighter->addMapping("(\\bbound\\b)", functionFormat);
	highlighter->addMapping("(\\bend\\b)", functionFormat);
	highlighter->addMapping("(\\bgenerals\\b)", functionFormat);
	highlighter->addMapping("(\\bgeneral\\b)", functionFormat);
	highlighter->addMapping("(\\bgen\\b)", functionFormat);
	highlighter->addMapping("(\\bintegers\\b)", functionFormat);
	highlighter->addMapping("(\\binteger\\b)", functionFormat);
	highlighter->addMapping("(\\bbinaries\\b)", functionFormat);
	highlighter->addMapping("(\\bbinary\\b)", functionFormat);
	highlighter->addMapping("(\\bbin\\b)", functionFormat);
	highlighter->addMapping("(\\bfree\\b)", functionFormat);

	// comments  (specs: color, regexp, font and size)
    QTextCharFormat singleLineCommentFormat = defaultFormat;
	singleLineCommentFormat.setFontFamily(fontFamily);
    singleLineCommentFormat.setForeground(QColor(colorComment));
    singleLineCommentFormat.setFontItalic(true);
    highlighter->addMapping("\\\\(.*)", singleLineCommentFormat);

	// set the default font of the QTextEdit area
    textEdit->setFont(defaultFormat.font());
    textEdit->document()->setDefaultFont(defaultFormat.font());
	// replace the document by the highlighted one
    highlighter->addToDocument(textEdit->document());
}

//! Call the solver
void MainWindow::solve()
{
	QSettings settings("Modelib", "LPditor");
	QString s = settings.value("solver").toString();
	QString p = settings.value("solverPath").toString();

	static unsigned nb = 0;
	save();
	/*
	Modele model;
	QString nameSol = curFile + ".sol";
	model.InitSolver(((s == "GLPK" || s == "GLPSOL") ? GLPK " CPLEX), nameSol.data(), p.data());
	model.Solve();
	QString nameHTML = curFile.data() + (nb==0? "" : QString(nb)) + ".html";
	model.SortieHTML(nameHTML.data());
	++nb;

	ResultViewer *resultViewer = new ResultViewer(nameHTML);
	*/
}

//! Redraw QTextEdit after change settings
void MainWindow::redraw()
{
    QApplication::setOverrideCursor(Qt::WaitCursor);

	delete highlighter;
	highlighter = new Highlighter();
	setupHighlight();

		//TODO:
		// how to repaint all components of a QTextEdit widget ??
		// this should do the job
		// The problem is that QWidget::repaint doesn't repaint all the widget in-heredited
		// even with a loop over qFindWidgetChildren

		if (not textEdit->document()->isEmpty()) // not empty ?
		{
			textEdit->selectAll();
			textEdit->cut();
			textEdit->paste();
		}

    QApplication::restoreOverrideCursor();
}

//! Popup the 'Option' widget
void MainWindow::utils()
{
	options = new Options(this);
	connect (options, SIGNAL(optionsRedraw_Signal()), this, SLOT(redraw()));
}

//! Create all actions
void MainWindow::createActions()
{
    newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this);
    newAct->setShortcut(tr("Ctrl+N"));
    newAct->setStatusTip(tr("Create a new file"));
    connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));

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

    saveAct = new QAction(QIcon(":/images/save.png"), tr("&Save"), this);
    saveAct->setShortcut(tr("Ctrl+S"));
    saveAct->setStatusTip(tr("Save the document to disk"));
    connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));

    printAct = new QAction(QIcon(":/images/print.png"), tr("&Print"), this);
    printAct->setShortcut(tr("Ctrl+P"));
    printAct->setStatusTip(tr("Save the document to disk"));
    connect(printAct, SIGNAL(triggered()), this, SLOT(print()));

    saveAsAct = new QAction(tr("Save &As..."), this);
    saveAsAct->setStatusTip(tr("Save the document under a new name"));
    connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));

    closeAct = new QAction(tr("&Close"), this);
    closeAct->setShortcut(tr("Ctrl+W"));
    closeAct->setStatusTip(tr("Close this window"));
    connect(closeAct, SIGNAL(triggered()), this, SLOT(close()));

    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcut(tr("Ctrl+Q"));
    exitAct->setStatusTip(tr("Exit the application"));
    connect(exitAct, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));

    cutAct = new QAction(QIcon(":/images/cut.png"), tr("Cu&t"), this);
    cutAct->setShortcut(tr("Ctrl+X"));
    cutAct->setStatusTip(tr("Cut the current selection's contents to the "
                            "clipboard"));
    connect(cutAct, SIGNAL(triggered()), textEdit, SLOT(cut()));

    undoAct = new QAction(QIcon(":/images/undo.png"), tr("&Undo"), this);
    undoAct->setShortcut(tr("Ctrl+Z"));
    undoAct->setStatusTip(tr("Undo last actions"));
    connect(undoAct, SIGNAL(triggered()), textEdit, SLOT(undo()));

    redoAct = new QAction(QIcon(":/images/redo.png"), tr("&Redo"), this);
    redoAct->setShortcut(tr("Ctrl+R"));
    redoAct->setStatusTip(tr("Redo last actions"));
    connect(redoAct, SIGNAL(triggered()), textEdit, SLOT(redo()));

    copyAct = new QAction(QIcon(":/images/copy.png"), tr("&Copy"), this);
    copyAct->setShortcut(tr("Ctrl+C"));
    copyAct->setStatusTip(tr("Copy the current selection's contents to the "
                             "clipboard"));
    connect(copyAct, SIGNAL(triggered()), textEdit, SLOT(copy()));


    selectAct = new QAction(QIcon(":/images/select.png"), tr("&Select All"), this);
    selectAct->setShortcut(tr("Ctrl+A"));
    selectAct->setStatusTip(tr("Select all the LP document"));
    connect(selectAct, SIGNAL(triggered()), textEdit, SLOT(selectAll()));


    pasteAct = new QAction(QIcon(":/images/paste.png"), tr("&Paste"), this);
    pasteAct->setShortcut(tr("Ctrl+V"));
    pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
                              "selection"));
    connect(pasteAct, SIGNAL(triggered()), textEdit, SLOT(paste()));

    aboutAct = new QAction(tr("&About"), this);
    aboutAct->setStatusTip(tr("Show the application's About box"));
    connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));

    aboutQtAct = new QAction(tr("About Authors"), this);
    aboutQtAct->setStatusTip(tr("Show informations about the authors"));
    connect(aboutQtAct, SIGNAL(triggered()), this, SLOT(aboutAuthors()));


    aboutPluginsAct = new QAction(tr("About Plugins"), this);
    aboutPluginsAct->setStatusTip(tr("Show informations about the plugins"));
    connect(aboutPluginsAct, SIGNAL(triggered()), this, SLOT(aboutPlugins()));


    solveAct = new QAction(QIcon(":/images/run.png"), tr("&Solve"), this);
    solveAct->setShortcut(tr("Ctrl+Enter"));
    solveAct->setStatusTip(tr("Solve the current linear programm"));
    connect(solveAct, SIGNAL(triggered()), this, SLOT(solve()));

    utilsAct = new QAction(QIcon(":/images/utils.png"), tr("&Options"), this);
    utilsAct->setShortcut(tr("F2"));
	utilsAct->setStatusTip(tr("Options of the LPditor"));
    connect(utilsAct, SIGNAL(triggered()), this, SLOT(utils()));

}

void MainWindow::aboutPlugins()
{
	PluginDialog dialog(pluginsDir.path(), pluginFileNames, this);
	dialog.exec();
}

void MainWindow::setLPFile(Modele *m)
{
	ostream out;
	out << (*m);

	// clear the textEdit
	textEdit->clear();
	ostream_iterator<string> iter(out,"\n");
	for (iter = out.begin(); iter != out.end(); ++iter)
	{
		QString qs(iter->c_str());
		textEdit.append(qs);
	}
}


//! Print the document
void MainWindow::print()
{
	if (!textEdit)
		return;
#ifndef QT_NO_PRINTER
	QPrinter printer(QPrinter::HighResolution);
	printer.setFullPage(true);

	QPrintDialog *dlg = new QPrintDialog(&printer, this);
	if (dlg->exec() == QDialog::Accepted) {
		textEdit->document()->print(&printer);
	}
	delete dlg;
#endif
}

//! Build the menus from actions
void MainWindow::createMenus()
{
    fileMenu = menuBar()->addMenu(tr("&File"));
    fileMenu->addAction(newAct);
    fileMenu->addAction(openAct);
    fileMenu->addAction(saveAct);
    fileMenu->addAction(saveAsAct);
    fileMenu->addSeparator();
    fileMenu->addAction(printAct);
    fileMenu->addSeparator();
    fileMenu->addAction(closeAct);
    fileMenu->addAction(exitAct);

    editMenu = menuBar()->addMenu(tr("&Edit"));
    editMenu->addAction(undoAct);
    editMenu->addAction(redoAct);
    editMenu->addSeparator();
	editMenu->addAction(selectAct);
    editMenu->addAction(cutAct);
    editMenu->addAction(copyAct);
    editMenu->addAction(pasteAct);



	menuBar()->addSeparator();
    optionMenu = menuBar()->addMenu(tr("&Options"));
    optionMenu->addAction(utilsAct);

    menuBar()->addSeparator();
    toolMenu = menuBar()->addMenu(tr("&Solve"));
    toolMenu->addAction(solveAct);

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

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

//! Build the toolbars from actions
void MainWindow::createToolBars()
{
    fileToolBar = addToolBar(tr("File"));
    fileToolBar->addAction(newAct);
    fileToolBar->addAction(openAct);
    fileToolBar->addAction(saveAct);
	fileToolBar->addAction(printAct);

    editToolBar = addToolBar(tr("Edit"));
    editToolBar->addAction(undoAct);
    editToolBar->addAction(redoAct);
    editToolBar->addAction(cutAct);
    editToolBar->addAction(copyAct);
    editToolBar->addAction(pasteAct);

    otherBar = addToolBar(tr("Edit"));
    otherBar->addAction(utilsAct);
    otherBar->addAction(solveAct);
}

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

//! Read only few settings (window properties)
void MainWindow::readSettings()
{
    QSettings settings("Modelib", "LPditor");
    QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
    QSize size = settings.value("size", QSize(400, 400)).toSize();
    move(pos);
    resize(size);
}

//! Write window settings
void MainWindow::writeSettings()
{
    QSettings settings("Modelib", "LPditor");
    settings.setValue("pos", pos());
    settings.setValue("size", size());
}

//! Read the plugin directory and load plugins
void MainWindow::readPlugins()
{
	QDir pluginsDir = QDir(qApp->applicationDirPath());
#if defined(Q_OS_WIN)
	if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
		pluginsDir.cdUp();
#elif defined(Q_OS_MAC)
	if (pluginsDir.dirName() == "MacOS")
	{
		pluginsDir.cdUp();
		pluginsDir.cdUp();
		pluginsDir.cdUp();
	}
#endif
	pluginsDir.cd("plugins");

	// load every plugins
	foreach (QString fileName, pluginsDir.entryList(QDir::Files))
	{
		QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
		QObject *plugin = loader.instance();
		if (plugin)
		{
			PluginInterface *iPlug = qobject_cast<PluginInterface *>(plugin);
			if (iBrush)
				addToMenu(plugin, iPlug->plugins(), pluginMenu, SLOT(applyPlugin()), pluginAct);
			pluginFileNames += fileName;
		}
	}

	// for saving file...
	pluginsDir.cd(qApp->applicationDirPath());
}

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

		if (actionGroup)
		{
			action->setCheckable(true);
			actionGroup->addAction(action);
		}
	}
}

//! Ask for saving the document
bool MainWindow::maybeSave()
{
    if (textEdit->document()->isModified()) {
        int ret = QMessageBox::warning(this, tr("LPditor"),
                     tr("The document has been modified.\n"
                        "Do you want to save your changes?"),
                     QMessageBox::Yes | QMessageBox::Default,
                     QMessageBox::No,
                     QMessageBox::Cancel | QMessageBox::Escape);
        if (ret == QMessageBox::Yes)
            return save();
        else if (ret == QMessageBox::Cancel)
            return false;
    }
    return true;
}

//! Load a file
void MainWindow::loadFile(const QString &fileName)
{

    QFile file(fileName);
    if (!file.open(QFile::ReadOnly | QFile::Text)) {
        QMessageBox::warning(this, tr("LPditor"),
                             tr("Cannot read file %1:\n%2.")
                             .arg(fileName)
                             .arg(file.errorString()));
        return;
    }

    QTextStream in(&file);
    QApplication::setOverrideCursor(Qt::WaitCursor);
    textEdit->setPlainText(in.readAll());
    QApplication::restoreOverrideCursor();

    setCurrentFile(fileName);
    statusBar()->showMessage(tr("File loaded"), 2000);
	setWindowModified(false);
}

//! Really save the file
bool MainWindow::saveFile(const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        QMessageBox::warning(this, tr("LPditor"),
                             tr("Cannot write file %1:\n%2.")
                             .arg(fileName)
                             .arg(file.errorString()));
        return false;
    }

    QTextStream out(&file);
    QApplication::setOverrideCursor(Qt::WaitCursor);
    out << textEdit->toPlainText();
    QApplication::restoreOverrideCursor();

    setCurrentFile(fileName);
    statusBar()->showMessage(tr("File saved"), 2000);
    return true;
}

//! Give a name to a unnamed file
void MainWindow::setCurrentFile(const QString &fileName)
{
    static int sequenceNumber = 1;

    isUntitled = fileName.isEmpty();
    if (isUntitled) {
        curFile = tr("document%1.lp").arg(sequenceNumber++);
    } else {
        curFile = QFileInfo(fileName).canonicalFilePath();
    }

    setWindowTitle(tr("%1[*] - %2").arg(strippedName(curFile)).arg(tr("LPditor :: Modelib project")));
    textEdit->document()->setModified(false);
	setWindowModified(false);
}

//! Get only the name of a file (not the path)
QString MainWindow::strippedName(const QString &fullFileName)
{
    return QFileInfo(fullFileName).fileName();
}

MainWindow *MainWindow::findMainWindow(const QString &fileName)
{
    QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();

    foreach (QWidget *widget, qApp->topLevelWidgets()) {
        MainWindow *mainWin = qobject_cast<MainWindow *>(widget);
        if (mainWin && mainWin->curFile == canonicalFilePath)
            return mainWin;
    }
    return 0;
}
