/* This file is part of the source code for the following publication:
 * Assembling Self-Supporting Structures, Deuss et al., SIGGRAPH Asia 2014
 *
 * Copyright (C) 2014 Mario Deuss <mario.deuss@epfl.ch>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef SELF_ASSEMBLY_H
#define SELF_ASSEMBLY_H

#include "SelfAssemblyDefines.h"
#include "Statistics.h"
#include <unordered_set>

namespace SelfAssembly{

class Assembly;

//Face
class F{
public:
	typedef Eigen::Matrix3d B; //a basis for the triangles in the face, u,v and normal
	F( int bI ):bI_(bI){;}
	virtual ~F(){;}

	unsigned int nVi() const { return vI_.size();}
	void addVi( Idx v ){ vI_.push_back(v);}
	const Idx& vi( Idx i ) const { return vI_.at(i);}
	
	void setBi( Idx b ){ bI_=b; }
	const Idx& bi() const { return bI_; }

	unsigned int nT() const { return ts_.size(); }
	const T& t( Idx i) const { return ts_.at(i); }
	bool triangulate( const Assembly& a );

	void vertices( const Assembly& a, Eigen::Matrix3Xd& verts ) const;
	void lsqPlane(const Assembly& a, Eigen::Matrix3d& basis, V& mean) const;
	V cog( const Assembly& a ) const;
	
	B basis(int ti, const Assembly &a) const;

private:
	Idxs vI_; //Vertex indices
	Idx bI_; //Block index
	Ts ts_; //Triangle indices
};

typedef std::vector<F> Fs;

//Block
class B{
public:
	typedef std::unordered_set<int> Clusters;

	B():hook_(V::Constant(MYNAN)),hookF_(-1),hookT_(-1),
		fixed_(false),enabled_(true),
		centroid_(V::Constant(MYNAN)),
		vol_(MYNAN),clusters_(0),blockSet_(-1){;}
	virtual ~B(){;}
	
	void setFixed( bool f ){ fixed_=f;}
	bool fixed() const { return fixed_;}
	
	void setEnabled( bool b ){ enabled_=b;}
	bool enabled() const { return enabled_; }
	
	unsigned int nFi() const{ return fI_.size();}
	unsigned int addFi( const int& _f){ fI_.push_back(_f); return fI_.size()-1;}
	const int& fi( int i ) const { return fI_.at(i); }
	
	const V& hook() const { return hook_;}
	void setHook( const V& h, Idx hookFace, Idx hookTriangle ){ hook_=h; hookF_=hookFace; hookT_=hookTriangle; }
	bool hasHook() const { return !std::isnan(hook_.squaredNorm());}
	bool setHookByRay(const V& o, const V& dir, const std::vector<int>& faces , Assembly &a);

	Idx hookFace() const{ if(!hasHook()){ return -1;} return hookF_;}
	Idx hookTriangle() const{ if(!hasHook()){ return -1;} return hookT_;}
	
	void clearInterfaces(){ ii_.clear();}
	unsigned int nIi() const { return ii_.size(); }
	void addIi( Idx _ii ){ ii_.push_back(_ii); }
	const Idx& ii( Idx i) const { return ii_.at(i); }
	
	unsigned int nCi() const { return ci_.size(); }
	void addCi( Idx _ci ){ ci_.push_back(_ci); }
	const Idx& ci( Idx i) const { return ci_.at(i); }
	
	void setCentroid(const V& _c){ centroid_=_c;}
	const V& centroid() const { return centroid_;}
	
	void setVolume( double _vol ){ vol_=_vol;}
	double volume() const { return vol_;}

	unsigned int nClusters(){ return clusters_.size(); }
	void addCluster( int c ){ clusters_.insert(c);}
	void removeCluster( int c ){ clusters_.erase(c);}
	void clearClusters(){ clusters_.clear();}
	bool hasCluster( int c ) const { return std::find(clusters_.begin(),clusters_.end(),c)!=clusters_.end();}
	int cluster() const { assert(clusters_.size()==1); return *clusters_.begin();}
	Clusters::const_iterator clustersBegin() const { return clusters_.begin(); }
	Clusters::const_iterator clustersEnd() const { return clusters_.end(); }

	Idx blockSet() const { return blockSet_;}
	void setBlockSet( Idx bsi ){ blockSet_=bsi; }

private:
	std::vector<int> fI_; //Face indices
	V hook_; //Position at which chain is attached
	Idx hookF_; //Face at which chain is attached
	Idx hookT_; //Triangle at which chain is attached
	bool fixed_;
	bool enabled_;
	Idxs ii_; //interface indices
	Idxs ci_; //cluster indices
	
	V centroid_;
	double vol_; //Volume

	Clusters clusters_; //Clusters for region growing
	Idx blockSet_;
};

//Interface
class I{

public:
	typedef Eigen::Matrix3d Basis;
	typedef Eigen::Vector3d Mean;
	typedef Eigen::Matrix<double,3,Eigen::Dynamic> Matrix3Xd;
	typedef std::pair<V,double> Sphere;
	typedef std::vector<Sphere> Spheres;
	typedef std::pair<Basis,V> Frame;
	typedef std::vector<Frame> Frames;
	
	I( unsigned int _b0,unsigned int _b1, Assembly& _a );
	I( unsigned int _b0,unsigned int _b1, int _f0, int _f1, Assembly& _a, std::vector<unsigned int> _multiplicities );
	
	bool findFaces(Assembly &_a, bool debug=false);
	bool computeBasis(Assembly& _a);
	void computeInterface(Assembly &_a);
	
	bool enabled( const Assembly &_a) const;
	
	bool selected() const { return selected_;}
	void setSelected( bool s ){ selected_=s;}

	void setScale( double s ){ scale_=s; }
	double scale() const { return scale_; }
	
	const unsigned int& bi	( unsigned int i ) const { assert(i<2); return bI_[i];}
	const int& fi	( unsigned int i ) const { assert(i<2); return fI_[i];}
	
	//Vertices of the incident faces, projected onto the basis. Same order as in corresponding faces
	unsigned int nV() const { return vertices_.size();}
	V v( int i ) const { return (vertices_.at(i)-mean_)*scale_+mean_;}
	
	const Basis& basis() const { return basis_;}
	
	void useMultiplicities( bool b ){ useMultiplicities_ = b; }
	unsigned int m( int i ) const { if(!useMultiplicities_){ return 1; }else{ return multiplicities_.at(i); }}
	void setM( int i, unsigned int m ){ multiplicities_.at(i)=m;}
	
	//For 3d printing:
	void addRegistrationSphere(const V& c, double r){ spheres_.push_back(std::make_pair(c,r)); }
	Sphere getRegistrationSphere( int si) const { return spheres_.at(si);}
	unsigned int nRegistrationSpheres() const { return spheres_.size(); }
	void clearRegistrationSpheres(){ spheres_.clear(); }
	
	//For 3d printing:
	unsigned int nFrames() const { return frames_.size(); }
	void addFrame( const V&c, const Basis& b){ frames_.push_back(std::make_pair(b,c));}
	Frame getFrame( int fi) const { return frames_.at(fi); }
	void clearFrames(){ frames_.clear(); }
	
	bool fixed(const Assembly &_a) const;
private:
	unsigned int bI_[2]; //Indices of block on each side of this interface
	int fI_[2]; //Indices of faces that touch each side of this interface
	
	Vs vertices_;
	std::vector<unsigned int> multiplicities_; //A mechanism to downscale the forces at vertices at the same location
	bool useMultiplicities_; //Disabled in all experiments
	
	bool selected_;
	
	//Contains the basis vectors of the interface:
	//basis_.col(0): u, col(1): v, col(2): normal
	//the basis' normal points away from b0_, and inside b1_
	Basis basis_;
	
	//The mean of the fitted points
	Mean mean_;
	double scale_;

	//Used for 3d printing
	Spheres spheres_;
	Frames frames_;
};

//Chain
class C{
public:
	C( const Idx& bi, const Idx& ai ):bI_(bi),aI_(ai),enabled_(true){;}
	
	const Idx& bi() const { return bI_;}
	const Idx& ai() const { return aI_;}
	
	void setEnabled( bool e);
	bool enabled( const Assembly& a) const;
	
private:
	Idx bI_; //Block index
	Idx aI_; //Anchor index
	bool enabled_;
};

typedef std::vector<I> Is; //Interfaces
typedef std::vector<B> Bs; //Blocks
typedef V A; //Anchor
typedef std::vector<A> As; //Anchors
typedef std::vector<C> Cs; //Chains

class Assembly{
	
public:
	typedef std::unordered_set<Idx> Neighbors;

	Assembly():density_(2000.),finalizedBlocks_(false),finalizedInterfaces_(false),maxCluster_(-1){;}
	virtual ~Assembly(){;}
	
	bool empty(){ return (nV()==0 && nF()==0 && nB()==0 && nI()==0 && nA()==0 && nC()==0);}
	void clear(){ vs_.clear(); fs_.clear(); bs_.clear(); is_.clear(); as_.clear(); cs_.clear();
				  finalizedBlocks_=false; finalizedInterfaces_=false; }
				  
	double aabbRadius() const;
	
	unsigned int nV() const { return vs_.size();}
	unsigned int addV( const V& _v ){ vs_.push_back(_v); return vs_.size()-1; }
	const V& v( int i ) const { return vs_.at(i);}
	
	unsigned int nF() const { return fs_.size();}
	int addF( const F& _f );
	const F& f( int i ) const { return fs_.at(i);}
	
	//All faces of _b need to be added already
	void addB( const B& _b ){ 
		for(unsigned int i=0;i<_b.nFi();++i){ assert(f(_b.fi(i)).bi()==(static_cast<Idx>(bs_.size())));}
		bs_.push_back(_b);
	}
	unsigned int nB() const { return bs_.size(); }
	const B& b( int i ) const { return bs_.at(i);}
	unsigned int nBFree() const;
	
	void enableBlock( Idx bi );
	void disableBlock( Idx bi );

	void setBlockSet( Idx bi, Idx bsi){
		bs_.at(bi).setBlockSet(bsi);
	}

	void addNeighbors(Idx bi, Neighbors& n , unsigned int nrings=1) const;
	bool areNeighbors( Idx bi, Idx bj ) const;
	
	void clearInterfaces();
	unsigned int nI() const { return is_.size();}

	void addI(unsigned int _b0, unsigned int _b1);
	void addI( unsigned int _b0,unsigned int _b1, int _f0, int _f1, std::vector<unsigned int> _multiplicities );
	const I& i( int _i ) const { return is_.at(_i);}
	void setISelected( Idx ii, bool s){is_.at(ii).setSelected(s);}

	//Number of vertices in all interfaces up to and including _i:
	int totalVertices( int _i ) const { return totalVertices_.at(_i);}
	int totalVertices() const { return totalVertices(nI());}
	
	unsigned int nA() const { return as_.size(); }
	void addA( const A& _a ){ as_.push_back(_a);}
	const A& a( int _ai ) const { return as_.at(_ai); }
	
	//Chains are initialized in finalize()
	void  enableChain( Idx ci ){ cs_.at(ci).setEnabled(true);}
	void disableChain( Idx ci ){ cs_.at(ci).setEnabled(false);}
	unsigned int nC() const { return cs_.size(); }
	const C& c( int _ci ) const { return cs_.at(_ci); }
	
	double density() const {return density_;}
	void setDensity( double d ){ density_=d;}
	
	void enableBlocks(bool disable=false, bool nonFixedOnly=false);
	
	void finalizeBlocks();
	void finalizeInterfaces();
	void volumeStatistics(Statistics &s, bool nonFixedOnly=false);
	void setHooksAboveCentroids(); //for debugging
	bool setHookByRay(Idx bi, const V& o, const V& dir, const std::vector<int>& faces );

	void addCluster( Idx bi, int cl);
	void mergeClusters(Idx bi);
	void clearClusters();
	
	void computeVolumeAndCentroid(Idx bi, double &vol, V &ctr);
	
	void setInterfaceScales( double s );

	//For 3d printing
	double computeRegistrationSpheres(double radius, bool doTxt=true);
	
	void setName( const std::string& name ){ name_=name; }
	const std::string& name() const { return name_;}
	bool existsI(unsigned int _b0, unsigned int _b1, int _f0, int _f1);
protected:
	void computeCentroidsAndVolumes();
	unsigned int computeValidChains();
	int addChain(unsigned int bi, unsigned int ai );
	unsigned int addChains(unsigned int bi);
	bool intersects(const V& o, const V& dir, double tMin =10e-6, double tMax = std::numeric_limits<double>::max(), V *bc=NULL);
	void recomputeTotalVertices();

private:
	Vs vs_;
	Fs fs_;
	Bs bs_;
	Is is_;
	As as_;
	Cs cs_;
	
	double density_;
	bool finalizedBlocks_;
	bool finalizedInterfaces_;
	
	std::vector<int> totalVertices_;
	int maxCluster_;
	
	std::string name_;
};

}

#endif
