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

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

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

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

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

using namespace cepcoreschema;

ActionGenerator::ActionGenerator(QString xmlFileName, QString licence) throw (QString) {
    this->licence = licence;
    QFileInfo xmlFile(xmlFileName);
    if ((! xmlFile.exists()) || (! xmlFile.isFile())) {
        QString msg = "Exception from ActionExtension generation: \n     The file " + xmlFileName +  " does not exist or is not a file...\n";
        throw (msg);
    }
    try {
        std::string xmlFileStr = xmlFileName.toStdString();
        std::auto_ptr<Action> domAction = action(xmlFileStr, xml_schema::flags::dont_validate);
        createFromDom(*domAction);
    } catch (...) {
        QString msg = "Action XML file not valid.";
        throw msg;
    }
}

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) {
    std::cout << "Creating action..." << std::endl;
    this->name          = QString(dom.name().c_str());
    this->description   = QString(dom.description().c_str()).simplified();
    this->className     = ClassNameHandler::getClassName(this->name);
    this->componentName = QString(dom.component().c_str());
    
    // classification
    // 1- Family
    this->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
    this->isItkFilter = false;
    if (dom.classification().itkFilter().present()) {
        this->isItkFilter = true;
        this->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));
        }

    }
}

ActionGenerator::~ActionGenerator() {
}

QString ActionGenerator::getClassName() const {
    return this->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);
    }
    std::cout << "generating " << extFilePath.absolutePath().toStdString() << "/" << className.toStdString() << ".h" << std::endl;
    QTextStream out(&extFile);
    QString text;
    do {
        text = in.readLine();
        text.replace(QRegExp("@LICENCE@"), licence);
        text.replace(QRegExp("@HEADDEF@"), className.toUpper());
        text.replace(QRegExp("@COMPONENTNAME@"), componentName);
        text.replace(QRegExp("@COMPONENTNAMESPACE@"), getComponentNamespace());
        text.replace(QRegExp("@ACTIONCLASSNAME@"), className);

        if (text.contains(QRegExp("@IF_NOCOMP@"))) {
            if (componentName != "") {
                do {
                    text = in.readLine();
                } while ( (! text.contains(QRegExp("@ELSEIF_NOCOMP@"))) &&
                          (! text.contains(QRegExp("@END_NOCOMP@"))));
            }
        } else if (text.contains(QRegExp("@ELSEIF_NOCOMP@"))) {
            if (componentName == "") {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@END_NOCOMP@")));
            }
        } else if (text.contains(QRegExp("@END_NOCOMP@"))) {
            // Go to next line...
        } else if (text.contains(QRegExp("@IF_DEFCOMPONENT@"))) {
            if ((componentName == "Component") || (componentName == "")) {
                do {
                    text = in.readLine();
                } while ( (! text.contains(QRegExp("@ENDIF_DEFCOMPONENT@"))) &&
                          (! text.contains(QRegExp("@ELSEIF_DEFCOMPONENT@"))));
            }
        } else if ((text.contains(QRegExp("@ELSEIF_DEFCOMPONENT@")))) {
            if ((componentName != "Component") && (componentName != "")) {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_DEFCOMPONENT@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_DEFCOMPONENT@"))) {
            // Go to next line...
        } else if (text.contains(QRegExp("@IF_IMAGECOMPONENT@"))) {
            if ((componentName != "ImageComponent") || (isItkFilter)) {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_IMAGECOMPONENT@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_IMAGECOMPONENT@"))) {
            // Go to the next line...
        } else if (text.contains(QRegExp("@IF_ITKFILTER@"))) {
            if (! isItkFilter) {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_ITKFILTER@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_ITKFILTER@"))) {
            // Go to the next line...
        } else {
            out << text << 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(QVector<ParameterGenerator *>::const_iterator it =  actionParameters.begin(); it != actionParameters.end(); it++) {
        if ((*it)->needsAdditionalInclude()) {
            additionalIncludes.insert((*it)->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(QRegExp("@LICENCE@"), licence);
        text.replace(QRegExp("@COMPONENTNAME@"), componentName);
        text.replace(QRegExp("@ACTIONCLASSNAME@"), className);
        text.replace(QRegExp("@ACTION_NAME@"), name);
        text.replace(QRegExp("@ACTION_DESCRIPTION@"), description);
        text.replace(QRegExp("@FAMILY@"), family);
        if (text.contains(QRegExp("@ADDITIONAL_INCLUDES@"))) {
            for (QSet<QString>::const_iterator incl = additionalIncludes.begin(); incl != additionalIncludes.end(); incl++) {
                out << (*incl) << endl;
            }
        } else if (text.contains(QRegExp("@IF_NOCOMP@"))) {
            if (componentName != "") {
                do {
                    text = in.readLine();
                } while ( (! text.contains(QRegExp("@ELSEIF_NOCOMP@"))) &&
                          (! text.contains(QRegExp("@END_NOCOMP@"))));
            }
        } else if (text.contains(QRegExp("@ELSEIF_NOCOMP@"))) {
            if (componentName == "") {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@END_NOCOMP@")));
            }
        } else if (text.contains(QRegExp("@END_NOCOMP@"))) {
            // Go to next line...
        } else if (text.contains(QRegExp("@IF_DEFCOMPONENT@"))) {
            if ((componentName == "Component") || (componentName == "")) {
                do {
                    text = in.readLine();
                } while ( (! text.contains(QRegExp("@ENDIF_DEFCOMPONENT@"))) &&
                          (! text.contains(QRegExp("@ELSEIF_DEFCOMPONENT@"))));
            }
        } else if ((text.contains(QRegExp("@ELSEIF_DEFCOMPONENT@")))) {
            if (componentName != "Component") {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_DEFCOMPONENT@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_DEFCOMPONENT@"))) {
            // Go to next line...
        } else if (text.contains(QRegExp("@IF_IMAGECOMPONENT@"))) {
            if ((componentName != "ImageComponent") || (isItkFilter)) {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_IMAGECOMPONENT@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_IMAGECOMPONENT@"))) {
            // Go to the next line...
        } else if (text.contains(QRegExp("@IF_ITKFILTER@"))) {
            if (! isItkFilter) {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_ITKFILTER@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_ITKFILTER@"))) {
            // Go to the next line...
        } else if (text.contains(QRegExp("@BEGIN_ADDTAGS@"))) {
            text = in.readLine();
            if (tags.size()) {
                QStringList addTheTags;
                while (! text.contains(QRegExp("@END_ADDTAG@")) && ! text.contains(QRegExp("@ELSE_ADDTAG@")) ) {
                    for (int tagIdx = 0; tagIdx < tags.size(); tagIdx++) {
                        QString textTmp = text;
                        textTmp.replace(QRegExp("@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) << 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 << endl;
                        text = in.readLine();
                    }
                }
                text = in.readLine();
            }
        } else if (text.contains(QRegExp("@IF_ADDPROPERTIES@"))) {
            text = in.readLine();
            while (! text.contains(QRegExp("@ENDIF_ADDPROPERTIES@"))) {
                if ( ! actionParameters.isEmpty()) {
                    out << text << endl;
                }
                text = in.readLine();
            }
        } else if (text.contains(QRegExp("@BEGIN_ADDPROPERTIES@"))) {
            text = in.readLine();
            if (actionParameters.size() > 0) {
                QStringList addTheProps;
                while (! text.contains(QRegExp("@END_ADDPROPERTIES@")) &&
                        ! text.contains(QRegExp("@ELSE_ADDPROPERTIES@")) ) {
                    for (QVector<ParameterGenerator *>::const_iterator it =  actionParameters.begin();
                            it != actionParameters.end(); it++) {
                        ParameterGenerator * param = (*it);
                        QString textTmp = text;

                        textTmp.replace(QRegExp("@PROPERTY_TYPE@"), param->getType());
                        textTmp.replace(QRegExp("@PROPERTY_NAME@"), param->getName());
                        textTmp.replace(QRegExp("@PROPERTY_CPP_NAME@"), param->getCppName());
                        textTmp.replace(QRegExp("@PROPERTY_NAME_STRING@"), param->getToString());
                        textTmp.replace(QRegExp("@PROPERTY_QVARIANT@"), param->getPropertyQVariant());
                        textTmp.replace(QRegExp("@PROPERTY_VALUE@"), param->getDefaultValue());
                        textTmp.replace(QRegExp("@PROPERTY_TOTYPE@"), param->getQVariantConversion());
                        textTmp.replace(QRegExp("@PROPERTY_DESCRIPTION@"), param->getDescription());
                        textTmp.replace(QRegExp("@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) << 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 << endl;
                        text = in.readLine();
                    }
                }
            }
        } else {
            out << text << 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(QRegExp("@LICENCE@"), licence);
        text.replace(QRegExp("@ACTIONCLASSNAME@"), className);
        text.replace(QRegExp("@OUTPUTTYPE@"), itkFilterOutputType);
        out << text << endl;
    } while (! text.isNull());

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