#ifndef MIQ_SEAMS_INTIALIZER
#define MIQ_SEAMS_INTIALIZER
#include <vcg/complex/complex.h>
#include <vcg/simplex/face/pos.h>
#include <vcg/simplex/face/jumping_pos.h>
#include <vcg/simplex/face/topology.h>
#include <wrap/io_trimesh/import_field.h>

template <class MeshType>
class SeamsInitializer
{

private:
    typedef typename MeshType::ScalarType ScalarType;
    typedef typename MeshType::FaceType FaceType;
    typedef typename MeshType::VertexType VertexType;
    typedef typename MeshType::CoordType CoordType;
    typedef typename MeshType::FaceIterator FaceIterator;

    MeshType *mesh;

    ///per face per edge of mmatch in the solver
    typename  MeshType::template PerFaceAttributeHandle<vcg::Point3i> Handle_MMatch;
    ///per vertex singular or not
    typename  MeshType::template PerVertexAttributeHandle<bool> Handle_Singular;
    ///per vertex degree of a singularity
    typename  MeshType::template PerVertexAttributeHandle<int> Handle_SingularDegree;
    ///seam per face
    typename  MeshType::template PerFaceAttributeHandle<vcg::Point3<bool> > Handle_Seams;
    ///seam index per face
    typename  MeshType::template PerFaceAttributeHandle<vcg::Point3i > Handle_SeamsIndex;

    bool IsRotSeam(const FaceType *f0,const int edge)
    {
        unsigned char MM=Handle_MMatch[f0][edge];//MissMatch(f0,edge);
        return (MM!=0);
    }

    ///return true if a vertex is singluar by looking at initialized missmatches
    bool IsSingularByMMatch(const VertexType &v,int &missmatch)
    {
        ///check that is on border..
        if (v.IsB())return false;

        std::vector<FaceType*> faces;
        std::vector<int> edges;

        vcg::face::Pos<FaceType> pos(v.cVFp(), v.cVFi());
        vcg::face::VFOrderedStarFF(pos,faces,edges);

        missmatch=0;
        for (unsigned int i=0;i<faces.size();i++)
        {
            FaceType *curr_f=faces[i];
            int currMM=Handle_MMatch[curr_f][edges[i]];
            missmatch+=currMM;
        }
        missmatch=missmatch%4;
        return(missmatch!=0);
    }

    ///initialized mapping structures if are not already initialized
    void AddAttributesIfNeeded()
    {

        bool HasHandleMMatch=vcg::tri::HasPerFaceAttribute(*mesh,std::string("MissMatch"));
        if (!HasHandleMMatch)
            Handle_MMatch = vcg::tri::Allocator<MeshType>::template AddPerFaceAttribute<vcg::Point3i>(*mesh,std::string("MissMatch"));
        else
            Handle_MMatch = vcg::tri::Allocator<MeshType>::template GetPerFaceAttribute<vcg::Point3i>(*mesh,std::string("MissMatch"));

        bool HasHandleSingularity=vcg::tri::HasPerVertexAttribute(*mesh,std::string("Singular"));
        if (!HasHandleSingularity)
            Handle_Singular=vcg::tri::Allocator<MeshType>::template AddPerVertexAttribute<bool>(*mesh,std::string("Singular"));
        else
            Handle_Singular=vcg::tri::Allocator<MeshType>::template GetPerVertexAttribute<bool>(*mesh,std::string("Singular"));

        bool HasHandleSingularityDegree=vcg::tri::HasPerVertexAttribute(*mesh,std::string("SingularityDegree"));
        if (!HasHandleSingularityDegree)
            Handle_SingularDegree=vcg::tri::Allocator<MeshType>::template AddPerVertexAttribute<int>(*mesh,std::string("SingularityDegree"));
        else
            Handle_SingularDegree=vcg::tri::Allocator<MeshType>::template GetPerVertexAttribute<int>(*mesh,std::string("SingularityDegree"));

        bool HasHandleSeams=vcg::tri::HasPerFaceAttribute(*mesh,std::string("Seams"));
        if (!HasHandleSeams)
            Handle_Seams=vcg::tri::Allocator<MeshType>::template AddPerFaceAttribute<vcg::Point3<bool> >(*mesh,std::string("Seams"));
        else
            Handle_Seams=vcg::tri::Allocator<MeshType>::template GetPerFaceAttribute<vcg::Point3<bool> >(*mesh,std::string("Seams"));

        bool HasHandleSeamsIndex=vcg::tri::HasPerFaceAttribute(*mesh,std::string("SeamsIndex"));
        if (!HasHandleSeamsIndex)
            Handle_SeamsIndex=vcg::tri::Allocator<MeshType>::template AddPerFaceAttribute<vcg::Point3i >(*mesh,std::string("SeamsIndex"));
        else
            Handle_SeamsIndex=vcg::tri::Allocator<MeshType>::template GetPerFaceAttribute<vcg::Point3i >(*mesh,std::string("SeamsIndex"));
    }


    void FloodFill(FaceType* start)
    {
        std::deque<FaceType*> d;
        ///clean the visited flag
        start->SetV();
        d.push_back(start);

        while (!d.empty()){
            FaceType *f = d.at(0); d.pop_front();
            for (int s = 0; s<3; s++)
            {
                FaceType *g = f->FFp(s);
                int j = f->FFi(s);
                if ((!(IsRotSeam(f,s))) && (!(IsRotSeam(g,j)))  && (!g->IsV()) )
                {
                    Handle_Seams[f][s]=false;
                    Handle_Seams[g][j]=false;
                    g->SetV();
                    d.push_back(g);
                }
            }
        }
    }

    void Retract(){
        std::vector<int> e(mesh->vert.size(),0); // number of edges per vert
        VertexType *vb = &(mesh->vert[0]);
        for (FaceIterator f = mesh->face.begin(); f!=mesh->face.end(); f++) if (!f->IsD()){
            for (int s = 0; s<3; s++){
                //if (f->seam[s])
                if (Handle_Seams[f][s])
                    if (f->FFp(s)<=&*f)  {
                        e[ f->V(s) - vb ] ++;
                        e[ f->V1(s) - vb ] ++;
                    }
            }
        }
        bool over=true;
        int guard = 0;
        do {
            over = true;
            for (FaceIterator f = mesh->face.begin(); f!=mesh->face.end(); f++) if (!f->IsD()){
                for (int s = 0; s<3; s++){
                    //if (f->seam[s])
                    if (Handle_Seams[f][s])
                        if (!(IsRotSeam(&(*f),s))) // never retract rot seams
                            //if (f->FFp(s)<=&*f)
                        {
                            if (e[ f->V(s) - vb ] == 1) {
                                // dissolve seam
                                //f->seam[s] = false;
                                Handle_Seams[f][s]=false;
                                //f->FFp(s)->seam[(int)f->FFi(s)] = false;
                                Handle_Seams[f->FFp(s)][(int)f->FFi(s)]=false;
                                e[ f->V(s) - vb ] --;
                                e[ f->V1(s) - vb ] --;
                                over = false;
                            }
                        }
                }
            }

            if (guard++>10000) over = true;

        } while (!over);
    }

    void AddSeamsByMM()
    {
        for (unsigned int i=0;i<mesh->face.size();i++)
        {
            FaceType *f=&mesh->face[i];
            if (f->IsD())continue;
            for (int j=0;j<3;j++)
            {
                if (IsRotSeam(f,j))
                    Handle_Seams[f][j]=true;
                    //f->SetSeam(j);
            }
        }
    }

    void SelectSingularityByMM()
    {

        for (unsigned int i=0;i<mesh->vert.size();i++)
        {
            if (mesh->vert[i].IsD())continue;
            int missmatch;
            bool isSing=IsSingularByMMatch(mesh->vert[i],missmatch);
            if (isSing)
            {
                //mesh->vert[i].SetS();
                Handle_Singular[i]=true;
                Handle_SingularDegree[i]=missmatch;
            }
            else
            {
                //mesh->vert[i].ClearS();
                Handle_Singular[i]=false;
                Handle_SingularDegree[i]=0;
            }
        }
    }


    int InitTopologycalCuts(){
        vcg::tri::UpdateFlags<MeshType>::FaceClearV(*mesh);

        for (FaceIterator f = mesh->face.begin(); f!=mesh->face.end(); f++)
            if (!f->IsD())
            {
                Handle_Seams[f][0]=true;
                Handle_Seams[f][1]=true;
                Handle_Seams[f][2]=true;
            }

        int index=0;
        for (FaceIterator f = mesh->face.begin(); f!=mesh->face.end(); f++)
            if (!f->IsD())
            {
                if (!f->IsV())
                {
                    index++;
                    FloodFill(&*f);
                }
            }

        Retract();
        return index;
    }

    void InitMMatch()
    {
        for (unsigned int i=0;i<mesh->face.size();i++)
        {
            FaceType *curr_f=&mesh->face[i];
            for (int j=0;j<3;j++)
            {
                FaceType *opp_f=curr_f->FFp(j);
                if (curr_f==opp_f)
                    Handle_MMatch[curr_f][j]=0;
                else
                    Handle_MMatch[curr_f][j]=vcg::tri::CrossField<MeshType>::MissMatchByCross(*curr_f,*opp_f);
            }
        }
    }

    void InitSeamIndexes()
    {
        ///initialize seams indexes
        for (unsigned int i=0;i<mesh->face.size();i++)
        {
            FaceType *f=&mesh->face[i];
            for (int j=0;j<3;j++)
               Handle_SeamsIndex[f][j]=-1;
        }

        std::vector<std::vector<std::pair<FaceType*,int> > > seamsVert;
        seamsVert.resize(mesh->vert.size());
        for (unsigned int i=0;i<mesh->face.size();i++)
        {
            FaceType *f=&mesh->face[i];
             for (int j=0;j<3;j++)
             {
                 if (f->IsB(j))continue;
                 if (Handle_Seams[f][j])
                 {
                     VertexType *v0=f->V0(j);
                     VertexType *v1=f->V1(j);
                     if (v0>v1)continue;
                     int index0=v0-&mesh->vert[0];
                     int index1=v1-&mesh->vert[0];
                     std::pair<FaceType*,int> entry=std::pair<FaceType*,int>(f,j);
                     seamsVert[index0].push_back(entry);
                     seamsVert[index1].push_back(entry);
                 }
             }
        }

        int curr_index=0;
        for (unsigned int i=0;i<mesh->vert.size();i++)
        {
            VertexType *v_seam=&mesh->vert[i];
            bool IsVertex=(Handle_Singular[i])||(seamsVert[i].size()>2)||
                          (v_seam->IsB());

            if(!IsVertex)continue;

            ///then follows the seam
            for (int j=0;j<seamsVert[i].size();j++)
            {
                FaceType *f=seamsVert[i][j].first;
                int edge=seamsVert[i][j].second;

                VertexType *v0=v_seam;
                VertexType *v1=NULL;

                ///the index is already initialized
                if (Handle_SeamsIndex[f][edge]!=-1)continue;
                bool finished=false;
                do
                {
                    ///otherwise follow the index
                    Handle_SeamsIndex[f][edge]=curr_index;///set current value
                    FaceType *f1=f->FFp(edge);
                    int edge1=f->FFi(edge);
                    Handle_SeamsIndex[f1][edge1]=curr_index;///set current value

                    assert((v0==f->V0(edge))||
                           (v0==f->V1(edge)));

                    if (f->V0(edge)==v0)
                        v1=f->V1(edge);
                    else
                        v1=f->V0(edge);

                   assert(v0!=v1);
                   //int index0=v0-&mesh->vert[0];
                   int index1=v1-&mesh->vert[0];

                   bool IsVertex=(Handle_Singular[v1])||
                                 (seamsVert[index1].size()>2)||
                                 (v1->IsB());
                   if(IsVertex)
                       finished=true;
                   else
                   {
                       assert(seamsVert[index1].size()==2);
                       FaceType *f_new[2];
                       int edge_new[2];

                       f_new[0]=seamsVert[index1][0].first;
                       edge_new[0]=seamsVert[index1][0].second;
                       f_new[1]=seamsVert[index1][1].first;
                       edge_new[1]=seamsVert[index1][1].second;

                       assert((f_new[0]==f)||(f_new[1]==f));
                       assert((edge_new[0]==edge)||(edge_new[1]==edge));

                       if ((f_new[0]==f)&&(edge_new[0]==edge))
                       {
                           f=f_new[1];
                           edge=edge_new[1];
                       }
                       else
                       {
                           f=f_new[0];
                           edge=edge_new[0];
                       }
                       v0=v1;
                   }
                }
                while (!finished);
                //return;
                curr_index++;
            }
        }

    }

public:


    void Init(MeshType *_mesh,
              bool orient_globally,
              bool initMM,
              bool initCuts)
    {

        mesh=_mesh;

        vcg::tri::UpdateFlags<MeshType>::VertexBorderFromNone(*mesh);
        vcg::tri::UpdateFlags<MeshType>::FaceBorderFromNone(*mesh);

        AddAttributesIfNeeded();
        if (orient_globally)
            vcg::tri::CrossField<MeshType>::MakeDirectionFaceCoherent(*mesh);
        if (initMM)
            InitMMatch();
        SelectSingularityByMM();
        if (initCuts)
        {
            InitTopologycalCuts();
            AddSeamsByMM();
        }
        InitSeamIndexes();
    }

    SeamsInitializer(){mesh=NULL;}
};

#endif