/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2016 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * 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 "ClassNameHandler.h"

// includes from std
#include <iostream>

// includes from Qt
#include <QTextStream>

// 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 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(QString xmlFilename, QString devDirectoryName) {
    initMyResource();
    setXmlFileName(xmlFilename);
    setDevDirectoryName(devDirectoryName);
    createDomTree();
}

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

CepGenerator::~CepGenerator() {
    std::cout << "inside CepGenerator destructor..." << std::endl;
    for (QVector<ExtensionGenerator *>::iterator i = extensions.begin(); i != extensions.end(); i++) {
        delete (*i);
    }
}

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

    if ((! xmlFile.exists()) || (! xmlFile.isFile())) {
        QString msg = "Exception from Cep generation: \n     The file " + xmlFileName +  " does not exist or is not a file...\n";
        throw (msg);
    }

    this->xmlFileName = xmlFile;
}

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

    if ( ! devDir.isDir()) {
        QString msg = "Exception from Cep generation: \n The path " + devDirectoryName + " is not a directory\n";
        throw (msg);
    }

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

void CepGenerator::createDomTree() throw (QString) {
    std::cout << "Creating Dom Tree..." << std::endl;
    std::string xmlFileStr = this->xmlFileName.canonicalFilePath().toStdString();

    try {
        this->domCep = cep(xmlFileStr, xml_schema::flags::dont_validate);
    } catch (const xml_schema::exception& e) {
        std::cout << "Error Parsing XML file:" << std::endl;
        std::cout << e << std::endl;
        throw QString(e.what());
    }
}

void CepGenerator::generateDirectoryTree() throw (QString) {
    std::cout << "Generating 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
    this->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();

    // Create build and doc directories ???

}


void CepGenerator::serializeManifest() {
    // Add a comment for the user NOT to manually modify this file...
    // TODO, how ?????
    // Setting the CEP root directory where the manifest should be
    QDir currentDirectory;
    currentDirectory.cd(devDirectoryName.absolutePath());
    currentDirectory.cd(cepDirectoryName);

    // Set the right file name
    QFileInfo xmlFileInfo;
    xmlFileInfo.setFile(currentDirectory.absolutePath(), cepManifestName + cepManifestExtension);
    QFile xmlFile(xmlFileInfo.absoluteFilePath());
    if (! xmlFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QString msg = "Exception from cep file generation \n    Cannot write on file " + xmlFileInfo.absoluteFilePath() + "\n";
        throw msg;
    }

    // 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)) {
        QString msg = "Exception from cep file generation \n    Cannot write on file " + cmakelistsOutFileInfo.absoluteFilePath() + "\n";
        throw msg;
    }

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

}

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

        this->licence = "";
        text = in.readLine();
        while (! text.isNull()) {
            this->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)) {
        QString msg = "Exception from cep file generation \n    Cannot write on file " + copyrightOutFileInfo.absoluteFilePath() + "\n";
        throw msg;
    }
    QTextStream out(&copyrightOutFile);

    QTextStream in(&(this->licence));
    QString text;
    do {
        text = in.readLine();
        out << text << 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)) {
            QString msg = "Exception from cep file generation \n    Cannot write on file " + readmeOutFileInfo.absoluteFilePath() + "\n";
            throw msg;
        }
        QTextStream out(&readmeOutFile);

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

        QString text;

        do {
            text = in.readLine();
            out << text << 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::createActionExtensionDoms() {
    std::cout << "creating action extension doms..." << std::endl;
    QString cmakelistFileName;
    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() {
    std::cout << "creating component extension doms..." << std::endl;
    QString cmakelistFileName;
    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() {
    std::cout << "creating library doms..." << std::endl;
    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);
            this->extensions.append(generator);
        }
    }

}

void CepGenerator::generateExtensions() {
    std::cout << "inside generateExtension" << std::endl;

    // Component and Action extensions genration
    for (QVector<ExtensionGenerator *>::iterator c = extensions.begin(); c != extensions.end(); c++) {
        (*c)->generateExtension();
    }

}

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