/*****************************************************************************
 * $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 "ComponentExtensionGenerator.h"

// includes from cepcoreschema
#include <ComponentExtension.hxx>
#include <Components.hxx>
#include <Dependency.hxx>

// includes from STL
#include <iostream>
#include <memory>


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

// local includes
#include "ClassNameHandler.h"
#include "DependencyGenerator.h"
#include "ComponentGenerator.h"

using namespace cepcoreschema;

ComponentExtensionGenerator::ComponentExtensionGenerator(QString xmlFileName, QString componentExtensionsDirectory, QString licence)
    : ExtensionGenerator(componentExtensionsDirectory, licence, "Component") {
    QFileInfo xmlFile(xmlFileName);
    if ((! xmlFile.exists()) || (! xmlFile.isFile())) {
        throw std::invalid_argument("I/O exception during component extension file generation:\nFile " + xmlFileName.toStdString() + " does not exist or is not a file...\n");
    }
    try {
        std::string xmlFileStr = xmlFileName.toStdString();
        auto domComponentExtension = componentExtension(xmlFileStr, xml_schema::flags::dont_validate);
        createFromDom(*domComponentExtension);
    }
    catch (...) {
        throw std::invalid_argument("I/O exception during component extension file generation:\nFile " + xmlFileName.toStdString() + " is not valid...\n");
    }
}

ComponentExtensionGenerator::ComponentExtensionGenerator(ComponentExtension& domComponentExtension, QString componentExtensionsDirectory, QString licence)
    : ExtensionGenerator(componentExtensionsDirectory, licence, "Component") {
    createFromDom(domComponentExtension);
}

void ComponentExtensionGenerator::createFromDom(ComponentExtension& dom) {
    this->name = QString(dom.name().c_str());
    this->description = QString(dom.description().c_str()).simplified();

    if (dom.dependencies().present()) {
        Dependencies deps = dom.dependencies().get();
        for (Dependencies::dependency_iterator it = deps.dependency().begin(); it != deps.dependency().end(); it++) {
            Dependency& dep = (*it);
            DependencyGenerator* depGen = new DependencyGenerator(dep);
            dependencyGenerators.append(depGen);
        }
    }

    // Create the component generators...
    for (Components::component_iterator comp = dom.components().component().begin(); comp != dom.components().component().end(); comp++) {
        Component& theComponent = (*comp);
        ComponentGenerator* componentGenerator = new ComponentGenerator(theComponent, licence);
        components.append(componentGenerator);
    }

}


ComponentExtensionGenerator::~ComponentExtensionGenerator() {
//    for (QVector<DependencyGenerator *>::iterator dep = dependencyGenerators.begin(); dep != dependencyGenerators.end(); dep++)
//        delete dep;

}


void ComponentExtensionGenerator::generateExtensionClass(QString directory) {
    // C generate actions
    for (auto& component : components) {
        component->generateFiles(directory);
    }

}

void ComponentExtensionGenerator::writeHFile(QString directory) {
    QString className = ClassNameHandler::getClassName(this->name);
    QFile initHFile(":/resources/ComponentExtension.h.in");
    initHFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream inh(&initHFile);

    QFileInfo extFileHPath;
    extFileHPath.setFile(directory, className + ".h");
    QFile extFileH(extFileHPath.absoluteFilePath());
    if (! extFileH.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QString msg = "Exception from extension generation: \n    Cannot write on file " + extFileHPath.fileName() + "\n";
        throw (msg);
    }
    QTextStream outh(&extFileH);

    QString headdef = className.toUpper();
    QString text;
    do {
        text = inh.readLine();
        text.replace(QRegularExpression("@LICENCE@"), licence);
        text.replace(QRegularExpression("@HEADDEF@"), headdef);
        text.replace(QRegularExpression("@EXTENSIONCLASSNAME@"), className);
        text.replace(QRegularExpression("@EXTENSIONNAME@"), this->name);
        text.replace(QRegularExpression("@EXTENSIONDESCRIPTION@"), this->description);
        outh << text << Qt::endl;
    }
    while (! text.isNull());
    extFileH.close();
    initHFile.close();
}

void ComponentExtensionGenerator::writeCFile(QString directory) {
    // build class name
    QString className = ClassNameHandler::getClassName(this->name);
    std::cout << "Generating ComponentExtension \"" << className.toStdString() << "\"..." << std::endl;

    QFile initCFile(":/resources/ComponentExtension.cpp.in");
    initCFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream inc(&initCFile);

    QFileInfo extFileCPath;
    extFileCPath.setFile(directory, className + ".cpp");
    QFile extFileC(extFileCPath.absoluteFilePath());
    if (! extFileC.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QString msg = "Exception from extension generation: \n    Cannot write on file " + extFileCPath.fileName() + "\n";
        throw (msg);
    }
    QTextStream outc(&extFileC);

    QString text;
    QVector<ComponentGenerator*>::const_iterator it;

    // Find file suffixes from components
    QStringList suffixesList;
    for (it = components.constBegin(); it < components.constEnd(); ++it) {
        QStringList componentSuffixes = (*it)->getSuffixesList();
        suffixesList << componentSuffixes;
    }

    do {
        text = inc.readLine();

        text.replace(QRegularExpression("@LICENCE@"), licence);

        text.replace(QRegularExpression("@EXTENSIONCLASSNAME@"), className);
        text.replace(QRegularExpression("@EXTENSIONCLASSNAMELOW@"), className.toLower());

        if (text.contains(QRegularExpression("@INCLUDECOMPONENTSHEADERS@"))) {
            for (it = components.constBegin(); it < components.constEnd(); ++it) {
                outc << "#include \"" << (*it)->getClassName() << ".h\"" << Qt::endl;
            }
        }

        else {
            if (text.contains(QRegularExpression("@EXTENSIONSDEF@"))) {
                for (QStringList::const_iterator suffixe = suffixesList.constBegin(); suffixe != suffixesList.constEnd(); ++suffixe) {
                    outc << "    ext << \"" << (*suffixe) << "\";" << Qt::endl;
                }

            }
            else {
                if (text.contains(QRegularExpression("@IF_NO_SUFFIX@"))) {
                    text = inc.readLine();
                    if (suffixesList.isEmpty()) {
                        // Copy what is between @IF_NO_SUFFIX@ and @ELSE_IF_ONLY_ONE_SUFFIX@
                        while (! text.contains(QRegularExpression("@ELSE_IF_ONLY_ONE_SUFFIX@"))) {
                            outc << text << Qt::endl;
                            text = inc.readLine();
                        }

                        // Disregard what is between @ELSE_IF_ONLY_ONE_SUFFIX@ and @END_IF_NO_SUFFIX@
                        while (! text.contains(QRegularExpression("@END_IF_NO_SUFFIX@"))) {
                            text = inc.readLine();
                        }
                    }
                    else {
                        if (suffixesList.size() == 1) {
                            // Disregard what is between @IF_NO_SUFFIX@ and @ELSE_IF_ONLY_ONE_SUFFIX@
                            while (! text.contains(QRegularExpression("@ELSE_IF_ONLY_ONE_SUFFIX@"))) {
                                text = inc.readLine();
                            }
                            text = inc.readLine();
                            // Find the component class corresponding to the extension
                            QString componentClassName = findComponentClass(suffixesList.at(0));

                            // Copy what is between @ELSE_IF_ONLY_ONE_SUFFIX@ and @ELSE_IF_SEVERAL_SUFFIX@
                            while (! text.contains(QRegularExpression("@ELSE_IF_SEVERAL_SUFFIX@"))) {
                                text.replace(QRegularExpression("@TOPLEVELCOMPONENTCLASSNAME@"), componentClassName);
                                outc << text;
                                text = inc.readLine();
                            }

                            // Disregard what is between @ELSE_IF_SEVERAL_SUFFIX@ and @END_IF_NO_SUFFIX@
                            while (! text.contains(QRegularExpression("@END_IF_NO_SUFFIX@"))) {
                                text = inc.readLine();
                            }
                        }

                        else { // several suffixes
                            // Disregard what is between @IF_NO_SUFFIX@ and @ELSE_IF_SEVERAL_SUFFIX@
                            while (! text.contains(QRegularExpression("@ELSE_IF_SEVERAL_SUFFIX@"))) {
                                text = inc.readLine();
                            }

                            // Copy what is between @ELSE_IF_SEVERAL_SUFFIX@ and @END_IF_NO_SUFFIX@
                            while (! text.contains(QRegularExpression("@END_IF_NO_SUFFIX@"))) {
                                text = inc.readLine();

                                text.replace(QRegularExpression("@EXTENSIONCLASSNAME@"), className);

                                if (text.contains(QRegularExpression("@EXTENSIONSDEF@"))) {
                                    for (QStringList::const_iterator suffixe = suffixesList.constBegin(); suffixe != suffixesList.constEnd(); ++suffixe) {
                                        outc << "    ext << \"" << (*suffixe) << "\";" << Qt::endl;
                                    }
                                }
                                else {
                                    if (text.contains(QRegularExpression("@CASES@"))) {
                                        int suffixeIdx;
                                        for (suffixeIdx = 0; suffixeIdx < suffixesList.size(); suffixeIdx ++) {
                                            QString componentClassName = findComponentClass(suffixesList.at(suffixeIdx));

                                            outc << "    case  " << suffixeIdx << ": " << Qt::endl;
                                            outc << "        component = new " << componentClassName << "(fileName);" << Qt::endl;
                                            outc << "        break;" << Qt::endl;
                                        }
                                    }
                                    else {
                                        if (! text.contains(QRegularExpression("@END_IF_NO_SUFFIX@"))) {
                                            outc << text << Qt::endl;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // current line
                    outc << Qt::endl;
                }

                else {
                    outc << text << Qt::endl;
                }
            }
        }
    }
    while (! text.isNull());

    extFileC.close();
    initCFile.close();
}


QString ComponentExtensionGenerator::findComponentClass(QString suffix) {
    QString className;
    int i = 0;

    while ((i < components.size()) && (! components.at(i)->getSuffixesList().contains(suffix))) {
        i++;
    }
    if (i < components.size()) {
        className = components.at(i)->getClassName();
    }

    return className;
}

void ComponentExtensionGenerator::generateTestDataFiles(QString directory, QString testDataDirName) {
    QStringList suffixesList;
    for (QVector<ComponentGenerator*>::const_iterator it = components.constBegin(); it < components.constEnd(); ++it) {
        QStringList componentSuffixes = (*it)->getSuffixesList();
        suffixesList << componentSuffixes;
    }

    if (! suffixesList.empty()) {
        QDir currentDirectory(directory);
        currentDirectory.mkdir(testDataDirName);
        currentDirectory.cd(testDataDirName);

        for (int suffixeIdx = 0; suffixeIdx < suffixesList.size(); suffixeIdx ++) {
            QString suffix = suffixesList.at(suffixeIdx);
            QString testFileName = currentDirectory.absolutePath() + "/" + "empty." + suffix;
            QString componentClassName = findComponentClass(suffixesList.at(suffixeIdx));

            QFile oFile(testFileName);
            if (! oFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
                std::cerr << "Could not create Test file " << testFileName.toStdString() << std::endl;
                return;
            }
            QTextStream outc(&oFile);
            outc << "// This is an empty test file to test " << componentClassName << " Component" << Qt::endl;
        }
    }

}

