/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "CepGenerator.h"
#include "ExtensionGenerator.h"
#include "ActionExtensionGenerator.h"
#include "ComponentExtensionGenerator.h"
#include "LibraryGenerator.h"
#include "ViewerExtensionGenerator.h"
#include "ClassNameHandler.h"

// includes from std
#include <iostream>
#include <stdexcept>

// includes from Qt
#include <QTextStream>
#include <QRegularExpression>

// includes from cepcoreschema
#include <Cep.hxx>
#include <Contact.hxx>


static const QString cepActionsDirectoryName          = "actions";
static const QString cepApplicationsDirectoryName     = "applications";
static const QString cepComponentsDirectoryName       = "components";
static const QString cepLibrariesDirectoryName        = "libraries";
static const QString cepViewersDirectoryName          = "viewers";
static const QString cepManifestName                  = "CEPContent";
static const QString cepManifestExtension             = ".xml";
static const QString cepSchemaNamespace               = "http://camitk.imag.fr/cepcoreschema";

inline void initMyResource() {
    Q_INIT_RESOURCE(cepgenerator);
}

using namespace cepcoreschema;

CepGenerator::CepGenerator() {
    initMyResource();
}

CepGenerator::CepGenerator(QString xmlFilename, QString devDirectoryName) {
    initMyResource();
    setXmlFileName(xmlFilename);
    setDevDirectoryName(devDirectoryName);
    createDomTree();
}

CepGenerator::CepGenerator(std::unique_ptr< cepcoreschema::Cep > domCep, QString devDirectoryName) {
    initMyResource();
    this->domCep = std::move(domCep);
    setDevDirectoryName(devDirectoryName);
}

CepGenerator::~CepGenerator() {
    for (auto& extension : extensions) {
        delete extension;
    }
}

void CepGenerator::setXmlFileName(QString xmlFileName) {
    QFileInfo xmlFile(xmlFileName);

    if ((! xmlFile.exists()) || (! xmlFile.isFile())) {
        throw std::invalid_argument("I/O exception during CEP file generation:\nFile " + xmlFileName.toStdString() + " does not exist or is not a file...\n");
    }

    this->xmlFileName = xmlFile;
}

void CepGenerator::setDevDirectoryName(QString devDirectoryName) {
    devDirectoryName = devDirectoryName + "/";
    QFileInfo devDir(devDirectoryName);

    if (! devDir.isDir()) {
        throw std::invalid_argument("I/O exception during CEP file generation\nPath " + devDirectoryName.toStdString() + " is not a directory\n");
    }

    this->devDirectoryName = devDir.absoluteDir();
}

void CepGenerator::createDomTree() {
    std::cout << "Parsing CEP XML..." << std::endl;
    std::string xmlFileStr = xmlFileName.canonicalFilePath().toStdString();

    try {
        domCep = cep(xmlFileStr, xml_schema::flags::dont_validate);
    }
    catch (const xml_schema::exception& e) {
        std::cerr << "Parsing exception during in CEP XML document " << xmlFileStr << ": " << std::endl;
        std::cerr << e << std::endl;
        throw std::invalid_argument(e.what());
    }
}

void CepGenerator::generateDirectoryTree() {
    std::cout << "Generating source directory tree..." << std::endl;

    // --------------------   Create a sub-directory with the CEP name
    QString cepName(domCep->name().c_str());
    // A directory name should be low case with no space
    cepDirectoryName = ClassNameHandler::getDirectoryName(cepName);

    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.mkdir(cepDirectoryName);
    currentDirectory.cd(cepDirectoryName);

    // Copy Manifest (actually, serialize the actual DOM)
    serializeManifest();

    // Copy CMakeLists.txt
    generateGlobalCMakeLists();

    // Copy  Copyright / FindCamiTK.cmake / README files
    generateCopyright();
    generateReadme();
    copyFindCamiTK();

    // Create actions / applications / components and libraries subdirectories
    // Actions
    generateActionsDirectory();
    // Applications
//    generateApplicationsDirectory();
    // Components
    generateComponentsDirectory();
    // Libraries
    generateLibrariesDirectory();
    // Viewers
    generateViewersDirectory();

    // Create doc directories ???

}


void CepGenerator::serializeManifest(QString fileName) {
    // Add a comment for the user NOT to manually modify this file...
    // TODO, how ?????
    // Setting the CEP root directory where the manifest should be

    QFileInfo xmlFileInfo;
    xmlFileInfo.setFile(fileName);
    if (xmlFileInfo.exists() != true) {
        if (xmlFileInfo.filePath().isEmpty() != true) {

        }
        else {
            fileName = cepManifestName;

            QDir currentDirectory;
            currentDirectory.cd(devDirectoryName.absolutePath());
            currentDirectory.cd(cepDirectoryName);

            // Set the right file name
            xmlFileInfo.setFile(currentDirectory.absolutePath(), fileName + cepManifestExtension);
        }
    }
    // Suppose that fileName is a complete path of file.
    else {
    }


    QFile xmlFile(xmlFileInfo.absoluteFilePath());
    if (! xmlFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        throw std::invalid_argument("I/O exception during CEP file generation\nCannot write into file " + xmlFileInfo.absoluteFilePath().toStdString() + "\n");
    }

    // Serialization of domCep to a std::string
    xml_schema::namespace_infomap map;
    map[""].name = cepSchemaNamespace.toStdString();
    map[""].schema = "Cep.xsd";
    std::ostringstream oss;
    cep(oss, *domCep, map);
    std::string xml(oss.str());


    QTextStream qout(&xmlFile);
    qout << xml.c_str();
    xmlFile.close();
}


void CepGenerator::generateGlobalCMakeLists() {
    // Setting CMakeLists parameters
    // project name
    QString projectNameString(domCep->name().c_str());

    // contacts
    QString contactString = "";
    Contact contact = domCep->contact();
    for (Contact::email_const_iterator i = contact.email().begin(); i != contact.email().end(); i++) {
        contactString += QString((*i).c_str());
        if ((i + 1) != contact.email().end()) {
            contactString += ", ";
        }
    }
    // description
    QString descriptionString(domCep->description().c_str());

    // Setting the CEP root directory where the manifest should be
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);

    // Open the resources CMakeFile model
    QFile initFile(":/resources/cep.CMakeLists.txt.in");
    initFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream in(&initFile);

    // Set the right output file name
    QFileInfo cmakelistsOutFileInfo;
    cmakelistsOutFileInfo.setFile(currentDirectory.absolutePath(), "CMakeLists.txt");
    QFile cmakelistsOutFile(cmakelistsOutFileInfo.absoluteFilePath());
    if (! cmakelistsOutFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        throw std::invalid_argument("I/O exception during CEP file generation\nCannot write into file " + cmakelistsOutFileInfo.absoluteFilePath().toStdString() + "\n");
    }

    QTextStream out(&cmakelistsOutFile);
    QString text;
    do {
        text = in.readLine();
        text.replace(QRegularExpression("@CMAKE_PROJECT_NAME@"), cepDirectoryName);
        text.replace(QRegularExpression("@PROJECT_NAME@"), projectNameString);
        text.replace(QRegularExpression("@CONTACT@"), contactString);
        text.replace(QRegularExpression("@DESCRIPTION@"), descriptionString);
        out << text << Qt::endl;
    }
    while (! text.isNull());
    cmakelistsOutFile.close();
    initFile.close();

}

void CepGenerator::setLicence() {
    // Get the copyright in the CEP DOM...
    if (domCep->copyright().present()) {
        std::cout << "Licence found." << std::endl;
        licence = QString(domCep->copyright().get().c_str());
    }
    else {
        std::cout << "No licence found: using default license." << std::endl;
        QFile defaultLicenceFile(":/resources/COPYRIGHT.in");
        defaultLicenceFile.open(QIODevice::ReadOnly | QIODevice::Text);
        QTextStream in(&defaultLicenceFile);
        QString text;

        licence = "";
        text = in.readLine();
        while (! text.isNull()) {
            licence += text + "\n";
            text = in.readLine();
        }
    }
}

void CepGenerator::generateCopyright() {
    // Setting the CEP root directory where the copyright should be
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);

    // Set the right output file name
    QFileInfo copyrightOutFileInfo;
    copyrightOutFileInfo.setFile(currentDirectory.absolutePath(), "COPYRIGHT");
    QFile copyrightOutFile(copyrightOutFileInfo.absoluteFilePath());
    if (! copyrightOutFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        throw std::invalid_argument("I/O exception during CEP file generation\nCannot write into file " + copyrightOutFileInfo.absoluteFilePath().toStdString() + "\n");
    }
    QTextStream out(&copyrightOutFile);

    QTextStream in(&(licence));
    QString text;
    do {
        text = in.readLine();
        out << text << Qt::endl;
    }
    while (! text.isNull());

    copyrightOutFile.close();

}

void CepGenerator::generateReadme() {
    // Setting the CEP root directory where the copyright should be
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);

    // Get the copyright in the CEP DOM...
    if (domCep->readme().present()) {
        // Set the right output file name
        QFileInfo readmeOutFileInfo;
        readmeOutFileInfo.setFile(currentDirectory.absolutePath(), "README");
        QFile readmeOutFile(readmeOutFileInfo.absoluteFilePath());
        if (! readmeOutFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
            throw std::invalid_argument("I/O exception during CEP file generation\nCannot write into file " + readmeOutFileInfo.absoluteFilePath().toStdString() + "\n");
        }
        QTextStream out(&readmeOutFile);

        QString readmeString(domCep->readme().get().c_str());
        QTextStream in(&readmeString);

        QString text;

        do {
            text = in.readLine();
            out << text << Qt::endl;
        }
        while (! text.isNull());
    }
    // ...Or copy the default one
    else {
        QString outputFileName = currentDirectory.absolutePath() + "/README";
        QFile::copy(":/resources/README.in", outputFileName);
    }
}

void CepGenerator::copyFindCamiTK() {
    // Setting the CEP root directory where the manifest should be
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    QString outputFileName = currentDirectory.absolutePath() + "/FindCamiTK.cmake";
    QFile::copy(":/resources/FindCamiTK.cmake.in", outputFileName);
}

void CepGenerator::generateActionsDirectory() {
    QString cmakelistFileName;
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    // createing actions directory
    currentDirectory.mkdir(cepActionsDirectoryName);
    // copying CMakeLists.txt
    currentDirectory.cd(cepActionsDirectoryName);
    cmakelistFileName = currentDirectory.absolutePath() + "/CMakeLists.txt";
    QFile::copy(":/resources/actions.CMakeLists.txt.in", cmakelistFileName);
}

void CepGenerator::generateApplicationsDirectory() {
    QString cmakelistFileName;
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    // createing actions directory
    currentDirectory.mkdir(cepApplicationsDirectoryName);
    // copying CMakeLists.txt
    currentDirectory.cd(cepApplicationsDirectoryName);
    cmakelistFileName = currentDirectory.absolutePath() + "/CMakeLists.txt";
    QFile::copy(":/resources/applications.CMakeLists.txt.in", cmakelistFileName);
}
void CepGenerator::generateComponentsDirectory() {
    QString cmakelistFileName;
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    // creating actions directory
    currentDirectory.mkdir(cepComponentsDirectoryName);
    // copying CMakeLists.txt
    currentDirectory.cd(cepComponentsDirectoryName);
    cmakelistFileName = currentDirectory.absolutePath() + "/CMakeLists.txt";
    QFile::copy(":/resources/components.CMakeLists.txt.in", cmakelistFileName);
}
void CepGenerator::generateLibrariesDirectory() {
    QString cmakelistFileName;
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    // createing actions directory
    currentDirectory.mkdir(cepLibrariesDirectoryName);
    // copying CMakeLists.txt
    currentDirectory.cd(cepLibrariesDirectoryName);
    cmakelistFileName = currentDirectory.absolutePath() + "/CMakeLists.txt";
    QFile::copy(":/resources/libraries.CMakeLists.txt.in", cmakelistFileName);
}

void CepGenerator::generateViewersDirectory() {
    QString cmakelistFileName;
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    // createing actions directory
    currentDirectory.mkdir(cepViewersDirectoryName);
    // copying CMakeLists.txt
    currentDirectory.cd(cepViewersDirectoryName);
    cmakelistFileName = currentDirectory.absolutePath() + "/CMakeLists.txt";
    QFile::copy(":/resources/viewers.CMakeLists.txt.in", cmakelistFileName);
}

void CepGenerator::createActionExtensionDoms() {
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    currentDirectory.cd(cepActionsDirectoryName);

    if (domCep->actionExtensions().present()) {
        ActionExtensions extensions = domCep->actionExtensions().get();
        for (ActionExtensions::actionExtension_iterator i = extensions.actionExtension().begin(); i != extensions.actionExtension().end(); i++) {
            // TODO: find a way to use std:auto_ptr here for the dom extension instead of creating a new instance
            // Is it only possible ? http://stackoverflow.com/questions/12674735/function-wont-accept-iterator-to-auto-ptr
            ActionExtension& extension = (*i);
            ActionExtensionGenerator* generator = new ActionExtensionGenerator(extension, currentDirectory.absolutePath(), licence);
            this->extensions.append(generator);
        }
    }
}

void CepGenerator::createComponentExtensionDoms() {
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    currentDirectory.cd(cepComponentsDirectoryName);

    if (domCep->componentExtensions().present()) {
        ComponentExtensions extensions = domCep->componentExtensions().get();
        for (ComponentExtensions::componentExtension_iterator i = extensions.componentExtension().begin(); i != extensions.componentExtension().end(); i++) {
            // TODO: find a way to use std:auto_ptr here for the dom extension instead of creating a new instance
            // Is it only possible ? http://stackoverflow.com/questions/12674735/function-wont-accept-iterator-to-auto-ptr
            ComponentExtension& extension = (*i);
            ComponentExtensionGenerator* generator = new ComponentExtensionGenerator(extension, currentDirectory.absolutePath(), licence);
            this->extensions.append(generator);
        }
    }
}

void CepGenerator::createLibrarieDoms() {
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    currentDirectory.cd(cepLibrariesDirectoryName);

    if (domCep->libraries().present()) {
        Libraries libs = domCep->libraries().get();
        for (Libraries::library_iterator i = libs.library().begin(); i != libs.library().end(); i++) {
            // TODO: find a way to use std:auto_ptr here for the dom extension instead of creating a new instance
            // Is it only possible ? http://stackoverflow.com/questions/12674735/function-wont-accept-iterator-to-auto-ptr
            Library& lib = (*i);
            LibraryGenerator* generator = new LibraryGenerator(lib, currentDirectory.absolutePath(), licence);
            extensions.append(generator);
        }
    }

}

void CepGenerator::createViewersExtensionDoms() {
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);
    currentDirectory.cd(cepViewersDirectoryName);

    if (domCep->viewerExtensions().present()) {
        ViewerExtensions extensions = domCep->viewerExtensions().get();
        for (auto i = extensions.viewerExtension().begin(); i != extensions.viewerExtension().end(); i++) {
            // TODO: find a way to use std:auto_ptr here for the dom extension instead of creating a new instance
            // Is it only possible ? http://stackoverflow.com/questions/12674735/function-wont-accept-iterator-to-auto-ptr
            ViewerExtension& extension = (*i);
            ViewerExtensionGenerator* generator = new ViewerExtensionGenerator(extension, currentDirectory.absolutePath(), licence);
            this->extensions.append(generator);
        }
    }
}

void CepGenerator::generateExtensions() {
    // Component and Action extensions genration
    for (auto& extension : extensions) {
        extension->generateExtension();
    }
}

void CepGenerator::process() {
    setLicence();
    generateDirectoryTree();
    createComponentExtensionDoms();
    createActionExtensionDoms();
    createLibrarieDoms();
    createViewersExtensionDoms();
    generateExtensions();
}
