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

// includes from cepcoreschema
#include <Component.hxx>

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

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

// Local includes
#include "ClassNameHandler.h"
#include "ParameterGenerator.h"

using namespace cepcoreschema;

ComponentGenerator::ComponentGenerator(QString xmlFileName, QString licence) {
    this->licence = licence;

    QFileInfo xmlFile(xmlFileName);
    if ((! xmlFile.exists()) || (! xmlFile.isFile())) {
        throw std::invalid_argument("I/O exception during component file generation:\nFile " + xmlFileName.toStdString() + " does not exist or is not a file...\n");
    }
    try {
        std::string xmlFileStr = xmlFileName.toStdString();
        std::unique_ptr<Component> domComponent = component(xmlFileStr, xml_schema::flags::dont_validate);
        createFromDom(*domComponent);
    }
    catch (...) {
        throw std::invalid_argument("I/O exception during component file generation:\nFile " + xmlFileName.toStdString() + " is not valid...\n");
    }
}

ComponentGenerator::ComponentGenerator(Component& domComponent, QString licence) {
    this->licence = licence;
    createFromDom(domComponent);
}

void ComponentGenerator::createFromDom(Component& dom) {
    name          = QString(dom.name().c_str());
    description   = QString(dom.description().c_str()).simplified();
    className     = ClassNameHandler::getClassName(this->name);
    std::cout << "Generating Component \"" << className.toStdString() << "\"..." << std::endl;

    // parameters
    if (dom.properties().present()) {
        Parameters theProperties = dom.properties().get();
        for (Parameters::parameter_iterator param = theProperties.parameter().begin(); param != theProperties.parameter().end(); param++) {
            Parameter& oneParameter = (*param);
            componentProperties.append(new ParameterGenerator(oneParameter));
        }

    }

    // Suffixes
    for (Component::fileSuffix_const_iterator suf = dom.fileSuffix().begin(); suf != dom.fileSuffix().end(); suf++) {
        this->suffixesList << suf->c_str();
    }

    // Representation
    this->representation = dom.representation().c_str();
}

QString ComponentGenerator::getClassName() const {
    return this->className;
}

QStringList ComponentGenerator::getSuffixesList() {
    return suffixesList;
}

void ComponentGenerator::generateFiles(QString directoryName) {
    writeHFile(directoryName);
    writeCFile(directoryName);
}

QString ComponentGenerator::getParentClassName() {
    QStringList possibleParentClassNames;
    possibleParentClassNames << "Image" << "Mesh" << "None";
    QString parentClassName;
    switch (possibleParentClassNames.indexOf(this->representation)) {
        case 0: // Image
            parentClassName = "ImageComponent";
            break;
        case 1: // Mesh
            parentClassName = "MeshComponent";
            break;
        default:
        case 2: // None
            parentClassName = "Component";
            break;
    }

    return parentClassName;
}
void ComponentGenerator::writeHFile(QString directoryName) {

    // Setting the right input and output files
    QFile initFile(":/resources/Component.h.in");
    initFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream in(&initFile);

    QFileInfo extFilePath;
    extFilePath.setFile(directoryName, className + ".h");
    QFile extFile(extFilePath.absoluteFilePath());

    if (! extFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QString msg = "Exception from component generation: \n    Cannot write on file " + extFilePath.fileName() + "\n";
        throw (msg);
    }

    QTextStream out(&extFile);
    QString text;
    do {
        text = in.readLine();
        text.replace(QRegularExpression("@LICENCE@"), licence);
        text.replace(QRegularExpression("@HEADDEF@"), className.toUpper());
        text.replace(QRegularExpression("@COMPONENTCLASSNAME@"), className);
        text.replace(QRegularExpression("@PARENTCLASSNAME@"), getParentClassName());



        if (text.contains(QRegularExpression("@IF_NONE@"))) {
            if (representation != "None") {
                do {
                    text = in.readLine();
                }
                while ((! text.contains(QRegularExpression("@ELSEIF_NONE@"))) &&
                        (! text.contains(QRegularExpression("@ENDIF_NONE@"))));
            }

        }
        else if (text.contains(QRegularExpression("@ELSEIF_NONE@"))) {
            if (representation == "None") {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_NONE@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_NONE@"))) {
            // Go to next line...
        }
        else {
            out << text << Qt::endl;
        }

    }
    while (! text.isNull());

    extFile.close();
    initFile.close();
}


void ComponentGenerator::writeCFile(QString directoryName) {
    // Computing variable
    QString parentClassName = getParentClassName();


    // Additional includes needed by parameter types
    QSet<QString> additionalIncludes;
    // here we use a QSet to avoid the repetion of includes
    for (auto componentProperty : std::as_const(componentProperties)) {
        if (componentProperty->needsAdditionalInclude()) {
            additionalIncludes.insert(componentProperty->getAdditionalInclude());
        }
    }

    // Setting the right input and output files
    QFile initFile(":/resources/Component.cpp.in");
    initFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream in(&initFile);

    QFileInfo extFilePath;
    extFilePath.setFile(directoryName, className + ".cpp");
    QFile extFile(extFilePath.absoluteFilePath());

    if (! extFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QString msg = "Exception from component generation: \n    Cannot write on file " + extFilePath.fileName() + "\n";
        throw (msg);
    }

    QTextStream out(&extFile);
    QString text;

    do {
        text = in.readLine();
        text.replace(QRegularExpression("@LICENCE@"), licence);
        text.replace(QRegularExpression("@HEADDEF@"), className.toUpper());
        text.replace(QRegularExpression("@COMPONENTCLASSNAME@"), className);
        text.replace(QRegularExpression("@PARENTCLASSNAME@"), parentClassName);

        if (text.contains(QRegularExpression("@ADDITIONAL_INCLUDES@"))) {
            for (QSet<QString>::const_iterator incl = additionalIncludes.constBegin(); incl != additionalIncludes.constEnd(); ++incl) {
                out << (*incl) << Qt::endl;
            }
        }
        else if (text.contains(QRegularExpression("@IF_NONE@"))) {
            // ignore lines until line @ELSEIF_NONE@ or line @ENDIF_NONE@
            // if there is a representation and this is not a subclass of ImageComponent
            if (representation != "None" && representation != "Image") {
                do {
                    text = in.readLine();
                }
                while (!(text.contains(QRegularExpression("@ELSEIF_NONE@")) ||
                         text.contains(QRegularExpression("@ENDIF_NONE@"))));
            }
        }
        else if (text.contains(QRegularExpression("@ELSEIF_NONE@"))) {
            // ignore lines until line @ENDIF_NONE@
            // if there is no representation or if this is a subclass of ImageComponent
            if (representation == "None" || representation == "Image") {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_NONE@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_NONE@"))) {
            // Go to next line...
        }
        else if (text.contains(QRegularExpression("@IF_MESH@"))) {
            if (representation != "Mesh") {
                do {
                    text = in.readLine();
                }
                while (!(text.contains(QRegularExpression("@ELSEIF_MESH@")) ||
                         text.contains(QRegularExpression("@ENDIF_MESH@"))));
            }
        }
        else if (text.contains(QRegularExpression("@ELSEIF_MESH@"))) {
            if (representation == "Mesh") {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_MESH@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_MESH@"))) {
            // Go to next line...
        }
        else if (text.contains(QRegularExpression("@IF_IMAGE@"))) {
            if (representation != "Image") {
                do {
                    text = in.readLine();
                }
                while (!(text.contains(QRegularExpression("@ELSEIF_IMAGE@")) ||
                         text.contains(QRegularExpression("@ENDIF_IMAGE@"))));
            }
        }
        else if (text.contains(QRegularExpression("@ELSEIF_IMAGE@"))) {
            if (representation == "Image") {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_IMAGE@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_IMAGE@"))) {
            // Go to next line...
        }
        else if (text.contains(QRegularExpression("@IF_ADDPROPERTIES@"))) {
            text = in.readLine();
            while (! text.contains(QRegularExpression("@ENDIF_ADDPROPERTIES@"))) {
                if (! componentProperties.isEmpty()) {
                    out << text << Qt::endl;
                }
                text = in.readLine();
            }
        }
        else if (text.contains(QRegularExpression("@BEGIN_ADDPROPERTIES@"))) {
            text = in.readLine();
            if (componentProperties.size() > 0) {
                QStringList addTheProps;
                for (auto param : std::as_const(componentProperties)) {
                    QString textTmp = text;

                    textTmp.replace(QRegularExpression("@PROPERTY_TYPE@"), param->getType());
                    textTmp.replace(QRegularExpression("@PROPERTY_NAME@"), param->getName());
                    textTmp.replace(QRegularExpression("@PROPERTY_CPP_NAME@"), param->getCppName());
                    textTmp.replace(QRegularExpression("@PROPERTY_VALUE_AS_QSTRING@"), param->getToString());
                    textTmp.replace(QRegularExpression("@PROPERTY_QVARIANT@"), param->getPropertyQVariant());
                    textTmp.replace(QRegularExpression("@PROPERTY_VALUE@"), param->getDefaultValue());
                    textTmp.replace(QRegularExpression("@PROPERTY_TOTYPE@"), param->getQVariantConversion());
                    textTmp.replace(QRegularExpression("@PROPERTY_DESCRIPTION@"), param->getDescription());
                    textTmp.replace(QRegularExpression("@PROPERTY_UNIT@"), param->getUnit());

                    addTheProps << textTmp;
                }
                while (! text.contains(QRegularExpression("@END_ADDPROPERTIES@")) &&
                        ! text.contains(QRegularExpression("@ELSE_ADDPROPERTIES@"))) {
                    text = in.readLine();
                }
                if (text.contains("@ELSE_ADDPROPERTIES@")) {
                    while (! text.contains("@END_ADDPROPERTIES@")) {
                        text = in.readLine();
                    }
                }
                for (int p = 0; p < addTheProps.size(); p++) {
                    out << addTheProps.at(p) << Qt::endl;
                }
            }
            else {
                while ((! text.contains("@ELSE_ADDPROPERTIES@")) && (! text.contains("@END_ADDPROPERTIES@"))) {
                    text = in.readLine();
                }
                if (text.contains("@ELSE_ADDPROPERTIES@")) {
                    text = in.readLine();
                    while (! text.contains("@END_ADDPROPERTIES@")) {
                        out << text << Qt::endl;
                        text = in.readLine();
                    }
                }
            }
        }
        else {
            out << text << Qt::endl;
        }
    }
    while (! text.isNull());

    extFile.close();
    initFile.close();

}

