/*
 * Copyright (C) 2009  Lorenzo Bettini <http://www.lorenzobettini.it>
 * See COPYING file that comes with this distribution
 */


#include <QtGui>
#include "qsource-highlight.h"

#include <QTextEdit>
#include <QTextStream>
#include <QCloseEvent>
#include <QFileDialog>
#include <QTemporaryFile>

#include <srchilite/versions.h>
#include <srchilite/sourcehighlight.h>
#include <srchilite/parserexception.h>
#include <srchilite/lineranges.h>
#include <srchilite/settings.h>
#include <srchilite/instances.h>

#include <srchiliteqt/ColorDialog.h>
#include <srchiliteqt/Qt4SourceHighlightStyleGenerator.h>
#include <srchiliteqt/SourceHighlightSettingsDialog.h>

#include "ui_mainwindow.h"

#include "qsourcehighlightframe.h"
#include "assistant.h"
#include "customhelpform.h"

#include <sstream>
#include <iostream>

QSourceHighlight::QSourceHighlight()
        : styleModified(false), ui(new Ui::MainWindow)
{
    // this is for Ui_MainWindow
    ui->setupUi(this);

    binDir = QCoreApplication::applicationDirPath ();
    qDebug() << "application path: " << binDir;

    assistant = new Assistant(binDir + "/../share/doc/qsource-highlight");

    ui->headerFrame->setFileDescription("header file");
    ui->footerFrame->setFileDescription("footer file");

    frame = new QSourceHighlightFrame(this);
    textEdit = frame->getInputTextEdit();
    outputTextEdit = frame->getOutputTextEdit();

    setCentralWidget(frame);

    createActions();
    createToolBars();
    createStatusBar();

    readSettings();

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

QSourceHighlight::~QSourceHighlight()
{

}


void QSourceHighlight::closeEvent(QCloseEvent *event)
{
      if (maybeSave()) {
            writeSettings();
            event->accept();
      } else {
            event->ignore();
      }
}

void QSourceHighlight::newFile()
{
      if (maybeSave()) {
            textEdit->clear();
            setCurrentFile("");
      }
}

void QSourceHighlight::open()
{
      if (maybeSave()) {
            QString fileName =
                    QFileDialog::getOpenFileName
                    (this, tr("Open input file"));
            if (!fileName.isEmpty())
            loadFile(fileName);
      }
}

void QSourceHighlight::openStyle()
{
    QString fileName =
            QFileDialog::getOpenFileName
            (this, tr("Open a style file"), "",
             tr("Style files (*.style);;All Files (*)"));
    if (!fileName.isEmpty()) {
        styleComboBox->insertItem(0, fileName);
        styleComboBox->setCurrentIndex(0);
    }
}


bool QSourceHighlight::save()
{
      if (curFile.isEmpty()) {
            return saveAs();
      } else {
            return saveFile(curFile, textEdit);
      }
}

bool QSourceHighlight::saveAs()
{
      QString fileName = QFileDialog::getSaveFileName(this);
      if (fileName.isEmpty())
            return false;

      return saveFile(fileName, textEdit);
}

bool QSourceHighlight::saveHighlighted()
{
      if (curHighlightedFile.isEmpty()) {
            return saveHighlightedAs();
      } else {
            return saveFile(curHighlightedFile, outputTextEdit);
      }
}

bool QSourceHighlight::saveHighlightedAs()
{
      QString fileName = QFileDialog::getSaveFileName(this);
      if (fileName.isEmpty())
            return false;

      return saveFile(fileName, outputTextEdit);
}

bool QSourceHighlight::saveStyle()
{
      if (curStyleFile.isEmpty()) {
            return saveStyleAs();
      } else {
            return saveFile(curStyleFile, 0);
      }
}

bool QSourceHighlight::saveStyleAs()
{
      QString fileName = QFileDialog::getSaveFileName(this);
      if (fileName.isEmpty())
            return false;

      // if we saved a new file, then add it to the
      // list of style file names and select it
      if (saveFile(fileName, 0)) {
          styleComboBox->insertItem(0, fileName);
          styleComboBox->setCurrentIndex(0);
          return true;
      } else {
          return false;
      }
}

void QSourceHighlight::about()
{
      QMessageBox::about(this, tr("About QSource-Highlight"),
            tr("<b>QSource-Highlight</b>, <a href=\"http://qsrchilite.sf.net\">http://qsrchilite.sf.net</a><br><br>"
                " a Qt4 frontend for <a href=\"http://www.gnu.org/software/src-highlite\">GNU Source-highlight</a>,<br>"
                  " using the <a href=\"http://srchiliteqt.sourceforge.net\">Source-highlight Qt library</a>.<br><br>") +
                + "Author: <a href=\"http://www.lorenzobettini.it\">Lorenzo Bettini</a><br><br>"
                  + "<br><br><i>" +
                  QString(srchilite::Versions::getCompleteVersion().c_str()) + "</i>");
}

void QSourceHighlight::documentWasModified()
{
      setWindowModified(textEdit->document()->isModified());
}

void QSourceHighlight::createActions()
{
      connect(ui->actionNew, SIGNAL(triggered()), this, SLOT(newFile()));

      connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(open()));

      connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(save()));
      connect(textEdit->document(), SIGNAL(modificationChanged(bool)),
              ui->actionSave, SLOT(setEnabled(bool)));

      connect(ui->actionSaveAs, SIGNAL(triggered()), this, SLOT(saveAs()));
      //connect(textEdit->document(), SIGNAL(modificationChanged(bool)),
      //        ui->actionSaveAs, SLOT(setEnabled(bool)));

      connect(ui->actionSaveHighlighted, SIGNAL(triggered()), this, SLOT(saveHighlighted()));
      connect(outputTextEdit->document(), SIGNAL(modificationChanged(bool)),
              ui->actionSaveHighlighted, SLOT(setEnabled(bool)));

      connect(ui->actionSaveHighlightedAs, SIGNAL(triggered()), this, SLOT(saveHighlightedAs()));
      //connect(outputTextEdit->document(), SIGNAL(modificationChanged(bool)),
      //        ui->actionSaveHighlightedAs, SLOT(setEnabled(bool)));

      connect(ui->actionOpenStyle, SIGNAL(triggered()), this, SLOT(openStyle()));

      connect(ui->actionSaveStyle, SIGNAL(triggered()), this, SLOT(saveStyle()));

      connect(ui->actionSaveStyleAs, SIGNAL(triggered()), this, SLOT(saveStyleAs()));

      connect(ui->actionExit, SIGNAL(triggered()), this, SLOT(close()));

      connect(ui->actionCut, SIGNAL(triggered()), textEdit, SLOT(cut()));

      connect(ui->actionCopy, SIGNAL(triggered()), textEdit, SLOT(copy()));

      connect(ui->actionPaste, SIGNAL(triggered()), textEdit, SLOT(paste()));

      connect(ui->actionHighlight, SIGNAL(triggered()), this, SLOT(highlightAll()));

      connect(ui->actionHighlightSelectedLines, SIGNAL(triggered()), this, SLOT(highlightSelected()));

      connect(textEdit, SIGNAL(copyAvailable(bool)),
            ui->actionHighlightSelectedLines, SLOT(setEnabled(bool)));

      connect(textEdit, SIGNAL(copyAvailable(bool)),
            ui->contextLineSpinBox, SLOT(setEnabled(bool)));

      connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(about()));

      connect(ui->actionAboutQt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));

      connect(textEdit, SIGNAL(copyAvailable(bool)),
            ui->actionCut, SLOT(setEnabled(bool)));
      connect(textEdit, SIGNAL(copyAvailable(bool)),
            ui->actionCopy, SLOT(setEnabled(bool)));

      connect(ui->actionCustomizeHighlightingStyle, SIGNAL(triggered()),
              this, SLOT(configureHighlighting()));

      connect(ui->actionSourceHighlightSettings, SIGNAL(triggered()),
              this, SLOT(configureSourceHighlight()));

      connect(ui->actionHelp, SIGNAL(triggered()),
              this, SLOT(showDocumentation()));
}

void QSourceHighlight::createToolBars()
{
      // create a combo box for language definition file selections
      languageComboBox = new srchiliteqt::LanguageComboBox();
      ui->highlightToolBar->addWidget(languageComboBox);
      // retrieve the current language by the text editor highlighter
      languageComboBox->setCurrentLanguage(textEdit->getHighlighter()->getLangFile());
      // and connect it to our text editor
      textEdit->connectLanguageComboBox(languageComboBox);

      // create a combo box for output format definition file selections
      outputFormatComboBox = new srchiliteqt::OutputFormatComboBox();
      ui->highlightToolBar->addWidget(outputFormatComboBox);
      // set default output to html
      outputFormatComboBox->setCurrentOutputFormat("html.outlang");
      // and connect it to our text editor
      //textEdit->connectLanguageComboBox(languageComboBox);

      styleComboBox = new srchiliteqt::StyleComboBox;
      ui->highlightToolBar->addWidget(styleComboBox);
      styleComboBox->setCurrentStyle("default.style");
      // each time we insert something it will be resized
      styleComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
      textEdit->connectStyleComboBox(styleComboBox);
}

void QSourceHighlight::currentStyleChanged(const QString &) {
    // since a new style was selected, we reset styleChanged
    styleModified = false;
}

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

void QSourceHighlight::readSettings()
{
    qDebug() << "reading settings...";
      QSettings settings("QSource-Highlight", "QSource-Highlight");
      QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
      QSize size = settings.value("size", QSize(400, 400)).toSize();
      QString fileName = settings.value("fileName", QString("")).toString();
      QString styleFileName = settings.value("styleFileName", QString("default.style")).toString();
      QString outputLangFile = settings.value("outputLangFile", QString("html.outlang")).toString();
      sourceHighlightDataDir = settings.value("sourceHighlightDataDir", "").toString();

      // set the global value for data dir
      srchilite::Settings::setGlobalDataDir(sourceHighlightDataDir.toStdString());

      // make sure to load the source-highlight files use the sourceHighlightDataDir
      reloadComboBoxes();

      outputFormatComboBox->setCurrentOutputFormat(outputLangFile);
      resize(size);
      move(pos);
      if (fileName.isEmpty())
          setCurrentFile("");
      else
          loadFile(fileName);
      textEdit->changeHighlightingStyle(styleFileName);
      if (styleComboBox->findText(styleFileName) == -1) {
          // that was a style file opened from the file system
          // thus we must add it to the style combo box
          styleComboBox->insertItem(0, styleFileName);
          styleComboBox->setCurrentIndex(0);
      }
      frame->splitter()->restoreState(settings.value("splitterSizes").toByteArray());
}

void QSourceHighlight::writeSettings()
{
    qDebug() << "saving settings...";
      QSettings settings("QSource-Highlight", "QSource-Highlight");
      settings.setValue("pos", pos());
      settings.setValue("size", size());
      settings.setValue("splitterSizes", frame->splitter()->saveState());
      settings.setValue("fileName", curFile);
      settings.setValue("styleFileName",
                        textEdit->getHighlighter()->getFormattingStyle());
      settings.setValue("outputLangFile",
                        outputFormatComboBox->getCurrentOutputFormat());
      settings.setValue("sourceHighlightDataDir",
                        sourceHighlightDataDir);
}

bool QSourceHighlight::maybeSave()
{
      if (textEdit->document()->isModified()) {
            int ret = QMessageBox::warning(this, tr("QSource-Highlight"),
                        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;
}

void QSourceHighlight::loadFile(const QString &fileName)
{
    if (fileName.isEmpty())
        return;

      QApplication::setOverrideCursor(Qt::WaitCursor);
      const QString error = textEdit->loadFile(fileName);
      if (error != "") {
          QMessageBox::warning(this, tr("QSource-Highlight"),
                              tr("Cannot read file %1:\n%2.")
                              .arg(fileName)
                              .arg(error));
      } else {
          setCurrentFile(fileName);
          statusBar()->showMessage(tr("File loaded"), 2000);
      }
      QApplication::restoreOverrideCursor();
}

bool QSourceHighlight::saveFile(const QString &fileName, const QTextEdit *editor)
{
      QFile file(fileName);
      if (!file.open(QFile::WriteOnly | QFile::Text)) {
            QMessageBox::warning(this, tr("QSource-Highlight"),
                              tr("Cannot write file %1:\n%2.")
                              .arg(fileName)
                              .arg(file.errorString()));
            return false;
      }

      QTextStream out(&file);
      QApplication::setOverrideCursor(Qt::WaitCursor);

      // TODO: this is a little bit ugly ;-)
      if (editor) {
        out << editor->toPlainText();
      } else {
          // we're saving the style file
          writeStyleFile(&file);
          curStyleFile = fileName;
      }

      QApplication::restoreOverrideCursor();

      // TODO: this is a little bit ugly ;-)
      if (editor == textEdit)
        setCurrentFile(fileName);
      else if (editor == outputTextEdit) {
        curHighlightedFile = fileName;
        outputTextEdit->document()->setModified(false);
      }

      statusBar()->showMessage(tr("File saved"), 2000);

      return true;
}

void QSourceHighlight::setCurrentFile(const QString &fileName)
{
      curFile = fileName;
      textEdit->document()->setModified(false);
      setWindowModified(false);

      QString shownName;
      if (curFile.isEmpty())
            shownName = "untitled.txt";
      else
            shownName = strippedName(curFile);

      setWindowTitle(tr("%1[*] - %2").arg(shownName).arg(tr("QSource-Highlight")));

      // notify the editor, 'cause we might need to change the highlighting language
      textEdit->changeFileName(fileName);
}

QString QSourceHighlight::strippedName(const QString &fullFileName)
{
      return QFileInfo(fullFileName).fileName();
}

void QSourceHighlight::configureHighlighting()
{
    srchiliteqt::ColorDialog dialog(textEdit->getHighlighter(), this);

    if (dialog.exec() == QDialog::Accepted) {
        dialog.syncFormatters();
        textEdit->getHighlighter()->rehighlight();

        // updating text editor colors is still up to us
        textEdit->changeColors(
                textEdit->getHighlighter()->getForegroundColor(),
                textEdit->getHighlighter()->getBackgroundColor());

        styleModified = true;
    }

}

void QSourceHighlight::configureSourceHighlight()
{
    srchiliteqt::SourceHighlightSettingsDialog dialog(this);
    if (sourceHighlightDataDir != "")
        dialog.setSourceHighlightDataDirPath(sourceHighlightDataDir);

    if (dialog.exec() == QDialog::Accepted) {
        if (sourceHighlightDataDir != dialog.getSourceHighlightDataDirPath()) {
            qDebug() << "new source-highlight data dir: " << dialog.getSourceHighlightDataDirPath();
            sourceHighlightDataDir = dialog.getSourceHighlightDataDirPath();
            srchilite::Settings::setGlobalDataDir(sourceHighlightDataDir.toStdString());
            reloadComboBoxes();
        }
    }

}

void QSourceHighlight::showDocumentation()
{
    if (!assistant->showDocumentation("index.html")) {
        // revert to a custom help viewer
        CustomHelpForm *helpForm = new CustomHelpForm(this, Qt::Window);
        if (helpForm->setHtmlHelpFileDir(binDir + "/../share/doc/qsource-highlight/html")) {
            helpForm->setWindowTitle("Custom Help Viewer");
            helpForm->show();
        }
    }
}

bool QSourceHighlight::checkSourceHighlightSettings() {
    if (!srchilite::Settings::checkSettings()) {
        QMessageBox::critical(this, tr("QSource-Highlight"),
                              tr("Source-highlight settings are wrong!\n"
                                 "Please configure it correctly\n"
                                 "otherwise the application may be come unstable"));
        return false;
    }

    return true;
}

void QSourceHighlight::reloadComboBoxes() {
    languageComboBox->reload(sourceHighlightDataDir);
    outputFormatComboBox->reload(sourceHighlightDataDir);
    styleComboBox->reload(sourceHighlightDataDir);

    if (checkSourceHighlightSettings()) {
            // make sure to reload the source-highlight instances
            srchilite::Instances::reload();
    }
}

void QSourceHighlight::highlightAll() {
    highlight(false);
}

void QSourceHighlight::highlightSelected() {
    highlight(true);
}

void QSourceHighlight::writeStyleFile(QFile *file) {
    // we retrieve the current formatters of the highlighter
    QList<srchiliteqt::Qt4TextFormatter*> formatters =
            textEdit->getHighlighter()->getQt4TextFormatterMap().values();
    QStringList contents =
            srchiliteqt::Qt4SourceHighlightStyleGenerator::createStyleContents
                (formatters, textEdit->getHighlighter()->getBackgroundColor());
    QTextStream out(file);
    out << contents.join("\n");
}

void QSourceHighlight::highlight(bool onlySelectedLines) {
    if (!checkSourceHighlightSettings())
        return;

    const QString outputFormatFile = outputFormatComboBox->getCurrentOutputFormat();
    srchilite::SourceHighlight sourcehighlight(outputFormatFile.toStdString());

    QString input = textEdit->toPlainText();

    std::istringstream is(input.toStdString());
    std::ostringstream os;

    // document options
    if (ui->docCheckBox->isChecked()) {
        sourcehighlight.setGenerateEntireDoc(true);
    }

    sourcehighlight.setHeaderFileName(ui->headerFrame->getFilePath().toStdString());
    sourcehighlight.setFooterFileName(ui->footerFrame->getFilePath().toStdString());

    QString docTitle = ui->documentTitleLineEdit->text();
    if (docTitle != "")
        sourcehighlight.setTitle(docTitle.toStdString());

    if (ui->tabToSpacesCheckBox->isChecked()) {
        sourcehighlight.setTabSpaces(ui->tabSpacesSpinBox->value());
    }

    // line number options
    if (ui->lineNumbersCheckBox->isChecked()) {
        sourcehighlight.setGenerateLineNumbers(true);

        if (ui->lineNumberDigitsSpinBox->value() != 0) {
            sourcehighlight.setLineNumberDigits(ui->lineNumberDigitsSpinBox->value());
        } else {
            // set the number of digits for line numbers according to the
            // number of lines in the text editor
            unsigned int lines = textEdit->document()->lineCount();

            int line_num_digit = 0;
            while (lines) {
                ++line_num_digit;
                lines /= 10;
            }

            sourcehighlight.setLineNumberDigits(line_num_digit);
        }

        if (ui->padCharLineEdit->text() != "") {
            // take only the first char since we need a char
            sourcehighlight.setLineNumberPad(ui->padCharLineEdit->text().toAscii().at(0));
        }

        if (ui->lineNumberAnchorsCheckBox->isChecked()) {
            sourcehighlight.setGenerateLineNumberRefs(true);
            sourcehighlight.setLineNumberAnchorPrefix
                    (ui->anchorPrefixLineEdit->text().toStdString());
        }
    }

    // if the current style was modified by the user, then we create
    // a temporary style file that will be used by source-highlight
    QTemporaryFile tempStyle;
    if (styleModified) {
        if (!tempStyle.open()) {
            QMessageBox::warning(this, tr("QSource-Highlight"),
                              tr("Cannot create temporary file:%1.\n"
                                 "The currently selected style file will be used.")
                              .arg(tempStyle.fileName()));
        } else {
            statusBar()->showMessage(tr("Using temporary style file: ") + tempStyle.fileName(), 4000);

            writeStyleFile(&tempStyle);

            sourcehighlight.setStyleFile(tempStyle.fileName().toStdString());
        }
    } else {
        // use the selected style
        const QString &currentStyleFile = styleComboBox->getCurrentStyle();
        if (currentStyleFile.endsWith(".css"))
            sourcehighlight.setStyleCssFile(currentStyleFile.toStdString());
        else
            sourcehighlight.setStyleFile(currentStyleFile.toStdString());
    }

    srchilite::LineRanges lineRanges;
    if (onlySelectedLines) {
        lineRanges.setContextLines(ui->contextLineSpinBox->value());

        // get the selected text line range
        QTextCursor cursor = textEdit->textCursor();
        QTextBlock start = textEdit->document()->findBlock(cursor.selectionStart());
        QTextBlock end = textEdit->document()->findBlock(cursor.selectionEnd());

        lineRanges.addRange
                ((QString("%1-%2").arg(start.blockNumber()+1).arg(end.blockNumber()+1)).toStdString());

        sourcehighlight.setLineRanges(&lineRanges);
    }

    QApplication::setOverrideCursor(Qt::WaitCursor);

    // let's highlight!
    try {
        sourcehighlight.highlight(is, os, languageComboBox->getCurrentLanguage().toStdString());

        // check whether there's an highlighting scheme for output format
        // so that we highlight the source of the output
        const std::string outputFileExtension =
                sourcehighlight.getOutputFileExtension();
        const QString outputFormatHighlightLangFile =
                textEdit->getHighlighter()->getLangDefFileFromFileName(outputFileExtension.c_str());
        if (outputFormatHighlightLangFile != "")
            outputTextEdit->changeHighlightingLanguage(outputFormatHighlightLangFile);
        else
            outputTextEdit->changeHighlightingLanguage("nohilite.lang");

        const QString highlightedContents = os.str().c_str();
        outputTextEdit->setPlainText(highlightedContents);

        // check whether we can provide a preview
        if (outputFormatFile.startsWith("html") || outputFormatFile.startsWith("xhtml")) {
            frame->setPreviewContents(highlightedContents);
        } else {
            frame->setNoPreview();
        }

        outputTextEdit->document()->setModified(true);
        statusBar()->showMessage(tr("Highlighting done"), 2000);
    } catch (const srchilite::ParserException &pe) {
        std::ostringstream details;
        details << pe;
        QMessageBox::warning(this, tr("QSource-Highlight"),
                              tr("Exception from Source-Highlight library:\n%1\n\ndetails: %2.")
                              .arg(pe.what()).arg(details.str().c_str()));
        statusBar()->showMessage(tr("Highlighting failed!"), 2000);
    } catch (const std::exception &e) {
        QMessageBox::warning(this, tr("QSource-Highlight"),
                              tr("Exception from Source-Highlight library:\n%1.")
                              .arg(e.what()));
        statusBar()->showMessage(tr("Highlighting failed!"), 2000);
    }

    QApplication::restoreOverrideCursor();
}

