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

// includes from cepcoreschema
#include <Action.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 "ParameterGenerator.h"
#include "ClassNameHandler.h"

using namespace cepcoreschema;

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

ActionGenerator::ActionGenerator(Action& domAction, QString licence) {
    this->licence = licence;
    createFromDom(domAction);
}

// -------------------- getComponentNamespace --------------------
QString ActionGenerator::getComponentNamespace() {
    if (componentName == "Component"
            || componentName == "SimplisticComponent"
            || componentName == "MeshComponent"
            || componentName == "ImageComponent"
            || componentName == "ImageAcquisitionComponent") {
        return "camitk::";
    }
    else {
        return "";
    }
}

// -------------------- createFromDom --------------------
void ActionGenerator::createFromDom(Action& dom) {
    name          = QString(dom.name().c_str());
    description   = QString(dom.description().c_str()).simplified();
    className     = ClassNameHandler::getClassName(name);
    componentName = QString(dom.component().c_str());
    std::cout << "Generating Action \"" << className.toStdString() << "\"..." << std::endl;

    // classification
    // 1- Family
    family           = QString(dom.classification().family().c_str());
    // 2- Checking tags
    for (Classification::tag_const_iterator it = dom.classification().tag().begin(); it != dom.classification().tag().end(); it++) {
        tags << QString((*it).c_str());
    }
    // 3- Checking  itk flag
    isItkFilter = false;
    if (dom.classification().itkFilter().present()) {
        isItkFilter = true;
        itkFilterOutputType = QString(dom.classification().itkFilter().get().outputType().c_str());
    }

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

    }
}

QString ActionGenerator::getClassName() const {
    return className;
}


void ActionGenerator::generateFiles(QString directoryName) {
    writeHFile(directoryName);
    writeCFile(directoryName);
    if (isItkFilter) {
        writeSpecialItkFile(directoryName);
    }
}

void ActionGenerator::writeHFile(QString directoryName) {

    // Setting the right input and output files
    QFile initFile(":/resources/Action.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 extension 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("@COMPONENTNAME@"), componentName);
        text.replace(QRegularExpression("@COMPONENTNAMESPACE@"), getComponentNamespace());
        text.replace(QRegularExpression("@ACTIONCLASSNAME@"), className);

        if (text.contains(QRegularExpression("@IF_NOCOMP@"))) {
            if (componentName != "") {
                do {
                    text = in.readLine();
                }
                while ((! text.contains(QRegularExpression("@ELSEIF_NOCOMP@"))) &&
                        (! text.contains(QRegularExpression("@END_NOCOMP@"))));
            }
        }
        else if (text.contains(QRegularExpression("@ELSEIF_NOCOMP@"))) {
            if (componentName == "") {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@END_NOCOMP@")));
            }
        }
        else if (text.contains(QRegularExpression("@END_NOCOMP@"))) {
            // Go to next line...
        }
        else if (text.contains(QRegularExpression("@IF_DEFCOMPONENT@"))) {
            if ((componentName == "Component") || (componentName == "")) {
                do {
                    text = in.readLine();
                }
                while ((! text.contains(QRegularExpression("@ENDIF_DEFCOMPONENT@"))) &&
                        (! text.contains(QRegularExpression("@ELSEIF_DEFCOMPONENT@"))));
            }
        }
        else if ((text.contains(QRegularExpression("@ELSEIF_DEFCOMPONENT@")))) {
            if ((componentName != "Component") && (componentName != "")) {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_DEFCOMPONENT@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_DEFCOMPONENT@"))) {
            // Go to next line...
        }
        else if (text.contains(QRegularExpression("@IF_IMAGECOMPONENT@"))) {
            if ((componentName != "ImageComponent") || (isItkFilter)) {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_IMAGECOMPONENT@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_IMAGECOMPONENT@"))) {
            // Go to the next line...
        }
        else if (text.contains(QRegularExpression("@IF_ITKFILTER@"))) {
            if (! isItkFilter) {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_ITKFILTER@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_ITKFILTER@"))) {
            // Go to the next line...
        }
        else {
            out << text << Qt::endl;
        }
    }
    while (! text.isNull());

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


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

    // Opening init file
    QFile initFile(":/resources/Action.cpp.in");
    initFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream in(&initFile);

    // Opening destination file
    QFileInfo extFilePath;
    extFilePath.setFile(directoryName, className + ".cpp");
    QFile extFile(extFilePath.absoluteFilePath());
    if (! extFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QString msg = "Exception from extension generation: \n    Cannot write on file " + extFilePath.fileName() + "\n";
        throw (msg);
    }
    QTextStream out(&extFile);

    // Parsing init file to fill destinaiton file.
    QString text;
    do {
        text = in.readLine();
        text.replace(QRegularExpression("@LICENCE@"), licence);
        text.replace(QRegularExpression("@COMPONENTNAME@"), componentName);
        text.replace(QRegularExpression("@ACTIONCLASSNAME@"), className);
        text.replace(QString("@ACTION_NAME@"), name);

        // transform action name to_camel_case_name (cf https://wiki.qt.io/Converting_Strings_from_and_to_Camel_Case)
        QRegularExpression regExp1 {"(.)([A-Z][a-z]+)"};
        QRegularExpression regExp2 {"([a-z0-9])([A-Z])"};
        QString camelCaseActionName = name;
        camelCaseActionName.simplified().replace(" ", "");
        camelCaseActionName.replace(regExp1, "\\1_\\2");
        camelCaseActionName.replace(regExp2, "\\1_\\2");
        camelCaseActionName = camelCaseActionName.toLower();
        text.replace(QRegularExpression("@CAMEL_CASE_ACTION_NAME@"), camelCaseActionName);
        text.replace(QRegularExpression("@ACTION_DESCRIPTION@"), description);
        text.replace(QRegularExpression("@FAMILY@"), family);
        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_NOCOMP@"))) {
            if (componentName != "") {
                do {
                    text = in.readLine();
                }
                while ((! text.contains(QRegularExpression("@ELSEIF_NOCOMP@"))) &&
                        (! text.contains(QRegularExpression("@END_NOCOMP@"))));
            }
        }
        else if (text.contains(QRegularExpression("@ELSEIF_NOCOMP@"))) {
            if (componentName == "") {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@END_NOCOMP@")));
            }
        }
        else if (text.contains(QRegularExpression("@END_NOCOMP@"))) {
            // Go to next line...
        }
        else if (text.contains(QRegularExpression("@IF_DEFCOMPONENT@"))) {
            if ((componentName == "Component") || (componentName == "")) {
                do {
                    text = in.readLine();
                }
                while ((! text.contains(QRegularExpression("@ENDIF_DEFCOMPONENT@"))) &&
                        (! text.contains(QRegularExpression("@ELSEIF_DEFCOMPONENT@"))));
            }
        }
        else if ((text.contains(QRegularExpression("@ELSEIF_DEFCOMPONENT@")))) {
            if (componentName != "Component") {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_DEFCOMPONENT@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_DEFCOMPONENT@"))) {
            // Go to next line...
        }
        else if (text.contains(QRegularExpression("@IF_IMAGECOMPONENT@"))) {
            if ((componentName != "ImageComponent") || (isItkFilter)) {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_IMAGECOMPONENT@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_IMAGECOMPONENT@"))) {
            // Go to the next line...
        }
        else if (text.contains(QRegularExpression("@IF_ITKFILTER@"))) {
            if (! isItkFilter) {
                do {
                    text = in.readLine();
                }
                while (! text.contains(QRegularExpression("@ENDIF_ITKFILTER@")));
            }
        }
        else if (text.contains(QRegularExpression("@ENDIF_ITKFILTER@"))) {
            // Go to the next line...
        }
        else if (text.contains(QRegularExpression("@BEGIN_ADDTAGS@"))) {
            text = in.readLine();
            if (tags.size()) {
                QStringList addTheTags;
                while (! text.contains(QRegularExpression("@END_ADDTAG@")) && ! text.contains(QRegularExpression("@ELSE_ADDTAG@"))) {
                    for (int tagIdx = 0; tagIdx < tags.size(); tagIdx++) {
                        QString textTmp = text;
                        textTmp.replace(QRegularExpression("@TAG@"), tags[tagIdx]);
                        addTheTags << textTmp;
                    }
                    text = in.readLine();
                }
                if (text.contains("@ELSE_ADDTAG@")) {
                    while (! text.contains("@END_ADDTAG@")) {
                        text = in.readLine();
                    }
                }
                for (int t = 0; t < addTheTags.size(); t++) {
                    out << addTheTags.at(t) << Qt::endl;
                }
            }
            else {
                while ((! text.contains("@ELSE_ADDTAG@")) && (! text.contains("@END_ADDTAG@"))) {
                    text = in.readLine();
                }
                if (text.contains("@ELSE_ADDTAG@")) {
                    text = in.readLine();
                    while (! text.contains("@END_ADDTAG@")) {
                        out << text << Qt::endl;
                        text = in.readLine();
                    }
                }
                text = in.readLine();
            }
        }
        else if (text.contains(QRegularExpression("@IF_ADDPROPERTIES@"))) {
            text = in.readLine();
            while (! text.contains(QRegularExpression("@ENDIF_ADDPROPERTIES@"))) {
                if (! actionParameters.isEmpty()) {
                    out << text << Qt::endl;
                }
                text = in.readLine();
            }
        }
        else if (text.contains(QRegularExpression("@BEGIN_ADDPROPERTIES@"))) {
            text = in.readLine();
            if (actionParameters.size() > 0) {
                QStringList addTheProps;
                while (! text.contains(QRegularExpression("@END_ADDPROPERTIES@")) &&
                        ! text.contains(QRegularExpression("@ELSE_ADDPROPERTIES@"))) {
                    for (auto param : std::as_const(actionParameters)) {
                        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;
                    }
                    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();

}

void ActionGenerator::writeSpecialItkFile(QString directoryName) {
    QString initFileName;
    if (itkFilterOutputType == "Same as Input") {
        initFileName = QString(":/resources/ActionImplementation.sai.cpp.in");
    }
    else {
        initFileName = QString(":/resources/ActionImplementation.cpp.in");
    }

    QFile initFile(initFileName);
    initFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream in(&initFile);

    QFileInfo extFilePath;
    extFilePath.setFile(directoryName, className + ".impl");
    QFile extFile(extFilePath.absoluteFilePath());
    if (! extFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QString msg = "Exception from extension 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("@ACTIONCLASSNAME@"), className);
        text.replace(QRegularExpression("@OUTPUTTYPE@"), itkFilterOutputType);
        out << text << Qt::endl;
    }
    while (! text.isNull());

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