/****************************************************************************
* VCGLib                                                            o o     *
* Visual and Computer Graphics Library                            o     o   *
*                                                                _   O  _   *
* Copyright(C) 2004-2016                                           \/)\/    *
* 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.                                                         *
*                                                                           *
****************************************************************************/

#ifndef OVERLAP_ESTIMATION_H
#define OVERLAP_ESTIMATION_H

#include <vcg/math/gen_normal.h>
#include <vcg/math/random_generator.h>
#include <vcg/space/index/grid_static_ptr.h>
#include <vcg/complex/algorithms/closest.h>
#include <vcg/complex/algorithms/point_sampling.h>

#include <qdatetime.h>

/** \brief This class provides a strategy to estimate the overlap percentage of two range maps/point clouds.
 *
 * This class can be used, for exemple, into an automatic alignment process to check the quality of the
 * transformation found; the idea is that bad alignments should have a small overlap. Two points are
 * considered 'overlapping in the righ way' if they are close (i.e distance is less then \c consensusDist)
 * and at the same time points' normals match quite well (i.e the angle between them is less then
 *  \c consensusNormalsAngle). The test to compute the overlap is perfomed on a given number of points
 * (2500 is the default) sampled in a normal equalized way (default) or uniformly.
 * \author Francesco Tonarelli
 */
template<class MESH_TYPE> class OverlapEstimation
{
    public:

    typedef MESH_TYPE MeshType;
    typedef typename MeshType::ScalarType ScalarType;
    typedef typename MeshType::CoordType CoordType;
    typedef typename MeshType::VertexType VertexType;
    typedef typename MeshType::FaceType FaceType;
    typedef typename MeshType::VertexPointer VertexPointer;
    typedef typename MeshType::VertexIterator VertexIterator;
    typedef typename std::vector<VertexPointer>::iterator VertexPointerIterator;
    typedef vcg::GridStaticPtr<VertexType, ScalarType > MeshGrid;
    typedef vcg::tri::EmptyTMark<MeshType> MarkerVertex;

    private:
    /** Private simple class needed to perform sampling of pointers to vertexes. */
    class VertexPointerSampler
    {
        public:

        MeshType* m;  //this is needed for advanced sampling (i.e poisson sampling)

        VertexPointerSampler(){ m = new MeshType(); m->Tr.SetIdentity(); m->sfn=0; }
        ~VertexPointerSampler(){ if(m) delete m; }
        std::vector<VertexType*> sampleVec;

        void AddVert(VertexType &p){ sampleVec.push_back(&p); } //this function is the only we really need
        void AddFace(const FaceType &f, const CoordType &p){}
        void AddTextureSample(const FaceType &, const CoordType &, const Point2i &){}
    };

    public:
    /** \brief Public class to hold parameters. Used to avoid endless list of parameters inside functions.
      * \author Francesco Tonarelli
      */
    class Parameters
    {
        public:
        int samples;                                    ///< Number of samples to check to compute the overlap. Higher values get more accurancy but requires more time.
        int bestScore;                                  ///< Score to overcome to paint \c mMov . If overlap estimation is called many times inside a loop, you can set this value in each iteration to paint \c mMov and see only the best overlap achived.
        float consensusDist;                            ///< Consensus distance. Lower values should gat more accurancy; high values can lead to performance hit.
        float consensusNormalsAngle;                    ///< Holds the the consensus angle for normals, in gradients. Lower values decrease accurancy, particulary for range maps with many peaks and high frequencies.
        float threshold;                                ///< Consensus percentage requested to win consensus. Used to paint \c mMov. If the overlap overcames the \c threshold (and \c bestScore), \c mMov is painted.
        bool normalEqualization;                        ///< Allows to use normal equalization sampling in consensus. If set to \c false uniform sampling is used instead. Uniform sampling is faster but less accurate.
        bool paint;                                     ///< Allows painting of \c mMov according to consensus. See Paint() for details.
        void (*log)(int level, const char * f, ... );   ///< Pointer to a log function.

        /** Constructor with default values. */
        Parameters()
        {
            samples = 2500;
            bestScore = 0;
            consensusDist = 2.0f;
            consensusNormalsAngle = 0.965f;   //15 degrees.
            threshold = 0.0f;
            normalEqualization = true;
            paint = false;
            log = NULL;
        }
    };

    private:
    MeshType* mFix;                             /** Pointer to mesh \c mFix. */
    MeshType* mMov;                             /** Pointer to mesh \c mMov. */
    std::vector<std::vector<int> >* normBuckets;          //structure to hold normals bucketing. Needed for normal equalized sampling during consensus
    MeshGrid* gridFix;                          //variable to manage uniform grid
    MarkerVertex markerFunctorFix;              //variable to manage uniform grid

    public:
    /** Default constructor. */
    OverlapEstimation() : normBuckets(NULL), gridFix(NULL){}
    /** Default destructor. Deallocates structures. */
    ~OverlapEstimation(){
        if(normBuckets) delete normBuckets;
        if(gridFix) delete gridFix;
    }
    /** Set the fix mesh \c mFix. */
    void SetFix(MeshType& m){ mFix = &m; }
    /** Set the move mesh \c mMov. */
    void SetMove(MeshType& m){ mMov = &m; }

    /** Paint \c mMov according to the overlap estimation result. Works only if \c Compute() or \c Check() have
     *  been previously called with \c Parameters.paint=true .<br>Legend: \arg \e red: points overlaps correctly.
     *  \arg \e blue: points are too far to overlap. \arg \e yellow: points are close, but normals mismatch.
     */
    void Paint()
    {
        for(VertexIterator vi=mMov->vert.begin(); vi!=mMov->vert.end(); vi++){
            if(!(*vi).IsD()){
                if((*vi).Q()==0.0) (*vi).C() = Color4b::Red;
                if((*vi).Q()==1.0) (*vi).C() = Color4b::Yellow;
                if((*vi).Q()==2.0) (*vi).C() = Color4b::Blue;
            }
        }
    }

    /** Initializes structures.
     *  @param param A reference to a \c Parameter class containing all the desidered options to estimate overlap.
     *  \return \c true if everything goes right.
     */
    bool Init(Parameters& param){
        //builds the uniform grid with mFix vertices
        gridFix = new MeshGrid();
        SetupGrid();

        //if requested, group normals of mMov into 30 buckets. Buckets are used for Vertex Normal Equalization
        //in consensus. Bucketing is done here once for all to speed up consensus.
        if(normBuckets) {normBuckets->clear(); delete normBuckets; }
        if(param.normalEqualization){
            normBuckets = BucketVertexNormal(mMov->vert, 30);
            assert(normBuckets);
        }
        return true;
    }

    /** Compute the overlap estimation between \c mFix and \c mMov.
     *  @param param A reference to a \c Parameter class containing all the desidered options to estimate overlap.
     *  \return The percentage of overlap in the range \c [0..1] .
     */
    float Compute(Parameters& param)
    {
        return Check(param)/float(param.samples);
    }

    /** Compute the overlap estimation between \c mFix and \c mMov.
     *  @param param A reference to a \c Parameter class containing all the desidered options to estimate overlap.
     *  \return The number of points that overlap correctly. This number is in the range \c [0..param.samples] .
     */
    //IMPORTANT: per vertex normals of mMov and mFix MUST BE PROVIDED YET NORMALIZED!!!
    int Check(Parameters& param)
    {
        //pointer to a function to compute distance beetween points
        vertex::PointDistanceFunctor<ScalarType> PDistFunct;

        //if no buckets are provided get a vector of vertex pointers sampled uniformly
        //else, get a vector of vertex pointers sampled in a normal equalized manner; used as query points
        std::vector<VertexPointer> queryVert;
        if(param.normalEqualization){
            assert(normBuckets);
            for(unsigned int i=0; i<mMov->vert.size(); i++) queryVert.push_back(&(mMov->vert[i]));//do a copy of pointers to vertexes
            SampleVertNormalEqualized(queryVert, param.samples);
        }
        else{
            SampleVertUniform(*mMov, queryVert, param.samples);
        }
        assert(queryVert.size()!=0);

        //init variables for consensus
        float consDist = param.consensusDist*(mMov->bbox.Diag()/100.0f);  //consensus distance
        int cons_succ = int(param.threshold*(param.samples/100.0f));      //score needed to pass consensus
        int consensus = 0;                  //counts vertices in consensus
        float dist;                         //holds the distance of the closest vertex found
        VertexType* closestVertex = NULL;   //pointer to the closest vertex
        Point3<ScalarType> queryNrm;        //the query point normal for consensus
        CoordType queryPnt;                 //the query point for consensus
        CoordType closestPnt;               //the closest point found in consensus
        Matrix33<ScalarType> inv33_matMov(mMov->Tr,3);          //3x3 matrix needed to transform normals
        Matrix33<ScalarType> inv33_matFix(Inverse(mFix->Tr),3); //3x3 matrix needed to transform normals

        //consensus loop
        VertexPointerIterator vi; int i;
        for(i=0, vi=queryVert.begin(); vi!=queryVert.end(); vi++, i++)
        {
            dist = -1.0f;
            //set query point; vertex coord is transformed properly in fix mesh coordinates space; the same for normals
            queryPnt = Inverse(mFix->Tr) * (mMov->Tr * (*vi)->P());
            queryNrm = inv33_matFix * (inv33_matMov * (*vi)->N());
            //if query point is bbox, the look for a vertex in cDist from the query point
            if(mFix->bbox.IsIn(queryPnt)) closestVertex = gridFix->GetClosest(PDistFunct,markerFunctorFix,queryPnt,consDist,dist,closestPnt);
            else closestVertex=NULL;  //out of bbox, we consider the point not in consensus...

            if(closestVertex!=NULL && dist < consDist){
                assert(closestVertex->P()==closestPnt); //coord and vertex pointer returned by getClosest must be the same

                //point is in consensus distance, now we check if normals are near
                if(queryNrm.dot(closestVertex->N())>param.consensusNormalsAngle)  //15 degrees
                {
                    consensus++;  //got consensus
                    if(param.paint) (*vi)->Q() = 0.0f;  //store 0 as quality
                }
                else{
                    if(param.paint) (*vi)->Q() = 1.0f;  //store 1 as quality
                }
            }
            else{
                if(param.paint) (*vi)->Q() = 2.0f;  //store 2 as quality
            }
        }

        //Paint the mesh only if required and if consensus is the best ever found. Colors have been stores as numbers into quality attribute
        if(param.paint){
            if(consensus>=param.bestScore && consensus>=cons_succ) Paint();
        }

        return consensus;
    }

    private:
    /** Fill the vector \c vert with \c sampleNum pointers to vertexes sampled uniformly from mesh \c m .
      * @param m Source mesh.
      * @param vert Destination vector.
      * @param sampleNum Requested number of vertexes.
      */
    void SampleVertUniform(MESH_TYPE& m, std::vector<typename MESH_TYPE::VertexPointer>& vert, int sampleNum)
    {
        VertexPointerSampler sampler;
        tri::SurfaceSampling<MeshType, VertexPointerSampler>::VertexUniform(m, sampler, sampleNum);
        for(unsigned int i=0; i<sampler.sampleVec.size(); i++) vert.push_back(sampler.sampleVec[i]);
    }
    /** Buckets normals of the vertexes contained in \c vert .
      * \return A vector of vectors containing indexes to \c vert .
      */
    std::vector<std::vector<int> >* BucketVertexNormal(typename MESH_TYPE::VertContainer& vert, int bucketDim = 30)
    {
        static vector<Point3f> NV;
        if(NV.size()==0) GenNormal<float>::Uniform(bucketDim,NV);

        std::vector<std::vector<int> >* BKT = new std::vector<std::vector<int> >(NV.size()); //NV size is greater then bucketDim, so don't change this!

        int ind;
        for(int i=0;i<vert.size();++i){
            ind=GenNormal<float>::BestMatchingNormal(vert[i].N(),NV);
            (*BKT)[ind].push_back(i);
        }

        return BKT;
    }
    /** Samples the \c vert vector in a normal equalized way.
      * \return \c SampleNum pointers to vertexes sampled in a normal equalized way. Pointers are stored in
      * the \c vert (i.e it is an \c in/out parameter).
      */
    bool SampleVertNormalEqualized(vector<typename MESH_TYPE::VertexPointer>& vert, int SampleNum)
    {
        assert(normBuckets);
        // vettore di contatori per sapere quanti punti ho gia' preso per ogni bucket
        std::vector<int> BKTpos(normBuckets->size(),0);

        if(SampleNum >= int(vert.size())) SampleNum= int(vert.size()-1);

        int ind;
        for(int i=0;i<SampleNum;){
            ind=LocRnd(normBuckets->size()); // Scelgo un Bucket
            int &CURpos = BKTpos[ind];
            std::vector<int> &CUR = (*normBuckets)[ind];

            if(CURpos<int(CUR.size())){
                swap(CUR[CURpos], CUR[ CURpos + LocRnd((*normBuckets)[ind].size()-CURpos)]);
                swap(vert[i],vert[CUR[CURpos]]);
                ++BKTpos[ind];
                ++i;
            }
        }

        vert.resize(SampleNum);
        return true;
    }
    /** Function to retrieve a static random number generator object.
      * \return A \c SubtractiveRingRNG object.
      */
    static math::SubtractiveRingRNG &LocRnd(){
        static math::SubtractiveRingRNG myrnd(time(NULL));
        return myrnd;
    }
    /** Gets a random number in the interval \c [0..n] . Number is
      * produced by a \c SubtractiveRingRNG object initialized once for all.
      * \return A random number in the interval \c [0..n] .
      */
    static int LocRnd(int n){
        return LocRnd().generate(n);
    }
    /** Put \c mFix into a grid. */
    inline void SetupGrid()
    {
        gridFix->Set(mFix->vert.begin(),mFix->vert.end());
        markerFunctorFix.SetMesh(mFix);
    }
};

#endif // OVERLAP_ESTIMATION_H
