/*
 * \file Nodes.c
 * \brief: implementation of the Nodes class, derived from DataSet class
 */

/*Headers: {{{1*/
#ifdef HAVE_CONFIG_H
	#include <config.h>
#else
#error "Cannot compile with HAVE_CONFIG_H symbol! run configure first!"
#endif

#include <vector>
#include <functional>
#include <algorithm>
#include <iostream>

#include "./DataSet.h"
#include "../shared/shared.h"
#include "../include/include.h"
#include "../EnumDefinitions/EnumDefinitions.h"

using namespace std;
/*}}}*/

/*Object constructors and destructor*/
/*FUNCTION Nodes::Nodes(){{{1*/
Nodes::Nodes(){
	enum_type=NodesEnum;
	return;
}
/*}}}*/
/*FUNCTION Nodes::~Nodes(){{{1*/
Nodes::~Nodes(){
	return;
}
/*}}}*/

/*Numerics*/
/*FUNCTION Nodes::Configure{{{1*/
void Nodes::Configure(Elements* elements,Loads* loads, Nodes* nodes, Vertices* vertices, Materials* materials,Parameters* parameters){

	vector<Object*>::iterator object;
	Node* node=NULL;

	for ( object=objects.begin() ; object < objects.end(); object++ ){

		node=(Node*)(*object);
		node->Configure(nodes,vertices);

	}

}
/*}}}*/
/*FUNCTION Nodes::DistributeDofs{{{1*/
void  Nodes::DistributeDofs(int analysis_type,int setenum){

	extern int num_procs;
	extern int my_rank;

	int  i;
	int  dofcount=0;
	int  maxdofspernode=0;
	int* alldofcount=NULL;
	int* truedofs=NULL;
	int* alltruedofs=NULL;
	int  numnodes=0;

	/*some check: */
	_assert_(setenum==GsetEnum || setenum==FsetEnum || setenum==SsetEnum);

	/*Go through objects, and distribute dofs locally, from 0 to numberofdofsperobject*/
	for (i=0;i<this->Size();i++){
		Node* node=(Node*)this->GetObjectByOffset(i);

		/*Check that this node corresponds to our analysis currently being carried out: */
		if (node->InAnalysis(analysis_type)){
			node->DistributeDofs(&dofcount,setenum);
		}
	}

	/* Now every object has distributed dofs, but locally, and with a dof count starting from 
	 * 0. This means the dofs between all the cpus are not unique. We now offset the dofs of eache
	 * cpus by the total last dofs of the previus cpu, starting from 0.
	 * First: bet number of dofs for each cpu*/
	alldofcount=(int*)xmalloc(num_procs*sizeof(int));
	MPI_Gather(&dofcount,1,MPI_INT,alldofcount,1,MPI_INT,0,MPI_COMM_WORLD);
	MPI_Bcast(alldofcount,num_procs,MPI_INT,0,MPI_COMM_WORLD);

	/* Every cpu should start its own dof count at the end of the dofcount from cpu-1*/
	dofcount=0;
	if(my_rank!=0){
		for(i=0;i<my_rank;i++){
			dofcount+=alldofcount[i];
		}
	}
	for (i=0;i<this->Size();i++){
		/*Check that this node corresponds to our analysis currently being carried out: */
		Node* node=(Node*)this->GetObjectByOffset(i);
		if (node->InAnalysis(analysis_type)){
			node->OffsetDofs(dofcount,setenum);
		}
	}

	/* Finally, remember that cpus may have skipped some objects, because they were clones. For every 
	 * object that is not a clone, tell them to show their dofs, so that later on, they can get picked 
	 * up by their clones: */
	maxdofspernode=this->MaxNumDofs(analysis_type,setenum);
	numnodes=this->NumberOfNodes(analysis_type);
	if(numnodes*maxdofspernode){
		truedofs=   (int*)xcalloc(numnodes*maxdofspernode,sizeof(int)); //initialize to 0, so that we can pick up the max
		alltruedofs=(int*)xcalloc(numnodes*maxdofspernode,sizeof(int));
	}

	for (i=0;i<this->Size();i++){
		Node* node=(Node*)this->GetObjectByOffset(i);
		if (node->InAnalysis(analysis_type)){
			node->ShowTrueDofs(truedofs,maxdofspernode,setenum);//give maxdofspernode, column size, so that nodes can index into truedofs
		}
	}
	MPI_Allreduce ( (void*)truedofs,(void*)alltruedofs,numnodes*maxdofspernode,MPI_INT,MPI_MAX,MPI_COMM_WORLD);

	/* Now every cpu knows the true dofs of everyone else that is not a clone*/
	for (i=0;i<this->Size();i++){
		Node* node=(Node*)this->GetObjectByOffset(i);
		if (node->InAnalysis(analysis_type)){
			node->UpdateCloneDofs(alltruedofs,maxdofspernode,setenum);
		}
	}

	/* Free ressources: */
	xfree((void**)&alldofcount);
	xfree((void**)&truedofs);
	xfree((void**)&alltruedofs);
}
/*}}}*/
/*FUNCTION Nodes::FlagClones{{{1*/
void  Nodes::FlagClones(int analysis_type){

	int i;
	extern int num_procs;

	int* ranks=NULL;
	int* minranks=NULL;
	int  numnodes;


	/*Figure out number of nodes for this analysis: */
	numnodes=this->NumberOfNodes(analysis_type);

	/*Allocate ranks: */
	ranks=(int*)xmalloc(numnodes*sizeof(int));
	minranks=(int*)xmalloc(numnodes*sizeof(int));

	for(i=0;i<numnodes;i++)ranks[i]=num_procs; //no cpu can have rank num_procs. This is the maximum limit.

	/*Now go through all our objects and ask them to report to who they belong (which rank): */
	Ranks(ranks,analysis_type);

	/*We need to take the minimum rank for each vertex, and every cpu needs to get that result. That way, 
	 * when we start building the dof list for all vertexs, a cpu can check whether its vertex already has been 
	 * dealt with by another cpu. We take the minimum because we are going to manage dof assignment in increasing 
	 * order of cpu rank. This is also why we initialized this array to num_procs.*/
	MPI_Allreduce ( (void*)ranks,(void*)minranks,numnodes,MPI_INT,MPI_MIN,MPI_COMM_WORLD);

	/*Now go through all objects, and use minranks to flag which objects are cloned: */
	for(i=0;i<this->Size();i++){

		Node* node=(Node*)this->GetObjectByOffset(i);

		/*Check that this node corresponds to our analysis currently being carried out: */
		if (node->InAnalysis(analysis_type)){

			/*For this object, decide whether it is a clone: */
			node->SetClone(minranks);
		}
	}

	/*Free ressources: */
	xfree((void**)&ranks); 
	xfree((void**)&minranks);

}
/*}}}*/
/*FUNCTION Nodes::MaxNumDofs{{{1*/
int   Nodes::MaxNumDofs(int analysis_type,int setenum){

	int i;
	int   max=0;
	int   allmax;
	int   numdofs=0;

	/*Now go through all nodes, and get how many dofs they own, unless they are clone nodes: */
	for(i=0;i<this->Size();i++){

		Node* node=(Node*)this->GetObjectByOffset(i);

		/*Check that this node corresponds to our analysis currently being carried out: */
		if (node->InAnalysis(analysis_type)){

			numdofs=node->GetNumberOfDofs(NoneApproximationEnum,setenum);
			if (numdofs>max)max=numdofs;
		}
	}

#ifdef _PARALLEL_
	/*Grab max of all cpus: */
	MPI_Allreduce ( (void*)&max,(void*)&allmax,1,MPI_INT,MPI_MAX,MPI_COMM_WORLD);
	max=allmax;
#endif

	return max;
}
/*}}}*/
/*FUNCTION Nodes::NumberOfDofs{{{1*/
int   Nodes::NumberOfDofs(int analysis_type,int setenum){

	int i;
	
	int   numdofs=0;
	int   allnumdofs;

	/*Now go through all nodes, and get how many dofs they own, unless they are clone nodes: */
	for(i=0;i<this->Size();i++){

		Node* node=(Node*)this->GetObjectByOffset(i);

		/*Check that this node corresponds to our analysis currently being carried out: */
		if (node->InAnalysis(analysis_type)){

			/*Ok, this object is a node, ask it to plug values into partition: */
			if (!node->IsClone()){

				numdofs+=node->GetNumberOfDofs(NoneApproximationEnum,setenum);

			}
		}
	}

	/*Gather from all cpus: */
	MPI_Allreduce ( (void*)&numdofs,(void*)&allnumdofs,1,MPI_INT,MPI_SUM,MPI_COMM_WORLD);
	return allnumdofs;
}
/*}}}*/
/*FUNCTION Nodes::NumberOfNodes(){{{1*/
int Nodes::NumberOfNodes(void){

	/*Careful! only use once all clones have been setup for all nodes!: */

	int i;
	
	int   numnodes=0;
	int   allnumnodes=0;

	/*Now go through all nodes, and get how many dofs they own, unless they are clone nodes: */
	for(i=0;i<this->Size();i++){
		Node* node=(Node*)this->GetObjectByOffset(i);

		/*Ok, this object is a node, ask it to plug values into partition: */
		if (!node->IsClone()) numnodes++;
	}

	/*Gather from all cpus: */
	MPI_Allreduce ( (void*)&numnodes,(void*)&allnumnodes,1,MPI_INT,MPI_SUM,MPI_COMM_WORLD);

	return allnumnodes;
}
/*}}}*/
/*FUNCTION Nodes::NumberOfNodes(analysis){{{1*/
int Nodes::NumberOfNodes(int analysis_type){

	int i;

	int max_sid=-1;
	int sid;
	int node_max_sid;

	for(i=0;i<this->Size();i++){

		Node* node=(Node*)this->GetObjectByOffset(i);

		/*Check that this node corresponds to our analysis currently being carried out: */
		if (node->InAnalysis(analysis_type)){

			sid=node->Sid();
			if (sid>max_sid)max_sid=sid;
		}
	}

	#ifdef _PARALLEL_
		MPI_Reduce (&max_sid,&node_max_sid,1,MPI_INT,MPI_MAX,0,MPI_COMM_WORLD );
		MPI_Bcast(&node_max_sid,1,MPI_INT,0,MPI_COMM_WORLD);
		max_sid=node_max_sid;
	#endif 

	if(max_sid==1){
		return 0;
	}
	else{
		/*sid starts at 0*/
		max_sid++;
	
		/*return*/
		return max_sid;
	}
}
/*}}}*/
/*FUNCTION Nodes::Ranks{{{1*/
void   Nodes::Ranks(int* ranks,int analysis_type){

	/*Go through nodes, and for each object, report it cpu: */

	int i;
	int rank;
	int sid;
	
	for(i=0;i<this->Size();i++){

		Node* node=(Node*)this->GetObjectByOffset(i);

		/*Check that this node corresponds to our analysis currently being carried out: */
		if (node->InAnalysis(analysis_type)){

			rank=node->MyRank();
			sid=node->Sid();

			/*Plug rank into ranks, according to id: */
			ranks[sid]=rank; 
		}
	}
	return;
}
/*}}}*/
/*FUNCTION Nodes::SetCurrentConfiguration{{{1*/
void Nodes::SetCurrentConfiguration(Elements* elements,Loads* loads, Nodes* nodes, Vertices* vertices, Materials* materials,Parameters* parameters){

	vector<Object*>::iterator object;
	Node* node=NULL;

	for ( object=objects.begin() ; object < objects.end(); object++ ){

		node=(Node*)(*object);
		node->SetCurrentConfiguration(nodes,vertices);

	}

}
/*}}}*/
