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

//#define FILTER_PHOTOSYNTH_DEBUG 1

using namespace vcg;

// Constructor usually performs only two simple tasks of filling the two lists
//  - typeList: with all the possible id of the filtering actions
//  - actionList with the corresponding actions. If you want to add icons to your filtering actions you can do here by construction the QActions accordingly
FilterPhotosynthPlugin::FilterPhotosynthPlugin()
{
  typeList << FP_IMPORT_PHOTOSYNTH;
  foreach(FilterIDType tt , types())
    actionList << new QAction(filterName(tt), this);
}

// ST() must return the very short string describing each filtering action
// (this string is used also to define the menu entry)
QString FilterPhotosynthPlugin::filterName(FilterIDType filterId) const
{
  switch(filterId)
  {
    case FP_IMPORT_PHOTOSYNTH:  return QString("Import Photosynth data");
    default:
        assert(0);
  }
  return QString();
}

// Info() must return the longer string describing each filtering action
// (this string is used in the About plugin dialog)
QString FilterPhotosynthPlugin::filterInfo(FilterIDType filterId) const
{
  switch(filterId)
  {
    case FP_IMPORT_PHOTOSYNTH:
      return QString("Downloads the synth data from the given URL and creates a document with multiple layers, each containing a set of points");
    default:
      assert(0);
  }
  return QString("Unknown Filter");
}

// The FilterClass describes in which generic class of filters it fits.
// This choice affect the submenu in which each filter will be placed
// More than a single class can be choosen.
FilterPhotosynthPlugin::FilterClass FilterPhotosynthPlugin::getClass(QAction *a)
{
  switch(ID(a))
  {
    case FP_IMPORT_PHOTOSYNTH:
      return MeshFilterInterface::MeshCreation;
    default:
      assert(0);
  }
  return MeshFilterInterface::Generic;
}

// This function define the needed parameters for each filter. Return true if the filter has some parameters
// it is called every time, so you can set the default value of parameters according to the mesh
// For each parameter you need to define,
// - the name of the parameter,
// - the default value
// - the string shown in the dialog
// - a possibly long string describing the meaning of that parameter (shown as a popup help in the dialog)
void FilterPhotosynthPlugin::initParameterSet(QAction *action, MeshModel &/*m*/, RichParameterSet &parlst)
{
  switch(ID(action))
  {
    case FP_IMPORT_PHOTOSYNTH:
      parlst.addParam(new RichString("synthURL",
                                     "http://photosynth.net/view.aspx?cid=e8f476c5-ed00-4626-a86c-31d654e94109",
                                     "Synth URL",
                                     "Paste the synth URL from your browser."));
      parlst.addParam(new RichInt("clusterID", -1, "Cluster ID", "The ID of the cluster to download, type '-1' to download all"));
      parlst.addParam(new RichBool ("saveImages", true, "Download images", "Download images making up the specified synth."));
      parlst.addParam(new RichString("savePath",
                                     "./",
                                     "Save to",
                                     "Enter the path where images will be saved to"));
      parlst.addParam(new RichBool ("addCameraLayer", true, "Show cameras", "Add a layer with points as cameras placeholders"));
      break;
    default:
      assert(0);
  }
}

// The Real Core Function doing the actual mesh processing.
bool FilterPhotosynthPlugin::applyFilter(QAction */*filter*/, MeshDocument &md, RichParameterSet &par, vcg::CallBackPos *cb)
{
      ImportSettings settings(par.getString("synthURL"), par.getInt("clusterID"));
      if(par.getBool("saveImages"))
        settings._imageSavePath = par.getString("savePath");
        SynthData *synthData = new SynthData(settings);
        synthData->downloadSynthInfo(cb);
      //Hangs on active wait until data are available from the server
    bool clearToProceed = false;
    bool success = synthData->_mutex.tryLock();
    if(success)
    {
      clearToProceed = synthData->_dataReady;
      synthData->_mutex.unlock();
    }
    //while(!synthData->_dataReady)
    while(!clearToProceed)
      {
        //allows qt main loop to process the events relative to the response from the server,
        //triggering the signals that cause the invocation of the slots that process the response
        //and set the control variable that stops this active wait.
        //Note that a call to the function usleep() causes an infinite loop, because when the process awakes,
        //the control remains inside this loop and doesn't reach qt main loop that this way can't process events.
      QApplication::processEvents();
        cb(synthData->progressInfo(),synthData->_info.toStdString().data());
      success = synthData->_mutex.tryLock();
      if(success)
      {
        clearToProceed = synthData->_dataReady;
        synthData->_mutex.unlock();
      }
      }

      if(!synthData->isValid())
      {
        this->errorMessage = SynthData::errors[synthData->_state];
        delete synthData;
        return false;
      }
      cb(0,"Finishing import...");
      QDir imageDir(settings._imageSavePath);
      imageDir.cd(synthData->_collectionID);
    #ifdef FILTER_PHOTOSYNTH_DEBUG
      QFile cameraXMLfile(imageDir.filePath("Cam.txt"));
      bool success = true;
      if (!cameraXMLfile.open(QIODevice::WriteOnly | QIODevice::Text))
      {
        success = false;
        qWarning() << "Failed to create cam.txt";
      }
      QTextStream out(&cameraXMLfile);
    #endif
      //scan coordinate systems list and add a new layer for each one, containing its points
      const QList<CoordinateSystem*> *coordinateSystems = synthData->_coordinateSystems;
      CoordinateSystem *sys;
      int count = coordinateSystems->count();
      foreach(sys, *coordinateSystems)
      {
        cb((int)(sys->_id / count),"Finishing import...");
        if(sys->_pointCloud)
        {
          MeshModel *mm = md.addNewMesh("","coordsys"); // After Adding a mesh to a MeshDocument the new mesh is the current one
          Point p;
          foreach(p, sys->_pointCloud->_points)
            tri::Allocator<CMeshO>::AddVertex(mm->cm, Point3m(p._x,p._z,-p._y), Color4b(p._r,p._g,p._b,255));

          mm->updateDataMask(MeshModel::MM_VERTCOLOR);
          //we consider cameras only if the user chooses to download images
          if(par.getBool("saveImages"))
          {
          MeshModel *mm1 = 0;
            if(par.getBool("addCameraLayer"))
              //create a new layer where add points representing cameras to
            mm1 = md.addNewMesh("","cameras");
            CameraParameters cam;
            //scan cameras list for this coordinate system and for each one add a raster with a shot matching the camera
            foreach(cam, sys->_cameraParametersList)
            {
              Shotm s;
              s.Extrinsics.SetRot(cam.getRotation());
              s.Extrinsics.SetTra(cam.getTranslation());
              Image img = synthData->_imageMap->value(cam._imageID);
              s.Intrinsics.FocalMm = cam[CameraParameters::FOCAL_LENGTH] * qMax(img._width,img._height);
              s.Intrinsics.PixelSizeMm = Point2m(1,1);
              s.Intrinsics.ViewportPx = Point2i(img._width,img._height);
              s.Intrinsics.CenterPx = Point2m(img._width/2,img._height/2);
    #ifdef FILTER_PHOTOSYNTH_DEBUG
              if(success)
                outputToFile(out, s, img, cam);
    #endif
            //if(par.getBool("addCameraLayer"))
            if(mm1)
              {
                //add a point to the cameras layer as a placeholder for the camera
              tri::Allocator<CMeshO>::AddVertices(mm1->cm,1);
              mm1->cm.vert.back().P() = cam.getTranslation();
              }
              //add a new raster
              //the same image can be added several times, one for each coordinate system it appears into
              //this way the user can choose which point cloud the raster has to align with
              RasterModel *rm = md.addNewRaster();
              QString imgName = imageDir.filePath(QString("IMG_%1.jpg").arg(img._ID) );
              rm->addPlane(new Plane(imgName,Plane::RGBA));
              rm->shot = s;
              rm->setLabel(QString("IMG_%1_%2.jpg").arg(int(img._ID),3,10,QLatin1Char('0')).arg(sys->_id));
            }
          }
        }
      }
    #ifdef DEGUG
      cameraXMLfile.close();
    #endif
      delete synthData;
  return true;
}

QString FilterPhotosynthPlugin::filterScriptFunctionName(FilterIDType filterID)
{
  switch(filterID)
  {
    case FP_IMPORT_PHOTOSYNTH:
      return QString("importPhotosynth");
    default:
      assert(0);
  }
  return QString();
}

/*
 * Writes on out the XML representing the camera described by s
 */
void FilterPhotosynthPlugin::outputToFile(QTextStream &out, Shotf &s, Image &img, CameraParameters &cam)
{
  QString traVec = QString("TranslationVector=\"%1 %2 %3 1\"").arg(s.Extrinsics.Tra().X()).arg(s.Extrinsics.Tra().Y()).arg(s.Extrinsics.Tra().Z());
  QString lensDist("LensDistortion=\"0 0\"");
  QString viewPx = QString("ViewportPixel=\"%1 %2\"").arg(img._width).arg(img._height);
  QString pxSize = QString("PixelSizeMm=\"%1 %2\"").arg(cam._pixelSizeMm).arg(cam._pixelSizeMm);
  QString centerPx = QString("CenterPx=\"%1 %2\"").arg(img._width/2).arg(img._height/2);
  QString focalMm = QString("FocalMm=\"%1\"").arg(s.Intrinsics.FocalMm);
  out << QString("Camera %1 (Image %2: %3): ").arg(cam._camID).arg(img._ID).arg(img._url) << "\n\n";
  out << "<!DOCTYPE ViewState>\n<project>\n";
  out << " <VCGCamera ";
  out << traVec << " ";
  out << lensDist << " ";
  out << viewPx << " ";
  out << pxSize << " ";
  out << centerPx << " ";
  out << focalMm << " ";
  out << "RotationMatrix=\"";
  unsigned int i, j;
  for(i = 0; i < 4; i++)
    for(j = 0; j < 4; j++)
      out << s.Extrinsics.Rot().ElementAt(i,j) << " ";
  out << "\" />\n";
  out << " <ViewSettings NearPlane=\"0\" TrackScale=\"0.299015\" FarPlane=\"13.0311\"/>\n";
  out << " <Render Lighting=\"0\" DoubleSideLighting=\"0\" SelectedVert=\"0\" ColorMode=\"3\" SelectedFace=\"0\" BackFaceCull=\"0\" FancyLighting=\"0\" DrawMode=\"2\" TextureMode=\"0\"/>\n";
  out << "</project>\n\n\n\n";
}

MESHLAB_PLUGIN_NAME_EXPORTER(FilterPhotosynthPlugin)
