/*
    Copyright (c) 2008-12 Qtrac Ltd. All rights reserved.
    This program or module 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 version 3 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.
*/

#include "alt_key.hpp"
#include "mainwindow.hpp"
#include "optionsdlg.hpp"
#include "syntaxhighlighter.hpp"
#include <QApplication>
#include <QChar>
#include <QClipboard>
#include <QDesktopWidget>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QHash>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QPushButton>
#include <QSet>
#include <QSettings>
#include <QShortcut>
#include <QSplitter>
#include <QTextBrowser>
#include <QTextEdit>
#include <QTextStream>
#include <QTimer>
#include <QVBoxLayout>


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
    helpDialog(0), alphabet(tr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")),
    prompt_to_save_on_exit(true), restore_workspace_at_startup(true),
    load_last_file_at_startup(true)
{
    QSettings settings;
    alphabet = settings.value("alphabet", alphabet).toString();
    prompt_to_save_on_exit = settings.value("prompt_to_save_on_exit",
                                        prompt_to_save_on_exit).toBool();
    restore_workspace_at_startup = settings.value(
            "restore_workspace_at_startup",
            restore_workspace_at_startup).toBool();
    load_last_file_at_startup = settings.value("load_last_file_at_startup",
            load_last_file_at_startup).toBool();
    predefined_accelerators = settings.value("predefined_accelerators")
                                             .toStringList();

    inputText = new QTextEdit;
    SyntaxHighlighter *highlighter = new SyntaxHighlighter(inputText);
    inputText->setAcceptRichText(false);
    inputText->setTabChangesFocus(true);
    outputText = new QTextBrowser;
    outputText->setObjectName("OutputText");
    highlighter = new SyntaxHighlighter(outputText);
    outputText->setTabChangesFocus(true);
    logText = new QTextBrowser;
    logText->setObjectName("LogText");
    logText->setTabChangesFocus(true);

    calculateButton = new QPushButton(
            QIcon(":/editcalculate.png"), tr("Calculate"));
    calculateButton->setToolTip(tr("Calculate the accelerators"));
    QPushButton *newButton = new QPushButton(
            QIcon(":/filenew.png"), tr("New"));
    newButton->setToolTip(tr("Clear the input text edit ready for a "
                             "new list"));
    QPushButton *loadButton = new QPushButton(
            QIcon(":/fileopen.png"), tr("Load..."));
    loadButton->setToolTip(tr("Load a plain text file of input strings"));
    QPushButton *saveButton = new QPushButton(
            QIcon(":/filesave.png"), tr("Save"));
    saveButton->setToolTip(tr("Save the input strings"));
    QPushButton *saveAsButton = new QPushButton(
            QIcon(":/filesaveas.png"), tr("Save &As..."));
    saveAsButton->setToolTip(tr("Save the input strings under a new "
                                "filename"));
    QPushButton *copyButton = new QPushButton(
            QIcon(":/editcopy.png"), tr("Copy to Clipboard"));
    copyButton->setToolTip(tr("Copy the accelerated strings to the "
                              "clipboard"));
    QPushButton *optionsButton = new QPushButton(
            QIcon(":/editoptions.png"), tr("Set Options..."));
    optionsButton->setToolTip(tr("Pop-up the options dialog"));
    QPushButton *helpButton = new QPushButton(
            QIcon(":/helphelp.png"), tr("Help"));
    helpButton->setToolTip(tr("Show the help text"));
    QPushButton *aboutButton = new QPushButton(
            QIcon(":/icon.png"), tr("About"));
    aboutButton->setToolTip(tr("Pop-up the about box"));
    QPushButton *quitButton = new QPushButton(
            QIcon(":/filequit.png"), tr("Quit"));
    quitButton->setToolTip(tr("Terminate the program"));

    QSplitter *middleSplitter = new QSplitter(Qt::Horizontal);
    middleSplitter->addWidget(inputText);
    middleSplitter->addWidget(outputText);
    splitter = new QSplitter(Qt::Vertical);
    splitter->addWidget(middleSplitter);
    splitter->setStretchFactor(0, 1);
    splitter->addWidget(logText);
    QVBoxLayout *rightLayout = new QVBoxLayout;
    rightLayout->addWidget(calculateButton);
    rightLayout->addStretch();
    rightLayout->addWidget(newButton);
    rightLayout->addWidget(loadButton);
    rightLayout->addWidget(saveButton);
    rightLayout->addWidget(saveAsButton);
    rightLayout->addWidget(copyButton);
    rightLayout->addStretch();
    rightLayout->addWidget(optionsButton);
    rightLayout->addWidget(helpButton);
    rightLayout->addWidget(aboutButton);
    rightLayout->addStretch();
    rightLayout->addWidget(quitButton);
    QHBoxLayout *layout = new QHBoxLayout;
    layout->addWidget(splitter, 1);
    layout->addLayout(rightLayout);
    QWidget *widget = new QWidget;
    widget->setLayout(layout);
    setCentralWidget(widget);

    new QShortcut(QKeySequence(tr("F1")), this, SLOT(help()));
    new QShortcut(QKeySequence(tr("Ctrl+S")), this, SLOT(save()));

    connect(calculateButton, SIGNAL(clicked()),
            this, SLOT(calculate()));
    connect(optionsButton, SIGNAL(clicked()),
            this, SLOT(setOptions()));
    connect(newButton, SIGNAL(clicked()),
            this, SLOT(new_()));
    connect(loadButton, SIGNAL(clicked()),
            this, SLOT(load()));
    connect(saveButton, SIGNAL(clicked()),
            this, SLOT(save()));
    connect(saveAsButton, SIGNAL(clicked()),
            this, SLOT(saveAs()));
    connect(copyButton, SIGNAL(clicked()),
            this, SLOT(copy()));
    connect(helpButton, SIGNAL(clicked()),
            this, SLOT(help()));
    connect(aboutButton, SIGNAL(clicked()),
            this, SLOT(about()));
    connect(quitButton, SIGNAL(clicked()),
            this, SLOT(close()));

    AQP::accelerateWidget(this);
    setWindowTitle(tr("Alt_Key"));
    setWindowIcon(QIcon(":/icon.png"));
    if (restore_workspace_at_startup)
        restoreGeometry(
                settings.value("MAINWINDOW/GEOMETRY").toByteArray());
    QTimer::singleShot(0, this, SLOT(afterCreation()));
}


void MainWindow::afterCreation()
{
    QSettings settings;
    if (restore_workspace_at_startup) {
        QByteArray bytes = settings.value("mainwindow/splitter")
                                          .toByteArray();
        if (bytes.isEmpty()) {
            QList<int> sizes = splitter->sizes();
            int total = sizes[0] + sizes[1];
            splitter->setSizes(QList<int>() << (total - 15) << 15);
        }
        else
            splitter->restoreState(bytes);
    }
    if (load_last_file_at_startup) {
        QString filename = settings.value("currentfile").toString();
        if (!filename.isEmpty() && QFile::exists(filename))
            loadFile(filename);
    }
}


void MainWindow::closeEvent(QCloseEvent *)
{
    if (helpDialog)
        helpDialog->deleteLater();
    if (prompt_to_save_on_exit && inputText->document()->isModified() &&
        QMessageBox::question(this,
            tr("Alt_Key - Unsaved Changes"),
            tr("Save unsaved changes?"),
            QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes) ==
            QMessageBox::Yes)
            save();
    QSettings settings;
    settings.setValue("mainwindow/geometry", saveGeometry());
    settings.setValue("mainwindow/splitter", splitter->saveState());
    settings.setValue("alphabet", alphabet);
    settings.setValue("prompt_to_save_on_exit", prompt_to_save_on_exit);
    settings.setValue("restore_workspace_at_startup",
                      restore_workspace_at_startup);
    settings.setValue("load_last_file_at_startup",
                      load_last_file_at_startup);
    settings.setValue("predefined_accelerators", predefined_accelerators);
    settings.setValue("currentfile", filename);
    QMainWindow::close();
}


void MainWindow::calculate()
{
    const QChar Cdot(0xB7);

    QHash<QString, QString> hash;
    foreach (const QString &string, predefined_accelerators) {
        QString key = string;
        hash[key.replace("&", "").toUpper()] = string;
    }
    outputText->clear();
    logText->clear();
    QString data = inputText->toPlainText() + "\n";
    QStringList string_lists = data.split("\n\n", QString::SkipEmptyParts);
    foreach (const QString &list, string_lists) {
        QStringList strings;
        QStringList fields = list.split("\n");
        foreach (QString string, fields) {
            string = string.trimmed();
            if (string.startsWith("#") || string.isEmpty())
                continue;
            strings.append(hash.value(string.toUpper(), string));
        }
        if (strings.join("").trimmed().isEmpty())
            continue;
        QStringList results = AQP::accelerated(strings, alphabet);
        foreach (const QString &string, results)
            outputText->append(string);
        outputText->append("");
        int count = AQP::numberAccelerated(results);
        QString number;
        if (count == results.count())
            number = tr("All %1 accelerated").arg(count);
        else
            number = tr("%1/%2 accelerated").arg(count)
                                            .arg(results.count());
        logText->append(tr("'%1' %2 %3 %4 %5% quality %6 %7")
                .arg(strings[0]).arg(Cdot)
                .arg(number).arg(Cdot)
                .arg(AQP::quality(results) * 100, 0, 'f', 1)
                .arg(Cdot).arg(highlightUnused(results)));
    }
}


QString MainWindow::highlightUnused(const QStringList &list_of_strings)
{
    QSet<QChar> used;
    foreach (QString string, list_of_strings) {
        string = string.replace("&&", "");
        int i = string.indexOf("&");
        if (i > -1)
            used.insert(string[i + 1].toUpper());
    }
    QString result;
    foreach (const QChar &x, alphabet) {
        if (used.contains(x))
            result += tr("<b><u>%1</u></b>").arg(x);
        else
            result += x;
    }
    return result;
}


void MainWindow::copy()
{
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(outputText->toPlainText());
    logText->append(tr("Copied result strings to the clipboard"));
}


void MainWindow::new_()
{
    if (inputText->document()->isModified() &&
        QMessageBox::question(this,
            tr("Alt_Key - Unsaved Changes"),
            tr("Save unsaved changes?"),
            QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes)
        save();
    inputText->clear();
    inputText->document()->setModified(false);
    outputText->clear();
    logText->clear();
    filename.clear();
    inputText->setFocus();
    setWindowTitle(tr("Alt_Key - Unnamed"));
}


void MainWindow::load()
{
    if (inputText->document()->isModified() &&
        QMessageBox::question(this,
            tr("Alt_Key - Unsaved Changes"),
            tr("Save unsaved changes?"),
            QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes)
        save();

    QString fname = QFileDialog::getOpenFileName(this,
                        tr("Alt_Key - Load Strings"),
                        filename.isEmpty() ?
                        "." : QFileInfo(filename).absolutePath(),
                        tr("Alt_Key Files (*.acc)\nAll (*)"));
    if (fname.isEmpty())
        return;
    loadFile(fname);
}


void MainWindow::loadFile(const QString &new_filename)
{
    QFile file(new_filename);
    if (!file.open(QIODevice::ReadOnly)) {
        logText->append(tr("<font color=red>Failed to load: %1")
                        .arg(file.errorString()));
        return;
    }
    QTextStream in(&file);
    in.setCodec("UTF-8");
    inputText->setText(in.readAll());
    file.close();
    filename = new_filename;
    setWindowTitle(tr("Alt_Key - %1")
                   .arg(QFileInfo(filename).baseName()));
    outputText->clear();
    logText->append(tr("Loaded %1").arg(filename));
}


void MainWindow::save()
{
    if (filename.isEmpty())
        return saveAs();
    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly)) {
        logText->append(tr("<font color=red>Failed to save: %1")
                        .arg(file.errorString()));
        return;
    }
    QTextStream out(&file);
    out.setCodec("UTF-8");
    out << inputText->toPlainText();
    file.close();
    inputText->document()->setModified(false);
    logText->append(tr("Saved %1").arg(filename));
}


void MainWindow::saveAs()
{
    QString fname = QFileDialog::getSaveFileName(this,
                        tr("Alt_Key - Save Strings"),
                        filename.isEmpty() ?
                        "." : QFileInfo(filename).absolutePath(),
                        tr("Alt_Key Files (*.acc)\nAll (*)"));
    if (fname.isEmpty())
        return;
    if (!fname.contains("."))
        fname += ".acc";
    filename = fname;
    setWindowTitle(tr("Alt_Key - %1").arg(QFileInfo(filename).baseName()));
    save();
}


void MainWindow::setOptions()
{
    OptionsDlg dialog(alphabet, prompt_to_save_on_exit,
                      restore_workspace_at_startup,
                      load_last_file_at_startup, predefined_accelerators,
                      this);
    if (dialog.exec()) {
        alphabet = dialog.alphabet();
        predefined_accelerators = dialog.predefined_accelerators();
        prompt_to_save_on_exit = dialog.prompt_to_save_on_exit();
        restore_workspace_at_startup =
                dialog.restore_workspace_at_startup();
        load_last_file_at_startup =
                dialog.load_last_file_at_startup();
    }
}


void MainWindow::help()
{
    if (!helpDialog) {
        helpDialog = new QDialog(this);
        helpDialog->setWindowTitle(tr("Alt_Key - Help"));
        QTextBrowser *browser = new QTextBrowser;
        QFile file(":/help.html");
        file.open(QIODevice::ReadOnly);
        browser->setText(file.readAll());
        file.close();
        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(browser);
        layout->setContentsMargins(0, 0, 0, 0);
        helpDialog->setLayout(layout);
#ifndef Q_WS_WIN
        QRect rect = QApplication::desktop()->availableGeometry();
        helpDialog->resize(qMin(600, rect.width()),
                           qMin(800, rect.height()));
#else
        helpDialog->resize(400, 400);
#endif
    }
    helpDialog->show();
    helpDialog->raise();
    helpDialog->activateWindow();
}


void MainWindow::about()
{
    static const QString version("2.2.6");

    QMessageBox::about(this, tr("Alt_Key - About"),
    tr("<p><b><a href=\"http://www.qtrac.eu/alt_key.html\">"
    "Alt_Key</a> %1</b> by Mark Summerfield."
    "<p>Copyright &copy; 2008-12 "
    "<a href=\"http://www.qtrac.eu\">Qtrac</a> "
    "Ltd. All rights reserved."
    "<hr><p>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 3 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 (in file <tt>gpl-3.0.txt</tt>) "
    "for more details.").arg(version));
}
