/**
 * Executes an operation (or workflow) and generates output
 * @param parameters the input and output parameters that the user filled in
 */
QString OperationCatalogModel::executeoperation(quint64 operationid, const QString& parameters, QVariant runparams) {

    try {
        IOperationMetaData metadata;
        metadata.prepare(operationid);
        auto opExpr = OperationExpression::createExpression(operationid, parameters);
        if ( metadata.isValid() && opExpr.isValid()){
            if ( metadata->resource().hasProperty("runinmainthread")){ // some operations may not run in a different thread. particular operations that invoke methods from the qml which must run in the mainthread
                OperationWorker::run(opExpr);
            }else {
                QThread* thread = new QThread;
                thread->setProperty("runparameters",runparams);
                OperationWorker* worker = new OperationWorker(opExpr);
                worker->moveToThread(thread);
                thread->setProperty("workingcatalog", qVariantFromValue(context()->workingCatalog()));
                thread->connect(thread, &QThread::started, worker, &OperationWorker::process);
                thread->connect(worker, &OperationWorker::finished, thread, &QThread::quit);
                thread->connect(worker, &OperationWorker::finished, worker, &OperationWorker::deleteLater);
                thread->connect(thread, &QThread::finished, thread, &QThread::deleteLater);
                thread->connect(worker, &OperationWorker::finished, this, &OperationCatalogModel::workerFinished);
                thread->start();

                return "TODO";

            }
        }
        return sUNDEF;
    } catch (const ErrorObject& err){
        emit error(err.message());
    }

    return sUNDEF;
}
/**
 * Executes an operation (or workflow) and generates output
 * @param parameters the input and output parameters that the user filled in
 */
QString OperationCatalogModel::executeoperation(quint64 operationid, const QString& parameters) {
    if ( operationid == 0 || parameters == "")
        return sUNDEF;

    Resource operationresource = mastercatalog()->id2Resource(operationid);
    if ( !operationresource.isValid())
        return sUNDEF;

    em->clearList();

    QString expression;
    QStringList parms = parameters.split("|");
    bool hasInvalidParameters = false;

    for(int i = 0; i < parms.size(); ++ i){
        if (operationresource.ilwisType() & itWORKFLOW){
            int parm = i + 1;
            if (operationresource[QString("pout_%1_optional").arg(parm)] == "false" && i < operationresource["outparameters"].toInt()) {
                QString value = parms[i + operationresource["inparameters"].toInt()];

                QString output = value.split("@@")[0];
                if (output.size() == 0) {
                    em->addError(1, "Output parameter " + QString::number(i) + " is undefined with name " +  operationresource[QString("pout_%1_name").arg(parm)].toString());
                    hasInvalidParameters = true;
                } else {
                    for (const char& c : output.toStdString()) {
                        if (!isalnum(c) && c != ':' && c != '/' && c != '\\' && c != '.') {
                            em->addError(1, "Output parameter " + QString::number(i) + " is not a valid name");
                            hasInvalidParameters = true;
                            break;
                        }
                    }
                }
            }
            if (operationresource[QString("pin_%1_optional").arg(parm)] == "false" && i < operationresource["inparameters"].toInt() && parms[i].size() == 0) {
                em->addError(1, "Input parameter " + QString::number(i) + " is undefined with name " +  operationresource[QString("pin_%1_name").arg(parm)].toString());
                hasInvalidParameters = true;
            }
        }
        if(i < operationresource["inparameters"].toInt()){
            if ( expression.size() != 0)
                expression += ",";
            expression += parms[i];
        }
    }

    if (hasInvalidParameters) return sUNDEF;

    QString allOutputsString;

    bool duplicateFileNames = false;

    QStringList parts = operationresource["outparameters"].toString().split("|");
    int maxparms = parts.last().toInt();
    int count = 1;
    for(int i=(parms.size() - maxparms); i<parms.size(); ++i){
        QString output = parms[i];


        QString pout = QString("pout_%1_type").arg(count++);

        IlwisTypes outputtype = operationresource[pout].toULongLong();
        if ( output.indexOf("@@") != -1 ){
            QString format;
            QStringList parts = output.split("@@");
            output = parts[0];
            if ( output == "")
                continue;

            //Check if user didnt put the same output name in another output field
            int occurences = 0;
            for(int j=(parms.size() - maxparms); j<parms.size(); ++j){
                QString compareString = parms[j].split("@@")[0];
                if(output == compareString){
                    occurences++;
                }
            }

            //Add the duplicate name to the list of duplicate names
            if(occurences>1){
                duplicateFileNames = true;
                em->addError(1, "Workflow did not execute, multiple occurences of an output name");
            }

            QString formatName = parts[1];

            if ( operationresource.ilwisType() & itWORKFLOW) {
                QStringList existingFileNames;

                DIR *directory;

                //If not memory
                QString fileName;

                if(formatName == "Memory" ){
                    //Get all files in the internal catalog
                    QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/internalcatalog";
                    directory = opendir(dataLocation.toStdString().c_str());
                }else {
                    //Get all files in the directory
                    QString dataLocation = output;
                    dataLocation.remove("file:///");

                    QStringList splitUrl = dataLocation.split("/");

                    fileName = splitUrl.last();

                    QString query = "name='" + formatName + "'";
                    std::multimap<QString, Ilwis::DataFormat>  formats = Ilwis::DataFormat::getSelectedBy(Ilwis::DataFormat::fpNAME, query);
                    if ( formats.size() == 1){
                        QString connector = (*formats.begin()).second.property(DataFormat::fpCONNECTOR).toString();
                        QString code = (*formats.begin()).second.property(DataFormat::fpCODE).toString();

                        QVariantList extensions = Ilwis::DataFormat::getFormatProperties(DataFormat::fpEXTENSION,outputtype, connector, code);

                        fileName += ".";
                        fileName += extensions[0].toString();
                    }

                    splitUrl.removeLast();

                    dataLocation = splitUrl.join("/");

                    directory = opendir(dataLocation.toStdString().c_str());
                }

                struct dirent *file;

                //Put the existing file names in a list for later use
                while ((file = readdir (directory)) != NULL) {
                    existingFileNames.push_back(file->d_name);
                }

                closedir(directory);

                //Check if a file with the same name already exist
                for(int j=0;j<existingFileNames.size();++j){
                    if(formatName == "Memory"){
                        if(existingFileNames[j] == output) {
                            duplicateFileNames = true;
                            em->addError(1, "Workflow did not execute duplicate name: " + output + ". Please change this name.");
                        }
                    }else{
                        if(existingFileNames[j] == fileName){
                            duplicateFileNames = true;
                            em->addError(1, "Workflow did not execute duplicate name: " + fileName + ". Please change this name.");
                        }
                    }
                }
            }

            if ( hasType(outputtype, itCOLUMN)){
                if ( formatName == "Memory"){
                    output = modifyTableOutputUrl(output, parms);
                }else
                    output = parms[0] + "[" + output + "]";
            }
            if ( formatName == "Keep original"){
                IIlwisObject obj;
                obj.prepare(parms[0], operationresource["pin_1_type"].toULongLong());
                if ( obj.isValid()){
                    IlwisTypes type = operationresource[pout].toULongLong();
                    QVariantList values = DataFormat::getFormatProperties(DataFormat::fpCODE,type,obj->provider());
                    if ( values.size() != 0){
                        format = "{format(" + obj->provider() + ",\"" + values[0].toString() + "\")}";
                    }else{
                        kernel()->issues()->log(QString("No valid conversion found for provider %1 and format %2").arg(obj->provider()).arg(IlwisObject::type2Name(type)));
                        return sUNDEF;
                    }
                }
            }
            //overrule the user if he wants to store things in the internalcatalog, then the format is by defintion stream
            if ( context()->workingCatalog()->source().url() == INTERNAL_OBJECT)
                formatName == "Memory";
            if ( formatName != "Memory"){ // special case
                if ( format == "") {
                    QString query = "name='" + formatName + "'";
                    std::multimap<QString, Ilwis::DataFormat>  formats = Ilwis::DataFormat::getSelectedBy(Ilwis::DataFormat::fpNAME, query);
                    if ( formats.size() == 1){
                        format = "{format(" + (*formats.begin()).second.property(DataFormat::fpCONNECTOR).toString() + ",\"" +
                                (*formats.begin()).second.property(DataFormat::fpCODE).toString() + "\")}";
                    }
                }
                // if there is no path we extend it with a path unless the output is a new column, output is than the "old" table so no new output object
                if ( output.indexOf("://") == -1 )
                    output = context()->workingCatalog()->source().url().toString() + "/" + output + format;
                else
                    output = output + format;
            }else{
                if ( hasType(outputtype,itRASTER)){
                    format = "{format(stream,\"rastercoverage\")}";
                }else if (hasType(outputtype, itFEATURE)){
                    format = "{format(stream,\"featurecoverage\")}";
                }else if (hasType(outputtype, itTABLE | itCOLUMN)){
                    format = "{format(stream,\"table\")}";
                }else if (hasType(outputtype, itCATALOG)){
                    format = "{format(stream,\"catalog\")}";
                }else if (hasType(outputtype, itDOMAIN)){
                    format = "{format(stream,\"domain\")}";
                }else if (hasType(outputtype, itCOORDSYSTEM)){
                    format = "{format(stream,\"coordinatesystem\")}";
                }else if (hasType(outputtype, itGEOREF)){
                    format = "{format(stream,\"georeference\")}";
                }

                output = output + format;
            }
        }

        if(!allOutputsString.isEmpty()){
            allOutputsString.append(",");
        }
        allOutputsString += output;
    }

    if(!duplicateFileNames){
        if ( allOutputsString == "")
            expression = QString("script %1(%2)").arg(operationresource.name()).arg(expression);
        else
            expression = QString("script %1=%2(%3)").arg(allOutputsString).arg(operationresource.name()).arg(expression);

        OperationExpression opExpr(expression);

        try {
            QThread* thread = new QThread;
            OperationWorker* worker = new OperationWorker(opExpr);
            worker->moveToThread(thread);
            thread->connect(thread, &QThread::started, worker, &OperationWorker::process);
            thread->connect(worker, &OperationWorker::finished, thread, &QThread::quit);
            thread->connect(worker, &OperationWorker::finished, worker, &OperationWorker::deleteLater);
            thread->connect(thread, &QThread::finished, thread, &QThread::deleteLater);
            thread->start();

            return "TODO";
        } catch (const ErrorObject& err){
            emit error(err.message());
        }
    }
    return sUNDEF;
}