/******************************************************************************
* AUTHOR        : Bharath A
* DATE          : 12-Jun-2008
* NAME			: convertFeatVecToTraceGroup
* DESCRIPTION	:
* ARGUMENTS		:
* RETURNS		:
* NOTES			:
* CHANGE HISTROY
* Author			Date				Description
******************************************************************************/
int NPenShapeFeatureExtractor::convertFeatVecToTraceGroup(
                                 const vector<LTKShapeFeaturePtr>& shapeFeature,
                                 LTKTraceGroup& outTraceGroup)
{
    LOG(LTKLogger::LTK_LOGLEVEL_DEBUG) << "Entering " <<
        "NPenShapeFeatureExtractor::convertFeatVecToTraceGroup()" << endl;
    
	vector<LTKChannel> channels;				//	channels of a trace

	LTKChannel xChannel("X", DT_FLOAT, true);	//	x-coordinate channel of the trace
	LTKChannel yChannel("Y", DT_FLOAT, true);	//	y-coordinate channel of the trace

	//initializing the channels of the trace
	channels.push_back(xChannel);
	channels.push_back(yChannel);

	//	composing the trace format object
	LTKTraceFormat traceFormat(channels);

	vector<float> point;				//	a point of a trace

	LTKTrace trace(traceFormat);

	for(int count=0;count<(int)shapeFeature.size();count++)
	{
		float Xpoint, Ypoint;
		bool penUp;

		NPenShapeFeature* ptr = (NPenShapeFeature*)(shapeFeature[count].operator ->());
		Xpoint = ptr->getX();
		Ypoint = ptr->getY();
		penUp = ptr->isPenUp();



		point.push_back(Xpoint);
		point.push_back(Ypoint);

		trace.addPoint(point);
		point.clear();


		if(penUp == true)	// end of a trace, clearing the trace now
		{
			outTraceGroup.addTrace(trace);
			trace.emptyTrace();
			LTKTrace tempTrace(traceFormat);
			trace = tempTrace;
		}
	}

    LOG(LTKLogger::LTK_LOGLEVEL_DEBUG) << "Exiting " <<
        "NPenShapeFeatureExtractor::convertFeatVecToTraceGroup()" << endl;
	return SUCCESS;

}
	int NPenShapeFeature::getDistance(const LTKShapeFeaturePtr& shapeFeaturePtr, float& outDistance) const
	{ 
		outDistance = 0.0;
		
		NPenShapeFeature *inFeature = (NPenShapeFeature*)(shapeFeaturePtr.operator ->());
		
		outDistance += (m_x - inFeature->getX())*(m_x - inFeature->getX());
		outDistance += (m_y - inFeature->getY())*(m_y - inFeature->getY());
		outDistance += (m_cosAlpha - inFeature->getCosAlpha())*(m_cosAlpha - inFeature->getCosAlpha());
		outDistance += (m_sinAlpha - inFeature->getSinAlpha())*(m_sinAlpha - inFeature->getSinAlpha());
		outDistance += (m_cosBeta - inFeature->getCosBeta())*(m_cosBeta - inFeature->getCosBeta());
		outDistance += (m_sinBeta - inFeature->getSinBeta())*(m_sinBeta - inFeature->getSinBeta());
		outDistance += (m_aspect - inFeature->getAspect())*(m_aspect - inFeature->getAspect());
		outDistance += (m_curliness - inFeature->getCurliness())*(m_curliness - inFeature->getCurliness());
		outDistance += (m_linearity - inFeature->getLinearity())*(m_linearity - inFeature->getLinearity());
		outDistance += (m_slope - inFeature->getSlope())*(m_slope - inFeature->getSlope());
		

		return SUCCESS;

	}
	LTKShapeFeaturePtr NPenShapeFeature::clone() const
	{
		NPenShapeFeature* npenSF = new NPenShapeFeature();


		npenSF->setX(this->getX());
		npenSF->setY(this->getY());
		npenSF->setCosAlpha(this->getCosAlpha());
		npenSF->setSinAlpha(this->getSinAlpha());
		npenSF->setCosBeta(this->getCosBeta());
		npenSF->setSinBeta(this->getSinBeta());
		npenSF->setAspect(this->getAspect());
		npenSF->setCurliness(this->getCurliness());
		npenSF->setLinearity(this->getLinearity());
		npenSF->setSlope(this->getSlope());
		npenSF->setPenUp(this->isPenUp());

		
		
		return (LTKShapeFeaturePtr)npenSF;
	}
/**********************************************************************************
* AUTHOR        : Bharath A
* DATE          : 12-Jun-2008
* NAME          : extractFeatures
* DESCRIPTION   : Extracts NPen features from a trace group
* ARGUMENTS     : The trace group from which features have to be extracted
* RETURNS       : vector of NPenShapeFeature objects
* NOTES         :
* CHANGE HISTROY
* Author            Date                Description of change
*************************************************************************************/
int NPenShapeFeatureExtractor::extractFeatures(const LTKTraceGroup& inTraceGroup, 
                                      vector<LTKShapeFeaturePtr>& outFeatureVec)
{

	LOG(LTKLogger::LTK_LOGLEVEL_DEBUG) << "Entering " <<
        "NPenShapeFeatureExtractor::extractFeatures()" << endl;

    NPenShapeFeature* featurePtr = NULL;  

	vector<vector<float> > floatFeatureValues;
    


	int errorCode;

	if(inTraceGroup.getNumTraces() == 0)
	{
		LOG(LTKLogger::LTK_LOGLEVEL_ERR)
		        <<"Error: FeatureExtractor::findAllFeatures"<<endl;

		LTKReturnError(EEMPTY_TRACE_GROUP);
	}


	vector<vector<float> > concatenatedCoord;
	int currPenUpPointIndex = -1;
	vector<int> penUpPointsIndices;
	

	int halfWindowSize = m_windowSize/2;

	if(halfWindowSize==0)
	{
		LOG(LTKLogger::LTK_LOGLEVEL_ERR)
		        <<"Error: FeatureExtractor::findAllFeatures"<<endl;

		LTKReturnError(EINVALID_NUM_OF_POINTS);
	}


	for(int t=0;t<inTraceGroup.getNumTraces();++t)
	{

		LTKTrace eachTrace;
		inTraceGroup.getTraceAt(t,eachTrace);

		if(eachTrace.isEmpty())
		{
			LOG(LTKLogger::LTK_LOGLEVEL_ERR)
						    <<"Error: FeatureExtractor::findAllFeatures"<<endl;

			LTKReturnError(EEMPTY_TRACE);

		}

		vector<float> xVec;
		vector<float> yVec;

		eachTrace.getChannelValues(X_CHANNEL_NAME,xVec);
		eachTrace.getChannelValues(Y_CHANNEL_NAME,yVec);

		if(t==0)
		{
			vector<float> firstPoint;
			firstPoint.push_back(xVec[0]);
			firstPoint.push_back(yVec[0]);

			concatenatedCoord.insert(concatenatedCoord.begin(),halfWindowSize,firstPoint);
		}

		for(int p=0;p<xVec.size();++p)
		{
			vector<float> point;
			point.push_back(xVec[p]);
			point.push_back(yVec[p]);

			concatenatedCoord.push_back(point);

		}

		currPenUpPointIndex += xVec.size();

		penUpPointsIndices.push_back(currPenUpPointIndex);

		if(t==(inTraceGroup.getNumTraces()-1))
		{
			vector<float> lastPoint;
			lastPoint.push_back(xVec[xVec.size()-1]);
			lastPoint.push_back(yVec[yVec.size()-1]);

			concatenatedCoord.insert(concatenatedCoord.end(),halfWindowSize,lastPoint);
		}

	}

	

	/*	0 - normalized x
		1 - normalized y
		2 - cos alpha
		3 - sin alpha
		4 - cos beta
		5 - sin beta
		6 - aspect
		7 - curliness
		8 - linearity
		9 - slope  
		10 - pen-up / pen-down stroke (0 for pen-down and 1 for pen-up)*/

	float deltaX=0;
	float deltaY=0;
	float hypotenuse=0;


	float cosalpha=0;
	float sinalpha=0;
	float cosbeta=0;
	float sinbeta=0;
	float ispenup=0;
	float aspect=0;
	float curliness=0;
	float linearity=0;
	float slope=0;


	float xMin,yMin,xMax,yMax; //for vicnity bounding box;
	float bbWidth,bbHeight;
	float maxOfWidthHeight;



	currPenUpPointIndex = 0;


	for(int f=halfWindowSize;f<(concatenatedCoord.size()-halfWindowSize);++f)
	{

		vector<float> eachPointFeature;

		eachPointFeature.push_back(concatenatedCoord[f][0]);  //x
		eachPointFeature.push_back(concatenatedCoord[f][1]);  //y

		deltaX = concatenatedCoord[f-1][0] - concatenatedCoord[f+1][0];
		deltaY = concatenatedCoord[f-1][1] - concatenatedCoord[f+1][1];

		hypotenuse = sqrt((deltaX*deltaX)+(deltaY*deltaY));

		if(hypotenuse < EPS)
		{
			cosalpha = 1;
			sinalpha = 0;
		}
		else
		{
			cosalpha = deltaX / hypotenuse;
			sinalpha = deltaY / hypotenuse;
		}

		eachPointFeature.push_back(cosalpha);
		eachPointFeature.push_back(sinalpha);

		eachPointFeature.push_back(cosbeta); //creating empty spaces for cosine and sine betas for future assignment
		eachPointFeature.push_back(sinbeta);

		vector<vector<float> > vicinity;

		float vicinityTrajLen = 0.0f;
		
		for(int v=f-halfWindowSize;v<=f+halfWindowSize;++v)
		{
			vicinity.push_back(concatenatedCoord[v]);

			if(v<(f+halfWindowSize))
			{
				vicinityTrajLen += (sqrt(((concatenatedCoord[v+1][1]-concatenatedCoord[v][1])*(concatenatedCoord[v+1][1]-concatenatedCoord[v][1]))+((concatenatedCoord[v+1][0]-concatenatedCoord[v][0])*(concatenatedCoord[v+1][0]-concatenatedCoord[v][0]))));
			}
		}

		findVicinityBoundingBox(vicinity,xMin,yMin,xMax,yMax);

		bbWidth = xMax - xMin;

		bbHeight = yMax - yMin;

		if(fabs(bbHeight+bbWidth)<EPS)
		{
			aspect = 0.0;
		}
		else
		{
			aspect = (bbHeight-bbWidth)/(bbHeight+bbWidth);
		}

		
		eachPointFeature.push_back(aspect);

		
		maxOfWidthHeight = ( bbWidth > bbHeight) ? bbWidth : bbHeight;

		if(fabs(maxOfWidthHeight) < EPS)
		{
			curliness = 0.0f;
		}
		else
		{
			curliness = (vicinityTrajLen / maxOfWidthHeight) - 2;
		}

		eachPointFeature.push_back(curliness);

		computeLinearityAndSlope(vicinity,linearity,slope);

		eachPointFeature.push_back(linearity);
		eachPointFeature.push_back(slope);

		if(penUpPointsIndices[currPenUpPointIndex] == (f-halfWindowSize))
		{
			ispenup = 1;
			++currPenUpPointIndex;
		}
		else
		{
			ispenup = 0;
		}
		eachPointFeature.push_back(ispenup); //currently assuming pen-up strokes are not resampled

		floatFeatureValues.push_back(eachPointFeature);

	}


		//duplicating first and last features
		vector<float> firstFeaturePoint = floatFeatureValues[0];

		floatFeatureValues.insert(floatFeatureValues.begin(),1,firstFeaturePoint);

		vector<float>  lastFeaturePoint = floatFeatureValues[floatFeatureValues.size()-1];

		floatFeatureValues.insert(floatFeatureValues.end(),1,lastFeaturePoint);


		for(int ff=1;ff<(floatFeatureValues.size()-1);++ff)
		{

			floatFeatureValues[ff][4] = (floatFeatureValues[ff-1][2]*floatFeatureValues[ff+1][2]) + (floatFeatureValues[ff-1][3]*floatFeatureValues[ff+1][3]);
			floatFeatureValues[ff][5] = (floatFeatureValues[ff-1][2]*floatFeatureValues[ff+1][3]) - (floatFeatureValues[ff-1][3]*floatFeatureValues[ff+1][2]);
			
		}

		//removing the extraneous feature points at the beginning and end
		floatFeatureValues.erase(floatFeatureValues.begin(),floatFeatureValues.begin()+1); 
		floatFeatureValues.pop_back();


		for(int a=0;a<floatFeatureValues.size();++a)
		{
				NPenShapeFeature* ptrFeature = new NPenShapeFeature();
				ptrFeature->setX(floatFeatureValues[a][0]);
				ptrFeature->setY(floatFeatureValues[a][1]);
				ptrFeature->setCosAlpha(floatFeatureValues[a][2]);
				ptrFeature->setSinAlpha(floatFeatureValues[a][3]);
				ptrFeature->setCosBeta(floatFeatureValues[a][4]);
				ptrFeature->setSinBeta(floatFeatureValues[a][5]);
				ptrFeature->setAspect(floatFeatureValues[a][6]);
				ptrFeature->setCurliness(floatFeatureValues[a][7]);
				ptrFeature->setLinearity(floatFeatureValues[a][8]);
				ptrFeature->setSlope(floatFeatureValues[a][9]);
				
				if(fabs(floatFeatureValues[a][10]-1.0f) < EPS)
				{
					ptrFeature->setPenUp(true);
				}
				else
				{
					ptrFeature->setPenUp(false);
				}

				outFeatureVec.push_back(LTKShapeFeaturePtr(ptrFeature));

				ptrFeature = NULL;
			
		}
		
	



    LOG(LTKLogger::LTK_LOGLEVEL_DEBUG) << "Exiting " <<
        "NPenShapeFeatureExtractor::extractFeatures()" << endl;
    
	return SUCCESS;
}