#include "Geom.h"

const mgVector3 MG_VECTOR3_X = mgVector3(1,0,0);
const mgVector3 MG_VECTOR3_Y = mgVector3(0,1,0);
const mgVector3 MG_VECTOR3_Z = mgVector3(0,0,1);

bool DoesPointHitUpward( mgVector3 Point, mgVector3 LineStart, mgVector3 LineEnd, mgVector3 LineParallel)
{
	if( ((Point.m_v[X] >= LineStart.m_v[X]) && (Point.m_v[X] <= LineEnd.m_v[X]) ) ||
		((Point.m_v[X] >= LineEnd.m_v[X]) && (Point.m_v[X] <= LineStart.m_v[X]) ) )
	{
		if( LineParallel.m_v[X] != 0.0 ){
			
			float Grad = LineParallel.m_v[Y] / LineParallel.m_v[X];		
			
			float yIntercept = Grad * ( Point.m_v[X] - LineStart.m_v[X]) + LineStart.m_v[Y];
			
			if( yIntercept >= Point.m_v[Y] ) 
				return true;
			else 
				return false;

		}
		else{
			float MinY = LineStart.m_v[Y] < LineEnd.m_v[Y] ? LineStart.m_v[Y] : LineEnd.m_v[Y];

			if( Point.m_v[Y] <= MinY ) 
				return true;
			else
				return false;			
		}
	}
	else 
		return false;
}

bool DoesPointHitDownward( mgVector3 Point, mgVector3 LineStart, mgVector3 LineEnd, mgVector3 LineParallel)
{
	if( ((Point.m_v[X] >= LineStart.m_v[X]) && (Point.m_v[X] <= LineEnd.m_v[X]) ) ||
		((Point.m_v[X] >= LineEnd.m_v[X]) && (Point.m_v[X] <= LineStart.m_v[X]) ) )
	{
		if( LineParallel.m_v[X] != 0.0 ){
			
			float Grad = LineParallel.m_v[Y] / LineParallel.m_v[X];		
			
			float yIntercept = Grad * ( Point.m_v[X] - LineStart.m_v[X]) + LineStart.m_v[Y];
			
			if( yIntercept <= Point.m_v[Y] ) 
				return true;
			else 
				return false;

		}
		else{
			float MinY = LineStart.m_v[Y] < LineEnd.m_v[Y] ? LineStart.m_v[Y] : LineEnd.m_v[Y];

			if( Point.m_v[Y] >= MinY ) 
				return true;
			else
				return false;			
		}
	}
	else 
		return false;
}

void CPolygon::FilterFrontandBackPoints()
{
	bool bPointPassed;
	
	std::vector< CPoint > m_TmpFrontPointList;
	std::vector< CPoint > m_TmpBackPointList;

	for(size_t i=0;i<m_FrontFacingCollidablePoints.size();++i){

		bPointPassed = true;

		for(size_t l=0;l<m_LineList.size();++l){
			
			if( (m_FrontFacingCollidablePoints[i].m_InLineIdx == l) ||
				(m_FrontFacingCollidablePoints[i].m_OutLineIdx == l) )
			{
				continue;
			}

			if( DoesPointHitDownward( m_FrontFacingCollidablePoints[i].m_Point,
				m_PointList[ m_LineList[l].m_FromPointIdx ].m_Point,
				m_PointList[ m_LineList[l].m_ToPointIdx ].m_Point,
				m_LineList[l].m_Line) )
			{
				bPointPassed = false;
				break;
			}
		}

		if( bPointPassed )
			m_TmpFrontPointList.push_back( m_FrontFacingCollidablePoints[i]);
	}

	for(size_t i=0;i<m_BackFacingCollidablePoints.size();++i){

		bPointPassed = true;


		for(size_t l=0;l<m_LineList.size();++l){
			
			if( (m_BackFacingCollidablePoints[i].m_InLineIdx == l) ||
				(m_BackFacingCollidablePoints[i].m_OutLineIdx == l) )
			{
				continue;
			}

			if( DoesPointHitUpward( m_BackFacingCollidablePoints[i].m_Point,
				m_PointList[ m_LineList[l].m_FromPointIdx ].m_Point,
				m_PointList[ m_LineList[l].m_ToPointIdx ].m_Point,
				m_LineList[l].m_Line) )
			{
				bPointPassed = false;
				break;
			}
		}

		if( bPointPassed )
			m_TmpBackPointList.push_back( m_BackFacingCollidablePoints[i]);
	}

	m_FrontFacingCollidablePoints = m_TmpFrontPointList;
	m_BackFacingCollidablePoints = m_TmpBackPointList;
}

void CPolygon::ProcessFrontandBackLines()
{
	size_t nLines = m_LineList.size();
	
	for( size_t i = 0; i< nLines; ++i ){
		mgVector3 vResult;
		
		m_LineList[i].m_Line.CrossProduct( (mgVector3)MG_VECTOR3_Y, vResult );
		
		if( vResult.m_v[Z] >= 0 ) {
			/* Front Facing */
			m_LineList[i].m_FrontFacing = true;
			m_FrontFacingLines.push_back( m_LineList[i] ); 
		}
		else{
			/* Back Facing */
			m_LineList[i].m_FrontFacing = false;
			m_BackFacingLines.push_back( m_LineList[i] );
		}
	}
}


void CPolygon::ProcessFrontandBackPoints()
{
	size_t nPoints = m_PointList.size();
	
	for( size_t i=0;i<nPoints;++i){

		if (  (m_LineList[ m_PointList[ i ].m_InLineIdx ].m_FrontFacing && 
			  !m_LineList[ m_PointList[ i ].m_OutLineIdx].m_FrontFacing) 
			  ||
			  (!m_LineList[ m_PointList[ i ].m_InLineIdx ].m_FrontFacing && 
			  m_LineList[ m_PointList[ i ].m_OutLineIdx].m_FrontFacing) 
			  )
		{
			// If the point is in the junction of back-face lines and 
			// front-face lines then simply add it 
			
			m_PointList[i].m_BackFacingCollidable = true;
			m_PointList[i].m_FrontFacingCollidable = true;

			m_FrontFacingCollidablePoints.push_back( m_PointList[i] );
			m_BackFacingCollidablePoints.push_back( m_PointList[i] );
		}
		else if(m_LineList[ m_PointList[ i ].m_InLineIdx ].m_FrontFacing && 
			    m_LineList[ m_PointList[ i ].m_OutLineIdx].m_FrontFacing)
		{
			//if both in-line and out-line are front facing
			
			if( (m_LineList[ m_PointList[i].m_InLineIdx ].m_Line.DotProduct( (mgVector3)MG_VECTOR3_Y )) <= 
				(m_LineList[ m_PointList[i].m_OutLineIdx ].m_Line.DotProduct( (mgVector3)MG_VECTOR3_Y )) )
			{
				m_PointList[i].m_FrontFacingCollidable = true;
				m_PointList[i].m_BackFacingCollidable = false;
				m_FrontFacingCollidablePoints.push_back( m_PointList[i] );
			}
		}
		else {

			// if both in-line and out-line are back facing/
			if( (m_LineList[ m_PointList[i].m_InLineIdx ].m_Line.DotProduct( (mgVector3)MG_VECTOR3_Y )) >= 
				(m_LineList[ m_PointList[i].m_OutLineIdx ].m_Line.DotProduct( (mgVector3)MG_VECTOR3_Y )) )
			{
				m_PointList[i].m_FrontFacingCollidable = false;
				m_PointList[i].m_BackFacingCollidable = true;

				m_BackFacingCollidablePoints.push_back( m_PointList[i] );
			}
		}

	}
}



void CPolygon::BuildConnectivity()
{
	size_t nPoints = m_PointList.size();

	for(size_t i=0;i<nPoints;++i){

		m_LineList.push_back( CLine() );

		m_LineList[i].m_FromPointIdx = (int)i;
		m_LineList[i].m_ToPointIdx = (int)((i + 1) % nPoints);
		
		m_LineList[i].m_Line = m_PointList[m_LineList[i].m_ToPointIdx].m_Point - m_PointList[ m_LineList[i].m_FromPointIdx ].m_Point;

		m_PointList[ m_LineList[i].m_FromPointIdx ].m_OutLineIdx = (int)i;
		m_PointList[ m_LineList[i].m_ToPointIdx ].m_InLineIdx = (int)i;
	}
}

void CPolygon::Process()
{
	BuildConnectivity();
	ProcessFrontandBackLines();
	ProcessFrontandBackPoints();

	FilterFrontAndBackPoints();
	FilterFrontAndBackLines();
}

bool CPolygon::ReadPolygon( const char *pFileName )
{
	FILE *fp = fopen( pFileName, "r");
	
	if( !fp ){
		printf("Couldn't read polyon file %s\n", pFileName);
		return false;
	}

	int nPoints;
	fscanf(fp,"%d",&nPoints);
	
	for(int i=0;i<nPoints;++i){
		
		float x,y;
		
		fscanf( fp, "%f,%f",&x,&y);
		
		m_PointList.push_back( CPoint(x,y) );
	}

	//fscanf(fp, ";");
	
	float maxx,maxy, minx,miny;

	fscanf(fp,"\n;%f,%f\n%f,%f",&maxx,&maxy,&minx,&miny);

	m_BoundingBox.SetBoundingBox( maxx,maxy,minx,miny );

	fclose( fp );

	Process();

	return true;
}