#include <Inventor/SoPath.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoNormal.h>
#include <Inventor/SoDetail.h>

//
// Several methods of SoDetail subclasses call an internal routine
// that creates a SoSearchAction but fails to delete it.  This causes
// a large memory leak (the searchAction, all of the paths it finds,
// and all of the nodes on those paths will never be deleted) and will
// cause the application to slow down because of the extra paths.
//
// To fix this, compile this file into a .o and then link
// the .o before -lInventor_s.  The linker will give warnings like:
// Warning: getCoordinate3Node__19SoVertexShapeDetailCFv: multiply defined
//        previous (used) definition from 'detail.o';
//        new (ignored) definition from '/usr/lib/libInventor_s.a'
// This is normal and expected.
//


////////////////////////////////////////////////////////////////////////
//
// Description:
//    Return the coordinate node that affects this shape.
//
// Use: protected

SoCoordinate3 *
SoVertexShapeDetail::getCoordinate3Node() const
//
////////////////////////////////////////////////////////////////////////
{
    return
      (SoCoordinate3 *) getLastPropertyNode(SoCoordinate3::getClassTypeId());
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Return the normal node that affects this shape, NULL if
// there isn't one.
//
// Use: protected

SoNormal *
SoVertexShapeDetail::getNormalNode() const
//
////////////////////////////////////////////////////////////////////////
{
    return
      (SoNormal *) getLastPropertyNode(SoNormal::getClassTypeId());
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  Returns the node of the given type that affects the shape that
//  this detail's path leads to. The passed property node should
//  be one that does not accumulate such as, oh, gosh,
//  maybe a Normal or a Coordinate3 node, because we look
//  for the last (and hence current) one.
//
//
// Use: private

SoNode *
SoVertexShapeDetail::getLastPropertyNode(SoType typeId) const
//
////////////////////////////////////////////////////////////////////////
{
#ifdef DEBUG    
    if (path == NULL) {
	 fprintf( stderr, "SoVertexShapeDetail::getLastPropertyNode - " );
	 fprintf( stderr, "path to shape is NULL\n" );
	 return NULL;
    }
#endif

    SoNode *result;
    
    if (path->getTail()->isOfType(typeId)) {
	// path already leads to a node of given type, so just return it
	result = path->getTail();
    }
    else {
	// Apply a search action to the path, and look
	// for the _last_ node of the requested type
	
	SoSearchAction sa;
	sa.setType(typeId);
	sa.setFindAll(TRUE);
	sa.apply(path);

	SoPathList paths = sa.getPaths();
    
	if (paths.length() == 0) {
	    // nothing found
	    result = NULL;
	}
	else {
	    // return the last one found
	    result = paths[paths.length() - 1]->getTail();
	}
    }

    return result;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Return the point detail information.
//
// Use: public

void
SoPointDetail::getPoint(
    long &_coordIndex, SoCoordinate3 *&coordNode,
    long &_normalIndex, SoNormal *&normalNode) const

//
////////////////////////////////////////////////////////////////////////
{
    _coordIndex = coordIndex;
    _normalIndex = normalIndex;

    coordNode = getCoordinate3Node();
    if (normalIndex == SO_DETAIL_NO_NORMAL_NODE)
	normalNode = NULL;
    else
	normalNode = getNormalNode();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Return the line detail information.
//
// Use: public

void
SoLineDetail::getLine(
    long &_coordIndex0, long &_coordIndex1, 
    SoCoordinate3 *&coordNode,
    long &_normalIndex0, long &_normalIndex1, 
    SoNormal *&normalNode) const
//
////////////////////////////////////////////////////////////////////////
{
    _coordIndex0 = coordIndex0;
    _coordIndex1 = coordIndex1;

    _normalIndex0 = normalIndex0;
    _normalIndex1 = normalIndex1;

    coordNode = getCoordinate3Node();

    if (normalIndex0 == SO_DETAIL_NO_NORMAL_NODE)
	normalNode = NULL;
    else	    
	normalNode = getNormalNode();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    return the detail about the closest point
//
// Use: virtual public

const SoPointDetail &
SoLineDetail::getClosestPoint()
{
    long closeCoordIndex;
    long closeNormalIndex;
    
    SoCoordinate3 *coordNode = getCoordinate3Node();

    float dist0 = (point - coordNode->point[(int) coordIndex0]).length();
    float dist1 = (point - coordNode->point[(int) coordIndex1]).length();

    if (dist0 < dist1) {
	closeCoordIndex = coordIndex0;
	closeNormalIndex = normalIndex0;
    }
    else {
	closeCoordIndex = coordIndex1;
	closeNormalIndex = normalIndex1;
    }

    pointDetail.setPointCoordinate(closeCoordIndex);
    pointDetail.setPoint(coordNode->point[(int) closeCoordIndex]);
	
    if (closeNormalIndex == SO_DETAIL_NO_NORMAL_NODE) {
	pointDetail.setNormal(normal);
    }
    else {
	pointDetail.setPointNormal(closeNormalIndex);
	
	SoNormal *normalNode = getNormalNode();
	if (normalNode)
	    pointDetail.setNormal(normalNode->vector[(int) closeNormalIndex]);
	else
	    pointDetail.setNormal(normal);
    }
	
    pointDetail.setPath(path);
    pointDetail.setMatrix(objToWorld);
    pointDetail.setUnits(unitsType);

    return pointDetail;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Return the line detail information.
//
// Use: public

void
SoFaceDetail::getFace(
    long &_numCoords, long *&_coordIndexList,
    SoCoordinate3 *&coordNode,
    long &_numNormals, long *&_normalIndexList,
    SoNormal *&normalNode) const
//
////////////////////////////////////////////////////////////////////////
{
    _numCoords = numCoords;
    _coordIndexList = coordIndexList;
    _numNormals = numNormals;
    _normalIndexList = normalIndexList;

    coordNode = getCoordinate3Node();

    if (numNormals == SO_DETAIL_NO_NORMAL_NODE)
	normalNode = NULL;
    else	    
	normalNode = getNormalNode();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    return the detail about the closest point
//
// Use: virtual public

const SoPointDetail &
SoFaceDetail::getClosestPoint()
{
    SoCoordinate3 *coordNode = getCoordinate3Node();

    float minDist = (point -coordNode->point[(int)coordIndexList[0]]).length();
    long closeCoordIndex = 0;
    long closeNormalIndex = 0;

    for (int i = 1; i < numCoords; i++) {
	float dist = 
	    (point - coordNode->point[(int)coordIndexList[i]]).length();

	if (dist < minDist) {
	    minDist = dist;
	    closeCoordIndex = i;

	    if (numNormals != 1)
		closeNormalIndex = i;
	}
    }
    
    pointDetail.setPointCoordinate(coordIndexList[closeCoordIndex]);
    pointDetail.setPoint(coordNode->point[
			 (int)coordIndexList[closeCoordIndex]]);
	
    if (numNormals == SO_DETAIL_NO_NORMAL_NODE) {
	pointDetail.setNormal(normal);
    }
    else {
	pointDetail.setPointNormal(normalIndexList[closeNormalIndex]);
	
	SoNormal *normalNode = getNormalNode();
	if (normalNode)
	    pointDetail.setNormal(
		normalNode->vector[(int)normalIndexList[closeNormalIndex]]);
	else
	    pointDetail.setNormal(normal);
	    
    }
	
    pointDetail.setPath(path);
    pointDetail.setMatrix(objToWorld);
    pointDetail.setUnits(unitsType);

    return pointDetail;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    return the detail about the closest line
//
// Use: virtual public

const SoLineDetail &
SoFaceDetail::getClosestLine()
{
    
    SoCoordinate3 *coordNode = getCoordinate3Node();

    // Start with the edge that goes from the last to the first point
    SbLine edge(
	coordNode->point[(int)coordIndexList[numCoords - 1]],
	coordNode->point[(int)coordIndexList[0]]);

    SbVec3f closePoint = edge.getClosestPoint(point);
    float minDist = (point - closePoint).length();
    long closeCoordIndex = numCoords - 1;

    for (int i = 0; i < numCoords - 1; i++) {
	edge.setValue(
	    coordNode->point[(int)coordIndexList[i]],
	    coordNode->point[(int)coordIndexList[i+1]]);

	SbVec3f edgePoint = edge.getClosestPoint(point);
	float dist = (point - edgePoint).length();

	if (dist < minDist) {
	    minDist = dist;
	    closePoint = edgePoint;
	    closeCoordIndex = i;
	}
    }

    // Fill the the coordinate indices for the detail
    long c0, c1, n0, n1; // indices of the coord and normal
    
    if (closeCoordIndex == numCoords - 1) {
	c0 = coordIndexList[numCoords - 1];
	c1 = coordIndexList[0];
    }
    else {
	c0 = coordIndexList[closeCoordIndex];
	c1 = coordIndexList[closeCoordIndex + 1];
    }

    // Indices of the coordinates
    lineDetail.setLineCoordinates(c0, c1);
    
    // Set the detail point to be the close point on the edge	
    lineDetail.setPoint(closePoint);

    // Fill in the normal indices for the detail
    if (numNormals == SO_DETAIL_NO_NORMAL_NODE) {
	n0 = n1 = SO_DETAIL_NO_NORMAL_NODE;
    }
    else {
	if (numNormals == 1) {
	    // Overall
	    n0 = n1 = 0;
	}
	else {
	    // Per vertex
	    if (closeCoordIndex == numCoords - 1) {
		n0 = normalIndexList[closeCoordIndex - 1];
		n1 = normalIndexList[0];
	    }
	    else {
		n0 = normalIndexList[closeCoordIndex];
		n1 = normalIndexList[closeCoordIndex + 1];
	    }

	}
    }

    lineDetail.setLineNormals(n0, n1);
	
    // Fill in the normal for the detail point.
    SbVec3f edgeNormal; // the normal at the closePoint on the edge

    if (numNormals == SO_DETAIL_NO_NORMAL_NODE) {
	// Default normal
	edgeNormal = normal;	
    }
    else {
	SoNormal *normalNode = getNormalNode();

	if (! normalNode) {
	    edgeNormal = normal;
	}
	else {
	    if (numNormals == 1) {
		// Overall
		edgeNormal = normalNode->vector[(int) normalIndexList[0]];
	    }
	    else {
		// Per vertex
		// Interpolate the normal along the edge

		SbVec3f norm0, norm1, coord0, coord1;

		coord0 = coordNode->point[(int)c0];
		coord1 = coordNode->point[(int)c1];

		norm0 = normalNode->vector[(int)n0];
		norm1 = normalNode->vector[(int)n1];

		float dist0 = (closePoint - coord0).length();
		float dist = (coord1 - coord0).length();

		edgeNormal = norm0 + (dist0/dist) * (norm1 - norm0);
	    }
	}
    }
    
    lineDetail.setNormal(edgeNormal);

    // This stuff doesn't change.    
    lineDetail.setPath(path);
    lineDetail.setMatrix(objToWorld);
    lineDetail.setUnits(unitsType);

    return lineDetail;
}
