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

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

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


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

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

using namespace cepcoreschema;

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

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

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

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


    // 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();
}

ComponentGenerator::~ComponentGenerator() {
}

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);
    }
    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("@COMPONENTCLASSNAME@"), className);
        text.replace(QRegExp("@PARENTCLASSNAME@"), getParentClassName());



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

        } else if (text.contains(QRegExp("@ELSEIF_NONE@"))) {
            if (representation == "None") {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_NONE@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_NONE@"))) {
            // Go to next line...
        } else {
            out << text << 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(QVector<ParameterGenerator *>::const_iterator it =  componentProperties.begin(); it != componentProperties.end(); it++) {
        if ((*it)->needsAdditionalInclude()) {
            additionalIncludes.insert((*it)->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);
    }
    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("@COMPONENTCLASSNAME@"), className);
        text.replace(QRegExp("@PARENTCLASSNAME@"), parentClassName);

        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_NONE@"))) {
            if (representation != "None") {
                do {
                    text = in.readLine();
                } while ( !( text.contains(QRegExp("@ELSEIF_NONE@")) ||
                             text.contains(QRegExp("@ENDIF_NONE@"))));
            }
        } else if (text.contains(QRegExp("@ELSEIF_NONE@"))) {
            if (representation == "None") {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_NONE@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_NONE@"))) {
            // Go to next line...
        } else if (text.contains(QRegExp("@IF_MESH@"))) {
            if (representation != "Mesh") {
                do {
                    text = in.readLine();
                } while ( ! ( text.contains(QRegExp("@ELSEIF_MESH@")) ||
                              text.contains(QRegExp("@ENDIF_MESH@"))));
            }
        } else if (text.contains(QRegExp("@ELSEIF_MESH@"))) {
            if (representation == "Mesh") {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_MESH@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_MESH@"))) {
            // Go to next line...
        } else if (text.contains(QRegExp("@IF_IMAGE@"))) {
            if (representation != "Image") {
                do {
                    text = in.readLine();
                } while ( ! ( text.contains(QRegExp("@ELSEIF_IMAGE@")) ||
                              text.contains(QRegExp("@ENDIF_IMAGE@"))));
            }
        } else if (text.contains(QRegExp("@ELSEIF_IMAGE@"))) {
            if (representation == "Image") {
                do {
                    text = in.readLine();
                } while (! text.contains(QRegExp("@ENDIF_IMAGE@")));
            }
        } else if (text.contains(QRegExp("@ENDIF_IMAGE@"))) {
            // Go to next line...
        } else if (text.contains(QRegExp("@IF_ADDPROPERTIES@"))) {
            text = in.readLine();
            while (! text.contains(QRegExp("@ENDIF_ADDPROPERTIES@"))) {
                if ( ! componentProperties.isEmpty()) {
                    out << text << endl;
                }
                text = in.readLine();
            }
        } else if (text.contains(QRegExp("@BEGIN_ADDPROPERTIES@"))) {
            text = in.readLine();
            if (componentProperties.size() > 0) {
                QStringList addTheProps;
                for (QVector<ParameterGenerator *>::const_iterator it =  componentProperties.begin(); it != componentProperties.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;
                }
                while (! text.contains(QRegExp("@END_ADDPROPERTIES@")) &&
                        ! text.contains(QRegExp("@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) << 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();

}

