/*
 * This file is part of kdev-python
 * Copyright 2013  Sven Brauch <svenbrauch@gmail.com>
 *
 * 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) version 3 or any later version
 * accepted by the membership of KDE e.V. (or its successor approved
 * by the membership of KDE e.V.), which shall act as a proxy
 * defined in Section 14 of version 3 of the license.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "docfilewizard.h"
#include "docfilemanagerwidget.h"

#include <QGroupBox>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QBoxLayout>
#include <QPushButton>
#include <QTabWidget>
#include <QScrollBar>
#include <QDebug>
#include <QDir>
#include <QStandardPaths>

#include <KLocalizedString>
#include <KMessageBox>
#include <KProcess>
#include <interfaces/icore.h>
#include <interfaces/iproject.h>
#include <interfaces/iprojectcontroller.h>
#include <project/projectmodel.h>
#include <util/path.h>

DocfileWizard::DocfileWizard(const QString& workingDirectory, QWidget* parent)
    : QDialog(parent)
    , worker(nullptr)
    , workingDirectory(workingDirectory)
{
    setLayout(new QVBoxLayout);

    // The interpreter group box
    QGroupBox* interpreter = new QGroupBox;
    interpreter->setTitle(i18n("Configure the Python interpreter to use"));
    QFormLayout* interpreterLayout = new QFormLayout;
    interpreterField = new QLineEdit("python");
    interpreterLayout->addRow(new QLabel(i18n("Python executable")), interpreterField);
    interpreter->setLayout(interpreterLayout);

    // The module + output file group box
    QGroupBox* module = new QGroupBox;
    module->setTitle(i18n("Select a python module to generate documentation for"));
    QFormLayout* moduleLayout = new QFormLayout;
    moduleField = new QLineEdit;
    moduleLayout->addRow(new QLabel(i18nc("refers to selecting a python module to perform some operation on",
                                          "Target module (e.g. \"math\")")), moduleField);
    outputFilenameField = new QLineEdit;
    moduleLayout->addRow(new QLabel(i18n("Output filename")), outputFilenameField);
    module->setLayout(moduleLayout);

    // Status group box
    QGroupBox* status = new QGroupBox;
    QTabWidget* tabs = new QTabWidget;
    status->setTitle(i18n("Status and output"));
    statusField = new QTextEdit();
    statusField->setText(i18n("The process has not been run yet."));
    statusField->setFontFamily("monospace");
    statusField->setLineWrapMode(QTextEdit::NoWrap);
    statusField->setReadOnly(true);
    statusField->setAcceptRichText(false);
    resultField = new QTextEdit();
    resultField->setText(i18n("The process has not been run yet."));
    resultField->setFontFamily("monospace");
    resultField->setLineWrapMode(QTextEdit::NoWrap);
    resultField->setReadOnly(true);
    resultField->setAcceptRichText(false);
    status->setLayout(new QHBoxLayout);
    tabs->addTab(statusField, i18n("Script output"));
    tabs->addTab(resultField, i18n("Results"));
    status->layout()->addWidget(tabs);

    // The run / close buttons
    QHBoxLayout* buttonsLayout = new QHBoxLayout;
    buttonsLayout->setDirection(QBoxLayout::RightToLeft);
    QPushButton* closeButton = new QPushButton(i18n("Close"));
    closeButton->setIcon(QIcon::fromTheme("dialog-close"));
    saveButton = new QPushButton(i18n("Save and close"));
    saveButton->setEnabled(false);
    saveButton->setIcon(QIcon::fromTheme("dialog-ok-apply"));
    runButton = new QPushButton(i18n("Generate"));
    runButton->setDefault(true);
    runButton->setIcon(QIcon::fromTheme("tools-wizard"));
    buttonsLayout->addWidget(closeButton);
    buttonsLayout->addWidget(runButton);
    buttonsLayout->addWidget(saveButton);
    buttonsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));

    // connections
    QObject::connect(closeButton, &QAbstractButton::clicked, this, &QWidget::close);
    QObject::connect(saveButton, &QAbstractButton::clicked, this, &DocfileWizard::saveAndClose);
    QObject::connect(moduleField, &QLineEdit::textChanged, this, &DocfileWizard::updateOutputFilename);
    QObject::connect(runButton, &QAbstractButton::clicked, this, &DocfileWizard::run);

    // putting it all together
    layout()->addWidget(interpreter);
    layout()->addWidget(module);
    layout()->addWidget(status);
    layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
    qobject_cast<QVBoxLayout*>(layout())->addLayout(buttonsLayout); // TODO ugh

    resize(640, 480);
}

const QString DocfileWizard::wasSavedAs() const
{
    return savedAs;
}

QString DocfileWizard::fileNameForModule(QString moduleName) const
{
    if ( moduleName.isEmpty() ) {
        return moduleName;
    }
    return moduleName.replace('.', '/') + ".py";
}

void DocfileWizard::setModuleName(const QString& moduleName)
{
    moduleField->setText(moduleName);
}

bool DocfileWizard::run()
{
    // validate input data, setup and program state
    if ( worker ) {
        // process already running
        return false;
    }
    QString scriptUrl = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/scripts/introspect.py");
    if ( scriptUrl.isEmpty() ) {
        KMessageBox::error(this, i18n("Couldn't find the introspect.py script; check your installation!"));
        return false;
    }
    if ( workingDirectory.isEmpty() ) {
        KMessageBox::error(this, i18n("Couldn't find a valid kdev-python data directory; check your installation!"));
        return false;
    }
    QString outputFilename = outputFilenameField->text();
    if ( outputFilename.contains("..") ) {
        // protect the user from writing outside the data directory accidentally
        KMessageBox::error(this, i18n("Invalid output filename"));
        return false;
    }

    runButton->setEnabled(false);

    // clean output from previous script runs; since the fields are set to readonly,
    // no user data will be lost
    statusField->clear();
    resultField->clear();

    // set up the process and connect relevant slots
    QString interpreter = interpreterField->text();
    QString module = moduleField->text();
    worker = new QProcess(this);
    QObject::connect(worker, &QProcess::readyReadStandardError, this, &DocfileWizard::processScriptOutput);
    QObject::connect(worker, &QProcess::readyReadStandardOutput, this, &DocfileWizard::processScriptOutput);
    QObject::connect(worker, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &DocfileWizard::processFinished);

    // can never have too many slashes
    outputFile.setFileName(workingDirectory + "/" + outputFilename);
    
    QList<KDevelop::IProject*> projs = KDevelop::ICore::self()->projectController()->projects();
    QStringList args;
    args << scriptUrl;
    foreach(const KDevelop::IProject* proj, projs)
    {
        if ( proj )
            args << proj->path().toLocalFile();
    }
    args << module;
    worker->start(interpreter, args);
    return true;
}

void DocfileWizard::saveAndClose()
{
    bool mayWrite = true;
    if ( outputFile.exists() ) {
        mayWrite = KMessageBox::questionYesNo(this, i18n("The output file <br/>%1<br/> already exists, "
                                                         "do you want to overwrite it?",
                                                          outputFile.fileName())) == KMessageBox::Yes;
    }
    if ( mayWrite ) {
        auto url = QUrl::fromLocalFile(outputFile.fileName());
        Q_ASSERT(url.isLocalFile());
        auto basePath = url.url(QUrl::RemoveFilename | QUrl::PreferLocalFile);

        // should have been done previously
        Q_ASSERT(QDir(basePath).exists());
        if ( ! QDir(basePath).exists() ) {
            QDir(basePath).mkpath(basePath);
        }
        outputFile.open(QIODevice::WriteOnly);
        QString header = "\"\"\"" + i18n("This file contains auto-generated documentation extracted\n"
                                         "from python run-time information. It is analyzed by KDevelop\n"
                                         "to offer features such as code-completion and syntax highlighting.\n"
                                         "If you discover errors in KDevelop's support for this module,\n"
                                         "you can edit this file to correct the errors, e.g. you can add\n"
                                         "additional return statements to functions to control the return\n"
                                         "type to be used for that function by the analyzer.\n"
                                         "Make sure to keep a copy of your changes so you don't accidentally\n"
                                         "overwrite them by re-generating the file.\n") + "\"\"\"\n\n";
        outputFile.write(header.toUtf8() + resultField->toPlainText().toUtf8());
        outputFile.close();
        savedAs = outputFile.fileName();
        close();
    }
}

void DocfileWizard::processScriptOutput()
{
    statusField->insertPlainText(worker->readAllStandardError());
    resultField->insertPlainText(worker->readAllStandardOutput());
    QScrollBar* scrollbar = statusField->verticalScrollBar();
    scrollbar->setValue(scrollbar->maximum());
}

void DocfileWizard::processFinished(int, QProcess::ExitStatus)
{
    worker = nullptr;
    runButton->setEnabled(true);
    saveButton->setEnabled(true);
}

void DocfileWizard::updateOutputFilename(const QString& newModuleName)
{
    QString newFileName = fileNameForModule(newModuleName);
    if ( fileNameForModule(previousModuleName) == outputFilenameField->text() ) {
        // the user didn't edit the field, or edited it to what it is anyways, so update the text
        // otherwise, do nothing.
        outputFilenameField->setText(newFileName);
    }
    previousModuleName = newModuleName;
}
