#include "pointStructureKernels.hpp"
#include "setFieldList.hpp"
#include "error.hpp"
#include "iOstream.hpp"
//#include "Time.hpp"
#include "mortonIndexing.hpp"

FUNCTION_H
bool pFlow::pointStructure::evaluatePointStructure()
{
	if(pointFlag_.size() != pointPosition_.size())
	{
		fatalErrorInFunction<<
		"number of elements in pointFlag and pointPosition is not equal \n";
		return false;
	}

	setNumMaxPoints();

	int32 minActive, maxActive;
	numActivePoints_ = pFlow::pointStructureKernels::scanPointFlag(
		0,
		numPoints_,
		static_cast<int8>(pointStructure::ACTIVE),
		pointFlag_.deviceVectorAll(),
		minActive,
		maxActive
		);
	
	activeRange_ = {minActive, maxActive};	
	
	return true;
}

FUNCTION_H
void pFlow::pointStructure::setNumMaxPoints()
{
	maxPoints_ = pointFlag_.capacity();
	numPoints_ = pointFlag_.size();
}

FUNCTION_H
pFlow::realx3Field_D& pFlow::pointStructure::pointPosition()
{
	return pointPosition_;
}

FUNCTION_H
pFlow::int8Field_HD& pFlow::pointStructure::pointFlag()
{
	return pointFlag_;
}
		
pFlow::uniquePtr<pFlow::int32IndexContainer> 
	pFlow::pointStructure::getNewPointsIndices(int32 numNewPoints)const
{


	if( capacity() - activeRange_.second >=  numNewPoints )
	{
		// fill the sequence starting from activeRange_.second-1
		return makeUnique<int32IndexContainer>(
			activeRange_.second,
			activeRange_.second+numNewPoints);

	}

	// second, check if there is space at the beginning
	if( activeRange_.first >= numNewPoints)
	{
		return makeUnique<int32IndexContainer>(
			0,
			numNewPoints);	
	}

	// otherwise scan the points from first to the end to find empty spaces 
	int32Vector newPoints(
		numNewPoints,
		RESERVE());

	newPoints.clear();
	int32 numAdded = 0;
	ForAll(i, pointFlag_)
	{
		if(!isActive(i))
		{
			newPoints.push_back(static_cast<int32>(i));
			numAdded++;
		}

		if(numAdded == numNewPoints)
		{
			return makeUnique<int32IndexContainer>(
				newPoints.data(),
				numNewPoints);
		}
	}

	// check if there is space at the end for the remaining of points
	if( numAdded <numNewPoints && capacity() - size() >= numNewPoints - numAdded )
	{
		int32 ind = size();
		for(int32 i=numAdded; i<numNewPoints; i++)
		{
			newPoints.push_back(ind);
			ind++;
		}
		
		return makeUnique<int32IndexContainer>(
			newPoints.data(),
			numNewPoints);
	}
	else
	{
		fatalErrorInFunction<<"not enough capacity for inserting particles into the point structure\n";
		return nullptr;
	}

	return nullptr;
}

pFlow::pointStructure::pointStructure()
:
	eventSubscriber(),	
	pointFlag_("pointFlag", "pointFlag", maxPoints_, 0 , RESERVE()),
	pointPosition_("pointPosition", "pointPosition", maxPoints_, 0, RESERVE()),
	activeRange_(0,0)
{}

pFlow::pointStructure::pointStructure
(
	const int8Vector& flgVec,
	const realx3Vector& posVec
)
:
	eventSubscriber(),	
	maxPoints_(posVec.capacity()),
	pointFlag_("pointFlag", "pointFlag", maxPoints_, 0 , RESERVE()),
	pointPosition_("pointPosition", "pointPosition", maxPoints_, 0, RESERVE()),
	activeRange_(0,0)
{
	
	pointFlag_.assign(flgVec);

	pointPosition_.assign(posVec);

	if( !evaluatePointStructure() )
	{
		fatalExit;
	}
}

pFlow::pointStructure::pointStructure
(
	const realx3Vector& posVec
)
:
	eventSubscriber(),	
	maxPoints_(posVec.capacity()),
	pointFlag_("pointFlag", "pointFlag", maxPoints_, 0 , RESERVE()),
	pointPosition_("pointPosition", "pointPosition", maxPoints_, 0, RESERVE()),
	activeRange_(0,0)
{

	pointPosition_.assign(posVec);

	pointFlag_.resize(pointPosition_.size(), static_cast<int8>(pointStructure::ACTIVE) );
	//pointFlag_.syncViews();

	if( !evaluatePointStructure() )
	{
		fatalExit;
	}
}

FUNCTION_H
const pFlow::realx3Field_D& pFlow::pointStructure::pointPosition()const
{
	return pointPosition_;
}

FUNCTION_H
const pFlow::int8Field_HD& pFlow::pointStructure::pointFlag()const
{
	return pointFlag_;
}

// - size of data structure
FUNCTION_H
pFlow::label pFlow::pointStructure::size()const
{
	return numPoints_;
}

FUNCTION_H
pFlow::label pFlow::pointStructure::capacity()const
{
	return maxPoints_;
}

FUNCTION_H
pFlow::label pFlow::pointStructure::numActive() const
{
	return numActivePoints_;
}

FUNCTION_H
bool pFlow::pointStructure::allActive()const
{
	return numActivePoints_ == numPoints_;
}

FUNCTION_H
bool pFlow::pointStructure::mortonSortPoints(const box& domain, real dx)
{
	if( !getSortedIndex(
		domain, 
		dx, 
		activeRange_,
		pointPosition_.deviceVectorAll(),
		pointFlag_.deviceVectorAll(),
		mortonSortedIndex_) )
	{
		fatalErrorInFunction<<"failed to perform morton sorting!"<<endl;
		return false;
	}

	pointPosition_.sortItems(mortonSortedIndex_);
	pointFlag_.sortItems(mortonSortedIndex_);

	auto oldSize 			= size();
	auto oldCapacity 	= capacity();
	auto oldRange 		= activeRange();

	// update size, range, capacity
	setNumMaxPoints();
	activeRange_ = {0, static_cast<int>(mortonSortedIndex_.size())};
	numActivePoints_ = mortonSortedIndex_.size();

	eventMessage msg(eventMessage::REARRANGE);

	if(oldSize != size() )
	{
		msg.add(eventMessage::SIZE_CHANGED);
	}

	if(oldCapacity != capacity())
	{
		msg.add(eventMessage::CAP_CHANGED);
	}

	if( oldRange != activeRange_)
	{
		msg.add(eventMessage::RANGE_CHANGED);
	}

	// notify all the registered objects except the exclusionList 
	if( !this->notify(msg) ) return false;

	return true;
}

FUNCTION_H
size_t pFlow::pointStructure::markDeleteOutOfBox(const box& domain)
{
	if(numPoints_==0) return 0;

	int32 minRange, maxRange;
	int32 numMarked = 
	pFlow::pointStructureKernels::markDeleteOutOfBox(
		domain,
		activeRange_.first,
		activeRange_.second,
		pointStructure::DELETED,
		pointPosition_.deviceVectorAll(),
		pointFlag_.deviceVectorAll(),
		activePointsMaskD(),
		minRange, maxRange );

	if(numMarked)
	{
		pointFlag_.modifyOnDevice();
		pointFlag_.syncViews();	
	}

	if( numMarked<=numActivePoints_)
	{
		numActivePoints_ -= numMarked;
	}
	else
	{
		fatalErrorInFunction<<
		"number of points marked as delete ("<<numMarked<<
		") is greater than the activePoints ("<<numActivePoints_<<
		").\n";
		fatalExit;
	}

	range newRange = {minRange, maxRange};
	
	if( activeRange_ != newRange )
	{
		activeRange_ = newRange;
		eventMessage msg(eventMessage::RANGE_CHANGED);
		
		// notify all the registered objects about active range change
		if( !this->notify(msg) ) 
		{
			fatalExit<<
			"something went wrong in notifying registered object about range change. \n";
			fatalExit;
		}
	}	

	return numMarked;
	
}

FUNCTION_H
bool pFlow::pointStructure::updateForDelete()
{
	notImplementedFunction;

	return true;
}

/*FUNCTION_H
pFlow::uniquePtr<pFlow::int32IndexContainer> pFlow::pointStructure::insertPoints
(
	const realx3Vector& pos,
	const setFieldList& setField,
	repository& owner,
	const List<eventObserver*>& exclusionList
)
{

	auto numNew = pos.size();
	if( numNew==0)
	{
		return makeUnique<int32IndexContainer>();
	}
		
	auto newPointsPtr = getNewPointsIndices( numNew );

	if(!newPointsPtr)return nullptr;

	auto oldSize 			= size();
	auto oldCapacity 	= capacity();
	auto oldRange 		= activeRange();

	tobeInsertedIndex_ = newPointsPtr();

	// set the position of new points
	
	if(!pointPosition_.insertSetElement(
		newPointsPtr(),
		pos)
		)return nullptr;

	
	if(!pointFlag_.insertSetElement(
		newPointsPtr(),
		static_cast<int8>(PointFlag::ACTIVE))
		)return nullptr;

	
	setNumMaxPoints();
	auto minInd = newPointsPtr().min();
	auto maxInd = newPointsPtr().max();


	List<eventObserver*> localExlusion(exclusionList);

	for(auto sfEntry:setField)
	{

		if(void* fieldPtr = 
			sfEntry.setPointFieldSelectedAll( 
				owner, 
				newPointsPtr(), 
				false );
			fieldPtr)

			localExlusion.push_back( 
				static_cast<eventObserver*>(fieldPtr)
				);
		else
			return nullptr;
	}

	// changes the active rage based on the new inserted points 
	activeRange_ = { static_cast<int>(min(activeRange_.first, minInd )),
					 static_cast<int>(max(activeRange_.second, maxInd+1))};

	numActivePoints_ += numNew;

	eventMessage msg(eventMessage::INSERT);

	if( oldRange != activeRange_ )
		msg.add(eventMessage::RANGE_CHANGED);

	if( oldSize != size() )
		msg.add(eventMessage::SIZE_CHANGED);

	if( oldCapacity != capacity() )
		msg.add(eventMessage::CAP_CHANGED);
	
	// notify all the registered objects except the exclusionList 
	if( !this->notify(msg, localExlusion) ) return nullptr;

	return newPointsPtr;
}



/*FUNCTION_H
bool pFlow::pointStructure::readPointStructure
(
	iIstream& is
)
{
	auto psCapacity = is.lookupDataOrSet("pointStructureCapacity", maxSizeDefault_);
	pointPosition_.reallocate(psCapacity);
	pointFlag_.reallocate(psCapacity);

	if( !pointPosition_.read(is))
	{
		ioErrorInFile(is.name(), is.lineNumber())<<
		"Error in reading pointPosition in pointStructure \n";
		return false;	
	}

	if(! pointFlag_.read(is, true))
	{
		ioErrorInFile(is.name(), is.lineNumber())<<
		"Error in reading pointFlag in pointStructure \n";
		return false;
	}

	
	return evaluatePointStructure();
}*/

/*FUNCTION_H
bool pFlow::pointStructure::writePointStructure
(
	iOstream& os
)const
{
	os.writeWordEntry("pointStructureCapacity", maxPoints_);

	if(!pointPosition_.write(os))
	{
		ioErrorInFile(os.name(), os.lineNumber())<<
		"error in writing pointPosition to file \n";
		return false;
	}

	if(!pointFlag_.write(os))
	{
		ioErrorInFile(os.name(), os.lineNumber())<<
		"error in writing pointFlag to file \n";
		return false;
	}
	return true;
}*/


/*pFlow::uniquePtr<pFlow::int32Vector> 
pFlow::pointStructure::newPointsIndices(
	int32 numNewPoints
)const
{
	
	auto newPointsPtr = makeUnique<int32Vector>(
		"pointStructure::newPointsPtr",
		numNewPoints);

	auto& newPoints = newPointsPtr();
	

	// first, check if there is space at the end
	if( capacity() - activeRange_.second >=  numNewPoints )
	{
		// fill the sequence starting from activeRange_.second-1
		fillSequence(newPoints, activeRange_.second-1);
		return newPointsPtr;
	}

	// second, check if there is space at the beggining
	if( activeRange_.first >= numNewPoints)
	{
		// fill the sequence starting from 0
		fillSequence(newPoints, 0);
		return newPointsPtr;
	}

	// otherwise scan the points from first to the end to find empty spaces 
	newPoints.clear();
	int32 numAdded = 0;
	ForAll(i, pointFlag_)
	{
		if(!isActive(i))
		{
			newPoints.push_back(static_cast<int32>(i));
			numAdded++;
		}

		if(numAdded == numNewPoints)
		{
			
			return newPointsPtr;
		}
	}

	// check if there is space at the end for the remaining of points
	if( capacity() - size() >= numNewPoints - numAdded )
	{
		int32 ind = size();
		for(int32 i=numAdded; i<numNewPoints; i++)
		{
			newPoints.push_back(ind);
			ind++;
		}
		
		return newPointsPtr;
	}
	else
	{
		fatalErrorInFunction<<"not enough capacity for inserting particles into the point structure\n";
		return nullptr;
	}

	return nullptr;
}*/

pFlow::pointStructure::pointStructure(const dictionary &domainDict)
{

}
