/****************************************************************************
* MeshLab                                                           o o     *
* A versatile mesh processing toolbox                             o     o   *
*                                                                _   O  _   *
* Copyright(C) 2005                                                \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* All rights reserved.                                                      *
*                                                                           *
* This program is free software; you can redistribute it and/or modify      *
* it under the terms of the GNU General Public License as published by      *
* the Free Software Foundation; either version 2 of the License, or         *
* (at your option) any later version.                                       *
*                                                                           *
* This program 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 General Public License (http://www.gnu.org/licenses/gpl.txt)          *
* for more details.                                                         *
*                                                                           *
****************************************************************************/

#include <common/GLExtensionsManager.h>
#include <common/mlapplication.h>
#include <common/mlexception.h>
#include <common/plugins/plugin_manager.h>
#include <common/globals.h>
#include <common/filterscript.h>
#include <common/mlexception.h>
#include <common/parameters/rich_parameter_list.h>
#include <wrap/qt/qt_thread_safe_memory_info.h>
#include <wrap/io_trimesh/alnParser.h>
#include <vcg/complex/algorithms/create/platonic.h>

#include <clocale>

#include <QDir>
#include <QGLFormat>
#include <QFileInfo>
#include <QElapsedTimer>
#include <QSettings>


class FilterData
{
public:
    FilterData();
    QString name;
    QString info;
    int filterClass;
    bool operator <(const FilterData &d) const {return name<d.name;}
};

class MeshLabServer
{
public:
    MeshLabServer(MLSceneGLSharedDataContext* shar)
		:shared(shar)
	{
	}

    ~MeshLabServer()
	{
	}

    static bool filterCallBack(const int pos, const char * str)
    {
        int static lastPos=-1;
        if(pos==lastPos) return true;
        lastPos=pos;
        printf("%s",str);
        return true;
    }

    // Here we need a better way to find the plugins directory.
    // To be implemented:
    // use the QSettings togeter with MeshLab.
    // When meshlab starts if he find the plugins write the absolute path of that directory in a persistent qsetting place.
    // Here we use that QSetting. If it is not set we remember to run meshlab first once.
    // in this way it works safely on mac too and allows the user to put the small meshlabserver binary wherever they desire (/usr/local/bin).

    void loadPlugins()
    {
        PM.loadPlugins();

        //printf("Total %i filtering actions\n", PM.actionFilterMap.size());
        printf("Total %i io plugins\n", PM.numberIOPlugins());
    }

    void dumpPluginInfoWiki(FILE *fp)
    {
        if(!fp) return;
        for(FilterPlugin *iFilter: PM.filterPluginIterator())
            for(QAction *filterAction: iFilter->actions())
                fprintf(fp, "*<b><i>%s</i></b> <br>%s<br>\n", qUtf8Printable(filterAction->text()), qUtf8Printable(iFilter->filterInfo(filterAction)));
    }
	
	QMap<QString, RichParameterList> generateFilterParameterMap()
	{	
		QMap<QString, RichParameterList> FPM;
		MeshDocument md;	
		MeshModel* mm = md.addNewMesh("", "dummy", true);	
		vcg::tri::Tetrahedron(mm->cm);
		mm->updateDataMask(MeshModel::MM_ALL);
		QMap<QString, QAction*>::iterator ai;	
		for(FilterPlugin* fpi : PM.filterPluginIterator())
		//for (ai = this->actionFilterMap.begin(); ai != this->actionFilterMap.end(); ++ai)	
		{	
			for (QAction* ai: fpi->actions()) {
				QString filterName = fpi->filterName(ai);//  ->filterName();	
				//QAction act(filterName,NULL);	
				RichParameterList rp;	
				FPM[filterName] = rp;
			}
		}	
		return FPM;	
	}

    void dumpPluginInfoDoxygen(FILE *fp)
    {
        if(!fp) return;
        int i=0;
        QMap<QString, RichParameterList> FPM = generateFilterParameterMap();
        fprintf(fp,"/*! \\mainpage MeshLab Filter Documentation\n");
        //fprintf(fp,"\\AtBeginDocument{\\setcounter{tocdepth}{1}}");

        for(FilterPlugin *iFilter: PM.filterPluginIterator())
        {
            for(QAction *filterAction: iFilter->actions())
            {
                fprintf(fp,
                    "\n\\section f%i %s \n\n"
                    "%s\n"
                    ,i++, qUtf8Printable(filterAction->text()), qUtf8Printable(iFilter->filterInfo(filterAction)));

                fprintf(fp,  "<H2> Parameters </h2>\n");
                //            fprintf(fp,  "\\paragraph fp%i Parameters\n",i);

                if(! FPM[filterAction->text()].isEmpty())
                {
                    fprintf(fp,"<TABLE>\n");
					for(RichParameter& pp : FPM[filterAction->text()])
                    {
                        fprintf(fp,"<TR><TD> \\c %s  </TD> <TD> %s </TD> <TD><i> %s -- </i></TD> </TR>\n",
							qUtf8Printable(pp.value().typeName()), qUtf8Printable(pp.fieldDescription()), qUtf8Printable(pp.toolTip()));
                    }
                    fprintf(fp,"</TABLE>\n");
                }
                else fprintf(fp,"No parameters.<br>");

            }
        }
        fprintf(fp,"*/");
    }

    bool importMesh(MeshModel &mm, const QString& fileName,FILE* fp = stdout)
    {
        // Opening files in a transparent form (IO plugins contribution is hidden to user)
        QStringList filters;

        // HashTable storing all supported formats together with
        // the (1-based) index  of first plugin which is able to open it
        QHash<QString, IOPlugin*> allKnownFormats;

        //PM.LoadFormats(filters, allKnownFormats,PluginManager::IMPORT);

        QFileInfo fi(fileName);
        // this change of dir is needed for subsequent textures/materials loading
        QDir curDir = QDir::current();
        QDir::setCurrent(fi.absolutePath());

        QString extension = fi.suffix();
        qDebug("Opening a file with extension %s", qUtf8Printable(extension));
        // retrieving corresponding IO plugin
        IOPlugin* pCurrentIOPlugin = PM.inputMeshPlugin(extension);
        if (pCurrentIOPlugin == 0)
        {
            fprintf(fp,"Error encountered while opening file: ");
            QDir::setCurrent(curDir.absolutePath());
            return false;
        }
        int mask = 0;

        RichParameterList prePar;
		pCurrentIOPlugin->initPreOpenParameter(extension,prePar);
        prePar.join(meshlab::defaultGlobalParameterList());

        try {
            pCurrentIOPlugin->open(extension, fileName, mm ,mask,prePar);
        }
        catch(const MLException& e){
            fprintf(fp,"MeshLabServer: Failed loading of %s from dir %s\n", qUtf8Printable(fileName), qUtf8Printable(QDir::currentPath()));
            QDir::setCurrent(curDir.absolutePath());
            return false;
        }
        // In case of polygonal meshes the normal should be updated accordingly
        if( mask & vcg::tri::io::Mask::IOM_BITPOLYGONAL)
        {
            mm.updateDataMask(MeshModel::MM_POLYGONAL); // just to be sure. Hopefully it should be done in the plugin...
            int degNum = vcg::tri::Clean<CMeshO>::RemoveDegenerateFace(mm.cm);
            if(degNum)
                fprintf(fp,"Warning model contains %i degenerate faces. Removed them.",degNum);
            mm.updateDataMask(MeshModel::MM_FACEFACETOPO);
            vcg::tri::UpdateNormal<CMeshO>::PerBitQuadFaceNormalized(mm.cm);
            vcg::tri::UpdateNormal<CMeshO>::PerVertexFromCurrentFaceNormal(mm.cm);
        } // standard case
        else {
            if( mask & vcg::tri::io::Mask::IOM_VERTNORMAL) // the mesh already has its per vertex normals (point clouds)
            {
                vcg::tri::UpdateNormal<CMeshO>::PerFace(mm.cm);
                vcg::tri::UpdateBounding<CMeshO>::Box(mm.cm);					// updates bounding box
            }
            else mm.updateBoxAndNormals(); // the very standard case
        }

        if(mm.cm.fn==0)
        {
            if (mask & vcg::tri::io::Mask::IOM_VERTNORMAL)
                mm.updateDataMask(MeshModel::MM_VERTNORMAL);
        }
        else
            mm.updateDataMask(MeshModel::MM_VERTNORMAL);

		if (shared != NULL)
			shared->meshInserted(mm.id());
        //vcg::tri::UpdateBounding<CMeshO>::Box(mm.cm);
        QDir::setCurrent(curDir.absolutePath());
        return true;
    }

    bool exportMesh(MeshModel *mm, const int mask, const QString& fileName,bool writebinary,FILE* fp = stdout)
    {
        QFileInfo fi(fileName);
        // this change of dir is needed for subsequent textures/materials loading
        QDir curDir = QDir::current();
        QDir::setCurrent(fi.absolutePath());

        QString extension = fi.suffix();

        // retrieving corresponding IO plugin
        IOPlugin* pCurrentIOPlugin = PM.outputMeshPlugin(extension);
        if (pCurrentIOPlugin == nullptr)
        {
            fprintf(fp,"Error encountered while opening file: ");
            //QString errorMsgFormat = "Error encountered while opening file:\n\"%1\"\n\nError details: The \"%2\" file extension does not correspond to any supported format.";
            //QMessageBox::critical(this, tr("Opening Error"), errorMsgFormat.arg(fileName, extension));
            QDir::setCurrent(curDir.absolutePath());
            return false;
        }

        // optional saving parameters (like ascii/binary encoding)
        RichParameterList savePar;
        pCurrentIOPlugin->initSaveParameter(extension, *mm, savePar);
        if(savePar.hasParameter("Binary")){
            savePar.setValue("Binary",BoolValue(writebinary));
        }

        int formatmask = 0;
        int defbits = 0;
        pCurrentIOPlugin->exportMaskCapability(extension,formatmask,defbits);
        try {
            pCurrentIOPlugin->save(extension, fileName, *mm ,mask & formatmask, savePar, nullptr);
            QDir::setCurrent(curDir.absolutePath());
            return true;
        }
        catch(const MLException& e)
        {
            fprintf(fp,"Failed saving\n");
            QDir::setCurrent(curDir.absolutePath());
            return false;
        }
    }

    bool loadMesh(const QString& fileName, IOPlugin *pCurrentIOPlugin, MeshModel* mm, int& mask,RichParameterList* prePar, const Matrix44m &mtr, MeshDocument* md, FILE* fp = stdout)
    {
        if (mm == NULL)
            return false;

        QFileInfo fi(fileName);
        QString extension = fi.suffix();
        if(!fi.exists())
        {
            QString errorMsgFormat = "Unable to open file:\n\"%1\"\n\nError details: file %1 does not exist.";
            fprintf(fp, "Meshlab Opening Error: %s", errorMsgFormat.arg(fileName).toStdString().c_str());
            return false;
        }
        if(!fi.isReadable())
        {
            QString errorMsgFormat = "Unable to open file:\n\"%1\"\n\nError details: file %1 is not readable.";
            fprintf(fp, "Meshlab Opening Error: %s", errorMsgFormat.arg(fileName).toStdString().c_str());
            return false;
        }

        // the original directory path before we switch it
        QString origDir = QDir::current().path();

        // this change of dir is needed for subsequent textures/materials loading
        QDir::setCurrent(fi.absoluteDir().absolutePath());

        // Adjust the file name after changing the directory
        QString fileNameSansDir = fi.fileName();

        // retrieving corresponding IO plugin
        if (pCurrentIOPlugin == 0)
        {
            QString errorMsgFormat = "Error encountered while opening file:\n\"%1\"\n\nError details: The \"%2\" file extension does not correspond to any supported format.";
            fprintf(fp, "Opening Error: %s", errorMsgFormat.arg(fileName, extension).toStdString().c_str());
            QDir::setCurrent(origDir); // undo the change of directory before leaving
            return false;
        }
        md->setBusy(true);
        pCurrentIOPlugin->setLog(&md->Log);

        try {
            pCurrentIOPlugin->open(extension, fileNameSansDir, *mm ,mask,*prePar);
        }
        catch (const MLException& e) {
            fprintf(fp, "Opening Failure: %s", (QString("While opening: '%1'\n\n").arg(fileName)+e.what()).toStdString().c_str()); // text+
            md->setBusy(false);
            QDir::setCurrent(origDir); // undo the change of directory before leaving
            return false;
        }


        //std::cout << "Opened mesh: in " << tm.elapsed() << " secs\n";
        // After opening the mesh lets ask to the io plugin if this format
        // requires some optional, or userdriven post-opening processing.
        // and in that case ask for the required parameters and then
        // ask to the plugin to perform that processing
        //RichParameterSet par;
        //pCurrentIOPlugin->initOpenParameter(extension, *mm, par);
        //pCurrentIOPlugin->applyOpenParameter(extension, *mm, par);

        QString err = pCurrentIOPlugin->warningMessageString();
        if (!err.isEmpty())
        {
            fprintf(fp, "Opening Problems: %s", (QString("While opening: '%1'\n\n").arg(fileName)+err).toStdString().c_str());
        }

        //saveRecentFileList(fileName);

        //if (!(mm->cm.textures.empty()))
        //    updateTexture(mm->id());

        // In case of polygonal meshes the normal should be updated accordingly
        if( mask & vcg::tri::io::Mask::IOM_BITPOLYGONAL)
        {
            mm->updateDataMask(MeshModel::MM_POLYGONAL); // just to be sure. Hopefully it should be done in the plugin...
            int degNum = vcg::tri::Clean<CMeshO>::RemoveDegenerateFace(mm->cm);
            if(degNum)
                fprintf(stdout, "Warning model contains %i degenerate faces. Removed them.",degNum);
            mm->updateDataMask(MeshModel::MM_FACEFACETOPO);
            vcg::tri::UpdateNormal<CMeshO>::PerBitQuadFaceNormalized(mm->cm);
            vcg::tri::UpdateNormal<CMeshO>::PerVertexFromCurrentFaceNormal(mm->cm);
        } // standard case
        else
        {
            vcg::tri::UpdateNormal<CMeshO>::PerFaceNormalized(mm->cm);
            if(!( mask & vcg::tri::io::Mask::IOM_VERTNORMAL) )
                vcg::tri::UpdateNormal<CMeshO>::PerVertexAngleWeighted(mm->cm);
        }

        vcg::tri::UpdateBounding<CMeshO>::Box(mm->cm);					// updates bounding box
        if(mm->cm.fn==0 && mm->cm.en==0)
        {
            if(mask & vcg::tri::io::Mask::IOM_VERTNORMAL)
                mm->updateDataMask(MeshModel::MM_VERTNORMAL);
        }

        if(mm->cm.fn==0 && mm->cm.en>0)
        {
            if (mask & vcg::tri::io::Mask::IOM_VERTNORMAL)
                mm->updateDataMask(MeshModel::MM_VERTNORMAL);
        }

        //updateMenus();
        int delVertNum = vcg::tri::Clean<CMeshO>::RemoveDegenerateVertex(mm->cm);
        int delFaceNum = vcg::tri::Clean<CMeshO>::RemoveDegenerateFace(mm->cm);
        vcg::tri::Allocator<CMeshO>::CompactEveryVector(mm->cm);
        if(delVertNum>0 || delFaceNum>0 )
            fprintf(fp, "MeshLab Warning: %s", (QString("Warning mesh contains %1 vertices with NAN coords and %2 degenerated faces.\nCorrected.").arg(delVertNum).arg(delFaceNum)).toStdString().c_str() );
        mm->cm.Tr = mtr;

        //computeRenderingDataOnLoading(mm,isareload, rendOpt);
        //updateLayerDialog();


        md->setBusy(false);

        QDir::setCurrent(origDir); // undo the change of directory before leaving

        return true;
    }

    bool loadMeshWithStandardParams(QString& fullPath, MeshModel* mm, const Matrix44m &mtr = Matrix44m::Identity(), MeshDocument* md = NULL)
    {
        if ((mm == NULL) || (md == NULL))
            return false;
        bool ret = false;
        if (!mm->isVisible())
        {
            mm->clear();
            mm->visible = false;
        }
        else
            mm->clear();
        QFileInfo fi(fullPath);
        QString extension = fi.suffix();
        IOPlugin *pCurrentIOPlugin = PM.inputMeshPlugin(extension);

        if(pCurrentIOPlugin != NULL)
        {
            RichParameterList prePar;
			pCurrentIOPlugin->initPreOpenParameter(extension,prePar);
            //prePar = prePar.join(currentGlobalParams);
            int mask = 0;
            QElapsedTimer t;t.start();
            ret = loadMesh(fullPath,pCurrentIOPlugin,mm,mask,&prePar,mtr, md, stdout);
        }
        return ret;
    }

    bool openProject(MeshDocument& md,const QString& fileName,FILE* fp = stdout)
    {
        //bool visiblelayer = layerDialog->isVisible();
        //showLayerDlg(false);
    	//globrendtoolbar->setEnabled(false);
        if (fileName.isEmpty()) return false;

        QFileInfo fi(fileName);
        //lastUsedDirectory = fi.absoluteDir();
        //TODO: move this to main()
        if((fi.suffix().toLower()!="aln") && (fi.suffix().toLower()!="mlp")  && (fi.suffix().toLower() != "mlb") && (fi.suffix().toLower()!="out") && (fi.suffix().toLower()!="nvm"))
        {
            //QMessageBox::critical(this, tr("Meshlab Opening Error"), "Unknown project file extension");
            fprintf(fp, "Meshlab Opening Error: Unknown project file extension\n");
            return false;
        }

        // Common Part: init a Doc if necessary, and
        //bool activeDoc = (bool) !mdiarea->subWindowList().empty() && mdiarea->currentSubWindow();
        //bool activeEmpty = activeDoc && md.meshList.empty();

        //if (!activeEmpty)  newProject(fileName);

        md.setFileName(fileName);
        //mdiarea->currentSubWindow()->setWindowTitle(fileName);
        md.setDocLabel(fileName);

        md.setBusy(true);

        // this change of dir is needed for subsequent textures/materials loading
        QDir::setCurrent(fi.absoluteDir().absolutePath());
        //qb->show();

        if (QString(fi.suffix()).toLower() == "aln")
        {
            std::vector<RangeMap> rmv;
    		int retVal = ALNParser::ParseALN(rmv, qUtf8Printable(fileName));
            if(retVal != ALNParser::NoError)
            {
                //QMessageBox::critical(this, tr("Meshlab Opening Error"), "Unable to open ALN file");
                fprintf(fp,"Meshlab Opening Error: Unable to open ALN file\n");
                return false;
            }

            bool openRes=true;
            std::vector<RangeMap>::iterator ir;
            for(ir=rmv.begin();ir!=rmv.end() && openRes;++ir)
            {
                QString relativeToProj = fi.absoluteDir().absolutePath() + "/" + (*ir).filename.c_str();
                md.addNewMesh(relativeToProj,relativeToProj);
                openRes = loadMeshWithStandardParams(relativeToProj,md.mm(),ir->transformation, &md);
                if(!openRes)
                    md.delMesh(md.mm());
            }
        }

        if (QString(fi.suffix()).toLower() == "mlp" || QString(fi.suffix()).toLower() == "mlb")
        {
            std::map<int, MLRenderingData> rendOpt;
            if (!MeshDocumentFromXML(md, fileName, (QString(fi.suffix()).toLower() == "mlb"), rendOpt))
            {
              //QMessageBox::critical(this, tr("Meshlab Opening Error"), "Unable to open MeshLab Project file");
              fprintf(fp,"Meshlab Opening Error: Unable to open MeshLab Project file\n");
              return false;
            }
    		//GLA()->updateMeshSetVisibilities();
            for (int i=0; i<md.meshList.size(); i++)
            {
                QString fullPath = md.meshList[i]->fullName();
                //md.setBusy(true);
                Matrix44m trm = md.meshList[i]->cm.Tr; // save the matrix, because loadMeshClear it...
                if (!loadMeshWithStandardParams(fullPath, md.meshList[i], trm, &md))
                    md.delMesh(md.meshList[i]);
            }
        }

        ////// BUNDLER
        if (QString(fi.suffix()).toLower() == "out"){

            QString cameras_filename = fileName;
            QString image_list_filename;
            QString model_filename;

            image_list_filename = "list.txt";
            if(image_list_filename.isEmpty())
                return false;

            if(!MeshDocumentFromBundler(md,cameras_filename,image_list_filename,model_filename)){
                //QMessageBox::critical(this, tr("Meshlab Opening Error"), "Unable to open OUTs file");
                fprintf(fp,"Meshlab Opening Error: Unable to open OUTs file\n");
                return false;
            }


    //WARNING!!!!! i suppose it's not useful anymore but.......
    /*GLA()->setColorMode(GLW::CMPerVert);
    GLA()->setDrawMode(GLW::DMPoints);*/
    /////////////////////////////////////////////////////////
        }

        //////NVM
        if (QString(fi.suffix()).toLower() == "nvm"){

            QString cameras_filename = fileName;
            QString model_filename;

            if(!MeshDocumentFromNvm(md,cameras_filename,model_filename)){
                //QMessageBox::critical(this, tr("Meshlab Opening Error"), "Unable to open NVMs file");
                fprintf(fp,"Meshlab Opening Error: Unable to open NVMs file\n");
                return false;
            }
    //WARNING!!!!! i suppose it's not useful anymore but.......
    /*GLA()->setColorMode(GLW::CMPerVert);
    GLA()->setDrawMode(GLW::DMPoints);*/
    /////////////////////////////////////////////////////////
        }

        md.setBusy(false);
        //qb->reset();

        return true;
    }

    bool saveProject(MeshDocument& md,const QString& filename,const QString& outfilemeshmiddlename = QString())
    {
        QFileInfo outprojinfo(filename);
        QString outdir = outprojinfo.absolutePath();

        QDir curDir = QDir::current();
        QDir::setCurrent(outprojinfo.absolutePath());
        foreach(MeshModel* m,md.meshList)
        {
            if (m != NULL)
            {
                QString outfilename;
                QFileInfo fi(m->fullName());
                if (!fi.exists())
                    outfilename = outdir + "/" + m->label().remove(" ") + outfilemeshmiddlename + ".ply";
                else
                    outfilename =  fi.absolutePath() + "/" + fi.completeBaseName() + outfilemeshmiddlename + "." + fi.completeSuffix();
                m->setFileName(outfilename);
                QFileInfo of(outfilename);
                m->setLabel(of.fileName());
                exportMesh(m,m->dataMask(),outfilename,true);
            }
        }

        QDir::setCurrent(curDir.absolutePath());
        return MeshDocumentToXMLFile(md, filename, false, false, outprojinfo.suffix().toLower() == "mlb");
    }

    bool script(MeshDocument &meshDocument,const QString& scriptfile,FILE* fp)
    {
        MeshModel* mm = meshDocument.mm();

        FilterScript scriptPtr;

        //Open/Load FilterScript

        if (scriptfile.isEmpty())
        {
            printf("No script specified\n");
            return false;
        }
        if (!scriptPtr.open(scriptfile))
        {
            printf("File %s was not found.\n", qUtf8Printable(scriptfile));
            return false;
        }
        fprintf(fp,"Starting Script of %i actions",scriptPtr.size());
        GLLogStream log;
        for (FilterNameParameterValuesPair& pair : scriptPtr)
        {
            bool ret = true;
            //RichParameterSet &par = (*ii).second;
            QString fname = pair.filterName();
            fprintf(fp,"filter: %s\n", qUtf8Printable(fname));
            QAction *action = PM.filterAction(fname);
            if (action == NULL)
            {
                fprintf(fp,"filter %s not found", qUtf8Printable(fname));
                return false;
            }

            FilterPlugin *iFilter = qobject_cast<FilterPlugin *>(action->parent());
            iFilter->setLog(&log);
            int req = iFilter->getRequirements(action);
            if (mm != NULL)
                mm->updateDataMask(req);
            //make sure the PARMESH parameters are initialized

            //A filter in the script file couldn't have all the required parameter not defined (a script file not generated by MeshLab).
            //So we have to ask to the filter the default values for all the parameters and integrate them with the parameters' values
            //defined in the script file.
            RichParameterList required = iFilter->initParameterList(action,meshDocument);
            RichParameterList &parameterSet = pair.second;

            //The parameters in the script file are more than the required parameters of the filter. The script file is not correct.
            if (required.size() < parameterSet.size())
            {
                fprintf(fp,"The parameters in the script file are more than the filter %s requires.\n", qUtf8Printable(fname));
                return false;
            }

			int i = 0;
			for(RichParameter& rp : required) {
				if (!parameterSet.hasParameter(rp.name())) {
					parameterSet.addParam(rp);
				}
				assert(parameterSet.size() == required.size());
				RichParameter& parameter = parameterSet.at(i);
				//if this is a mesh parameter and the index is valid
				if(parameter.value().isMesh()) {
					RichMesh& md = reinterpret_cast<RichMesh&>(parameter);
					if(!(md.value().getMeshId() < (unsigned int) meshDocument.size())) {
						fprintf(fp,"Meshes loaded: %i, meshes asked for: %i \n", meshDocument.size(), md.value().getMeshId() );
						fprintf(fp,"One of the filters in the script needs more meshes than you have loaded.\n");
						exit(-1);
					}
				}
				i++;
			}

            QGLWidget* wid = NULL;
            if (shared != NULL)
            {
                wid = new QGLWidget(NULL,shared);
                iFilter->glContext = new MLPluginGLContext(QGLFormat::defaultFormat(), wid->context()->device(),*shared);
                bool created = iFilter->glContext->create(wid->context());
                if ((!created) || (!iFilter->glContext->isValid()))
                {
                    fprintf(fp, "A valid GLContext is required by the filter to work.\n");
                    return false;
                }
                MLRenderingData dt;
                MLRenderingData::RendAtts atts;
                atts[MLRenderingData::ATT_NAMES::ATT_VERTPOSITION] = true;
                atts[MLRenderingData::ATT_NAMES::ATT_VERTNORMAL] = true;

                if (iFilter->filterArity(action) == FilterPlugin::SINGLE_MESH)
                {
                    MLRenderingData::PRIMITIVE_MODALITY pm = MLPoliciesStandAloneFunctions::bestPrimitiveModalityAccordingToMesh(meshDocument.mm());
                    if ((pm != MLRenderingData::PR_ARITY) && (meshDocument.mm() != NULL))
                    {
                        dt.set(pm, atts);
                        iFilter->glContext->initPerViewRenderingData(meshDocument.mm()->id(), dt);
                    }

                    if (meshDocument.mm() != NULL)
                    {
                        meshDocument.mm()->cm.svn = int(vcg::tri::UpdateSelection<CMeshO>::VertexCount(meshDocument.mm()->cm));
                        meshDocument.mm()->cm.sfn = int(vcg::tri::UpdateSelection<CMeshO>::FaceCount(meshDocument.mm()->cm));
                    }

                }
                else
                {
                    for (int ii = 0; ii < meshDocument.meshList.size(); ++ii)
                    {
                        MeshModel* mm = meshDocument.meshList[ii];
                        MLRenderingData::PRIMITIVE_MODALITY pm = MLPoliciesStandAloneFunctions::bestPrimitiveModalityAccordingToMesh(mm);
                        if ((pm != MLRenderingData::PR_ARITY) && (mm != NULL))
                        {
                            dt.set(pm, atts);
                            iFilter->glContext->initPerViewRenderingData(mm->id(), dt);
                        }

                        if (mm != NULL)
                        {
                            mm->cm.svn = int(vcg::tri::UpdateSelection<CMeshO>::VertexCount(mm->cm));
                            mm->cm.sfn = int(vcg::tri::UpdateSelection<CMeshO>::FaceCount(mm->cm));
                        }
                    }
                }
            }
            meshDocument.setBusy(true);
            unsigned int postConditionMask = MeshModel::MM_UNKNOWN;
			try {
				iFilter->applyFilter( action, pair.second, meshDocument, postConditionMask, filterCallBack);
			}
			catch (MLException&) {
				ret = false;
			}
            meshDocument.setBusy(false);
            if (shared != NULL)
                delete iFilter->glContext;
            delete wid;
            QStringList logOutput;
            log.print(logOutput);
            foreach(QString logEntry, logOutput)
                fprintf(fp,"%s\n",qUtf8Printable(logEntry));
            if(!ret)
            {
                fprintf(fp,"Problem with filter: %s\n",qUtf8Printable(fname));
                return false;
            }
        }
        return true;
    }

private:
    PluginManager PM;
    MLSceneGLSharedDataContext* shared;
};

namespace commandline
{
    const char inproject('p');
    const char outproject('w');
    const char overwrite('x');
    const char inputmeshes('i');
    const char outputmesh('o');
    const char layer('l');
    const char lastlayer('x');
    const char currentlayer('c');
    const char mask('m');
    const char vertex('v');
    const char face('f');
    const char wedge('w');
    const char mesh('m');
    const char color('c');
    const char flags('f');
    const char normal('n');
    const char quality('q');
    const char radius('r');
    const char polygon('p');
    const char texture('t');
    const char log('l');
    const char dump('d');
    const char script('s');
    const char saveparam('s');
    const char ascii('a');

    void usage()
    {
        printf("MeshLabServer version: %s\n", qUtf8Printable(MeshLabApplication::appVer()));
        QFile docum(":/meshlabserver.txt");
        if (!docum.open(QIODevice::ReadOnly))
        {
            printf("MeshLabServer was not able to locate meshlabserver.txt file. The program will be closed\n");
            exit(-1);
        }
        QString help(docum.readAll());
        printf("\nUsage:\n%s",qUtf8Printable(help));
        docum.close();
    }

    QString optionValueExpression(const char cmdlineopt)
    {
        //Validate an option followed by spaces and a filepath
        return QString ("-" + QString(cmdlineopt) + "\\s+(.+)");
    }

    QString outputmeshExpression()
    {
		QString options("(" + QString(vertex) + "|" + QString(face) + "|" + QString(wedge) + "|" + QString(mesh) + "|" +QString(saveparam) + ")(" + QString(color) + "|" + QString(quality) + "|" + QString(flags) + "|" + QString(normal) + "|" + QString(radius) + "|" + QString(texture) + "|" + QString(polygon) + "|" + QString(ascii) + ")");
		QString optionslist(options + "(\\s+" + options + ")*");
		QString savingmask("-" + QString(mask) + "\\s+" + optionslist);
		QString layernumber("\\d+");
		QString layertosave("-" + QString(layer) + "\\s+(" + layernumber + "|" + currentlayer + "|" + lastlayer + ")");
		return optionValueExpression(outputmesh) + "(\\s+(" + savingmask + "|" + layertosave + "\\s+" + savingmask + "|" + layertosave + "))*";
    }

    bool validateCommandLine(const QString& str)
    {
        QString logstring("(" + optionValueExpression(log) + "\\s+" +  optionValueExpression(dump) + "|" + optionValueExpression(dump) + "\\s+" +  optionValueExpression(log) + "|" +  optionValueExpression(dump) + "|" + optionValueExpression(log) + ")");
        QString arg("(" + optionValueExpression(inproject) + "|" + optionValueExpression(inputmeshes) + "|" + optionValueExpression(outproject) + "(\\s+-" + overwrite + ")?" + "|" + optionValueExpression(script) + "|" + outputmeshExpression() + ")");
        QString args("(" + arg + ")(\\s+" + arg + ")*");
        QString completecommandline("(" + logstring + "|" + logstring + "\\s+" + args + "|" + args + ")");
        QRegExp completecommandlineexp(completecommandline);
        //completecommandlineexp.setMinimal(true);

        bool valid = completecommandlineexp.isValid();
        if (!valid)
            return false;
        completecommandlineexp.indexIn(str);
        QString rr = completecommandlineexp.cap();
        return (completecommandlineexp.matchedLength() == str.size());
    }

}

struct OutFileMesh
{
    OutFileMesh() : writebinary(true) {}
    QString filename;
    int mask;
    bool writebinary;
	/*WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
	/* We need these two constant values because when we parse the command line we don't know
   * yet how many layers will have the current document and which will be the current one.
   * Opening a project and/or importing a file happens after the parsing of the commandline
   * is completed */
	static const int lastlayerconst = -2;
	static const int currentlayerconst = -1;
	/******************************************************************************************/

	// Possible values can be:
	//	- lastlayerconst #the last layer of a document, DEFAULT value
	//	- currentlayerconst #the current layer of a document, sometimes it's different from the last layer of a document
	//	- a number between [0,inf) #identifying the correspondent layer position
  // WARNING!!!!! Please note that the layer position is DIFFERENT from the layer id
	int layerposition;
};

struct OutProject
{
    QString filename;
    bool overwrite;
};

int main(int argc, char *argv[])
{
    GLExtensionsManager::init();
    FILE* logfp = stdout;
    FILE* dumpfp = NULL;
    MeshLabApplication app(argc, argv);
    QStringList st = app.arguments();
    std::setlocale(LC_ALL, "C");
    QLocale::setDefault(QLocale::C);
    if(argc == 1)
    {
        commandline::usage();
        //system("pause");
        exit(-1);
    }
    QStringList scriptfiles;
    QList<OutFileMesh> outmeshlist;
    QList<OutProject> outprojectfiles;

    QString cmdline;
    for (int ii = 1; ii < argc; ++ii)
    {
        QString argum(argv[ii]);
        argum = argum.trimmed();
        if (argum.contains(' '))
            argum = "\"" + argum + "\"";
        cmdline = cmdline + argum + " ";
    }
    if (!commandline::validateCommandLine(cmdline.trimmed()))
    {
        printf("CommandLine Syntax Error: please refer to the following documentation for a complete list of the MeshLabServer parameters.\n");
        commandline::usage();
        //system("pause");
        exit(-1);
    }

	QSettings settings(MeshLabApplication::organization(),MeshLabApplication::appArchitecturalName(MeshLabApplication::HW_64BIT));

	QVariant xmlgpupar = settings.value("MeshLab::System::maxGPUMemDedicatedToGeometry");

	QDomDocument doc;
	doc.setContent(xmlgpupar.toString(), false);

	QDomElement paramelem = doc.firstChild().toElement();
	int gpumemmb = paramelem.attribute("value").toInt();

	std::ptrdiff_t maxgpumem = (std::ptrdiff_t) gpumemmb * (float)(1024 * 1024);
	vcg::QtThreadSafeMemoryInfo gpumeminfo(maxgpumem);

    QGLFormat fmt = QGLFormat::defaultFormat();
    fmt.setAlphaBufferSize(8);
    QGLFormat::setDefaultFormat(fmt);

	MeshDocument meshDocument;

	MLSceneGLSharedDataContext shared(meshDocument, gpumeminfo, MeshLabScalarTest<MESHLAB_SCALAR>::doublePrecision(), 100000,100000);
	shared.makeCurrent();
	if (!GLExtensionsManager::initializeGLextensions_notThrowing())
	{
		printf("GLEW Init: failed!\n");
		//system("pause");
		exit(-1);
	}
	shared.doneCurrent();
    printf("Loading Plugins:\n");
	MeshLabServer server(&shared);
    server.loadPlugins();

    bool writebinary = true;
    int i = 1;
    while(i < argc)
    {
        QString tmp = argv[i];
        switch(argv[i][1])
        {
        case commandline::inproject :
            {
                if (((i+1) < argc) && (argv[i+1][0] != '-'))
                {
                    QFileInfo finfo(argv[i+1]);
                    QString inputproject = finfo.absoluteFilePath();
                    bool opened = server.openProject(meshDocument,inputproject, logfp);
                    if (!opened)
                    {
                        fprintf(logfp,"MeshLab Project %s has not been correctly opened. MeshLabServer application will exit.\n",qUtf8Printable(inputproject));
						//system("pause");
                        exit(-1);
                    }
                    else
                        fprintf(logfp,"MeshLab Project %s has been loaded.\n",qUtf8Printable(inputproject));
                    ++i;
                }
                else
                {
                    fprintf(logfp,"Missing project name. MeshLabServer application will exit.\n");
					//system("pause");
                    exit(-1);
                }
                ++i;
                break;
            }
        case commandline::outproject :
            {
                if (((i+1) < argc) && (argv[i+1][0] != '-'))
                {
                    QFileInfo finfo(argv[i+1]);
                    OutProject pr;
                    pr.overwrite = false;
                    pr.filename = finfo.absoluteFilePath();
                    if (finfo.completeSuffix().toLower() != "mlp")
                    {
                        fprintf(logfp,"Project %s is not a valid \'mlp\' file format. Output file will be renamed as %s.mlp .\n",qUtf8Printable(pr.filename),qUtf8Printable(pr.filename + ".mlp"));
                        pr.filename += ".mlp";
                    }
                    ++i;
					QString overtmp('-');
					overtmp += commandline::overwrite;
                    if (((i + 1) < argc) && (QString(argv[i+1]) == overtmp))
                    {
                        pr.overwrite = true;
                        ++i;
                    }
                    outprojectfiles << pr;
                }
                ++i;
                break;
            }
        case commandline::inputmeshes :
            {
                while( ((i+1) < argc) && argv[i+1][0] != '-')
                {
                    QFileInfo info(argv[i+1]);
                    //now add it to the document
                    MeshModel* mmod = meshDocument.addNewMesh(info.absoluteFilePath(),"");
                    if (mmod == NULL)
                    {
                        fprintf(logfp,"It was not possible to add new mesh %s to MeshLabServer. The program will exit\n",qUtf8Printable(info.absoluteFilePath()));
						//system("pause");
                        exit(-1);
                    }
                    bool opened = server.importMesh(*mmod, info.absoluteFilePath(),logfp);
                    if (!opened)
                    {
                        fprintf(logfp,"It was not possible to import mesh %s into MeshLabServer. The program will exit\n ",qUtf8Printable(info.absoluteFilePath()));
						//system("pause");
                        exit(-1);
                    }
                    fprintf(logfp,"Mesh %s loaded has %i vn %i fn\n", qUtf8Printable(info.absoluteFilePath()), mmod->cm.vn, mmod->cm.fn);
                    i++;
                }
                i++;
                break;
            }
        case commandline::outputmesh :
            {
                QString fileout;
                int mask = 0;
                OutFileMesh outfl;
                if( ((i+1) < argc) && argv[i+1][0] != '-')
                {
                    QFileInfo info(argv[i+1]);
                    outfl.filename = info.absoluteFilePath();

					/*WARNING! in order to maintain backward SYNTAX compatibility (not the SEMANTIC one!) by default the outputmesh saved is the one contained in the current layer*/
					outfl.layerposition = OutFileMesh::currentlayerconst;

                    fprintf(logfp,"output mesh  %s\n", qUtf8Printable(outfl.filename));
                    i++;
                }

				if (((i + 1) < argc) && (QString(argv[i + 1]) == (QString("-") + commandline::layer)))
				{
					i = i + 2;
					if (argv[i][0] == commandline::lastlayer)
						outfl.layerposition = OutFileMesh::lastlayerconst;
					else
					{
						if (argv[i][0] == commandline::currentlayer)
							outfl.layerposition = OutFileMesh::currentlayerconst;
						else
							outfl.layerposition = QString(argv[i]).toInt();
					}
				}

                if (((i + 1) < argc) && (QString(argv[i+1]) == (QString("-") + commandline::mask)))
                {
                    i = i + 2;
                    do
                    {
                        switch (argv[i][0])
                        {
                        case commandline::vertex :
                            {
                                switch (argv[i][1])
                                {
                                case commandline::color : i++; fprintf(logfp,"vertex color, "     ); mask |= vcg::tri::io::Mask::IOM_VERTCOLOR;    break;
                                case commandline::flags : i++; fprintf(logfp,"vertex flags, "     ); mask |= vcg::tri::io::Mask::IOM_VERTFLAGS;    break;
                                case commandline::normal : i++; fprintf(logfp,"vertex normals, "   ); mask |= vcg::tri::io::Mask::IOM_VERTNORMAL;   break;
                                case commandline::quality : i++; fprintf(logfp,"vertex quality, "   ); mask |= vcg::tri::io::Mask::IOM_VERTQUALITY;  break;
                                case commandline::radius : i++; fprintf(logfp,"vertex radii, "   ); mask |= vcg::tri::io::Mask::IOM_VERTRADIUS;  break;
                                case commandline::texture : i++; fprintf(logfp,"vertex tex coords, "); mask |= vcg::tri::io::Mask::IOM_VERTTEXCOORD; break;
                                default :  i++; fprintf(logfp,"WARNING: unknowns per VERTEX attribute '%s'",argv[i+1]);break;
                                }
                                break;
                            }
                        case commandline::face :
                            {
                                switch (argv[i][1])
                                {
                                case commandline::color : i++; fprintf(logfp,"face color, "  ); mask |= vcg::tri::io::Mask::IOM_FACECOLOR;   break;
                                case commandline::flags : i++; fprintf(logfp,"face flags, "  ); mask |= vcg::tri::io::Mask::IOM_FACEFLAGS;   break;
                                case commandline::normal : i++; fprintf(logfp,"face normals, "); mask |= vcg::tri::io::Mask::IOM_FACENORMAL;  break;
                                case commandline::quality : i++; fprintf(logfp,"face quality, "); mask |= vcg::tri::io::Mask::IOM_FACEQUALITY; break;
                                default :  i++; fprintf(logfp,"WARNING: unknowns per FACE attribute '%s'",argv[i+1]);break;
                                }
                                break;
                            }
                        case commandline::wedge :
                            {
                                switch (argv[i][1])
                                {
                                case commandline::color : i++; fprintf(logfp,"wedge color, "     ); mask |= vcg::tri::io::Mask::IOM_WEDGCOLOR;   break;
                                case commandline::normal : i++; fprintf(logfp,"wedge normals, "   ); mask |= vcg::tri::io::Mask::IOM_WEDGNORMAL;  break;
                                case commandline::texture : i++; fprintf(logfp,"wedge tex coords, "); mask |= vcg::tri::io::Mask::IOM_WEDGTEXCOORD;break;
                                default :  i++; fprintf(logfp,"WARNING: unknowns per WEDGE attribute '%s'",argv[i+1]);break;
                                }
                                break;
                            }

                        case commandline::saveparam :
                             {
                                switch( argv[i][1])
                                {
                                    case commandline::ascii:
                                        {
                                            writebinary = false;
                                            i++;
                                            break;
                                         }
                                }
                                break;
                             }
						case commandline::mesh :
							{
								switch (argv[i][1])
								{
								case commandline::polygon: i++; fprintf(logfp, "mesh polygon, "); mask |= vcg::tri::io::Mask::IOM_BITPOLYGONAL;   break;
								default:  i++; fprintf(logfp, "WARNING: unknowns per MESH attribute '%s'", argv[i + 1]); break;
								}
								break;
							}
                        default :  i++; fprintf(logfp,"WARNING: unknowns attribute '%s'",argv[i]);break;
                        }
                    }while (((i) < argc) && (argv[i][0] != '-'));
                }
                else
                    ++i;
                outfl.mask = mask;
                outfl.writebinary = writebinary;
                outmeshlist << outfl;
                break;
            }
        case commandline::script :
            {
                QFileInfo fi(argv[i+1]);
                QString scriptName = fi.absoluteFilePath();
                scriptfiles << scriptName;
                i += 2;
                break;
            }
        case commandline::log :
            {
                //freopen redirect both std::cout and printf. Now I'm quite sure i will get everything the plugins will print in the standard output (i hope no one used std::cerr...)
                logfp = fopen(argv[i+1],"a");
                if (logfp == NULL)
                    printf("Error occurred opening file %s. It's not possible to redirect the output from the stdout\n",argv[i+1]);
                else
                    printf("Log is saved in %s\n", argv[i+1]);
                i += 2;
                break;
            }
        case commandline::dump :
            {
                dumpfp = fopen(argv[i+1],"w");
                if (dumpfp == NULL)
                    fprintf(logfp,"Error occurred opening file %s. It's not possible to redirect the output from the stdout\n",argv[i+1]);
                else
                {
                    server.dumpPluginInfoDoxygen(dumpfp);
                    fclose(dumpfp);
                    fprintf(logfp,"Dump file is saved in %s\n", argv[i+1]);
                }
                i+=2;
                break;
            }
        default:
            {
                printf("Something bad happened parsing the document. String %s\n",qUtf8Printable(argv[i]));
				//system("pause");
                exit(-1);
            }
        }
    }

    for(int ii = 0; ii < scriptfiles.size();++ii)
    {
        fprintf(logfp,"Apply FilterScript: '%s'\n",qUtf8Printable(scriptfiles[ii]));
        bool returnValue = server.script(meshDocument, scriptfiles[ii],logfp);
        if(!returnValue)
        {
            fprintf(logfp,"Failed to apply script file %s\n",qUtf8Printable(scriptfiles[ii]));
			//system("pause");
            exit(-1);
        }
    }

    for(int ii = 0;ii < outprojectfiles.size();++ii)
    {
        QString outfilemiddlename = "";
        if (!outprojectfiles[ii].overwrite)
        {
            outfilemiddlename = "_out";
            if (ii >= 1)
                outfilemiddlename += QString::number(ii);
        }
        bool saved = server.saveProject(meshDocument,outprojectfiles[ii].filename,outfilemiddlename);
        if (saved)
            fprintf(logfp,"Output project has been saved in %s.\n",qUtf8Printable(outprojectfiles[ii].filename));
        else
        {
            fprintf(logfp,"Project %s has not been correctly saved in. MeshLabServer Application will exit.\n",qUtf8Printable(outprojectfiles[ii].filename));
			//system("pause");
            exit(-1);
        }
    }

	if (meshDocument.size() < outmeshlist.size())
		fprintf(logfp, "Error: trying to save %i meshes, but only %i available in the project\n", outmeshlist.size(), meshDocument.size());

	for (int ii = 0; ii < outmeshlist.size(); ++ii)
	{
		bool exported = false;
		if (outmeshlist[ii].layerposition < meshDocument.meshList.size())
		{
			int layertobesaved = outmeshlist[ii].layerposition;

			if (layertobesaved == OutFileMesh::lastlayerconst)
				layertobesaved = meshDocument.meshList.size() - 1;
			else
				if (layertobesaved == OutFileMesh::currentlayerconst)
					layertobesaved = meshDocument.meshList.indexOf(meshDocument.mm());

			if ((layertobesaved >= 0) && (layertobesaved < meshDocument.meshList.size()))
			{
				MeshModel* meshmod = meshDocument.meshList[layertobesaved];
				if (meshmod != NULL)
					exported = server.exportMesh(meshDocument.meshList[layertobesaved], outmeshlist[ii].mask, outmeshlist[ii].filename, outmeshlist[ii].writebinary, logfp);
				if (exported)
					fprintf(logfp, "Mesh %s saved as %s (%i vn %i fn)\n", qUtf8Printable(meshmod->fullName()), qUtf8Printable(outmeshlist[ii].filename), meshmod->cm.vn, meshmod->cm.fn);
				else
					fprintf(logfp, "Output mesh %s has NOT been saved\n", qUtf8Printable(outmeshlist[ii].filename));
			}
			else
				fprintf(logfp, "Output mesh %s has NOT been saved. A not existent layer has been requested to be saved\n", qUtf8Printable(outmeshlist[ii].filename));
		}
		else
			fprintf(logfp, "Invalid layer number %i. Last layer in the current document is the number %i. Output mesh %s will not be saved\n", outmeshlist[ii].layerposition, meshDocument.meshList.size() - 1, qUtf8Printable(outmeshlist[ii].filename));
	}//for(int ii


	if((logfp != NULL) && (logfp != stdout))
	{
		fclose(logfp);
	}

	shared.deAllocateGPUSharedData();
	//system("pause");
	return 0;
}//int main()
