//
// This is a patch for the Inventor 2.0 SoPath code.  Three routines
// had bugs in them that affected some programs:
// The getLength() would return incorrect results sometimes for nodes
// with hidden children.
// The containsPath() method could return incorrect results if both
// paths contained hidden children
// And the isRelevantNotification method could cause core dumps when
// used with SoSF/MFPath fields.
//
//
// To apply this patch, compile this file into a .o and then link
// the .o before -lInventor.  The linker may give a warning.
// This is normal and expected.
//

#include 
#include 
#include 
#include 
#include 

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Returns the public length of the path.
//
// Use: public

int
SoPath::getLength() const
//
////////////////////////////////////////////////////////////////////////
{
    // Cast const away...
    SoPath *This = (SoPath *)this;

    // If we aren't sure how many are public, figure it out:
    if (numPublic == -1) {

	int lastPublicIndex = 0;
	if (minNumPublic > 1)
	    lastPublicIndex = minNumPublic - 1;

	// Last test is for the second to last node.
	// If it passes, then lastPublicIndex will be incremented to be the
	// final node, which we don't need to test.

	for (  ; lastPublicIndex < (getFullLength() - 1) ; lastPublicIndex++) {
	    // Children of this node will be private, so stop.
	    if ( ! nodes[lastPublicIndex]->isOfType(SoGroup::getClassTypeId()))
		break;
	}
	This->numPublic = This->minNumPublic = lastPublicIndex + 1;
    }
    return numPublic;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Returns TRUE if the nodes in the chain in the passed path are
//    contained (in consecutive order) in this path chain.
//
// Use: public

SbBool
SoPath::containsPath(const SoPath *path) const
//
////////////////////////////////////////////////////////////////////////
{
    int i, j;

    // First find the head of the target path in this path
    for (i = 0; i < getFullLength(); i++)
	if (getNode(i) == path->getHead())
	    break;

    // Head node is not there
    if (i == getFullLength())
	return FALSE;

    // If there aren't enough nodes left in this path, then no match
    if (getFullLength() - i < path->getFullLength())
	return FALSE;

    // Otherwise, start comparing nodes in the two paths to see if the
    // paths match
    for (j = 0; j < path->getFullLength(); j++)
	if (path->getNode(j) != getNode(i + j))
	    return FALSE;

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Returns TRUE if the given notification list involves a change to
//    a node that affects the path. It is assumed that the last (most
//    recent) node in the list is the head node of the path.
//
// Use: internal

SbBool
SoPath::isRelevantNotification(SoNotList *list) const
//
////////////////////////////////////////////////////////////////////////
{
    // Trace down the notification list to find the first node (if
    // any) that is not on the path. Stop when we reach the first
    // (in time) record that was at a node in the graph.

    const SoNotRec	*rec = list->getLastRec();
    const SoNotRec	*prevRec = NULL;
    int			curIndex = 0;
    SbBool		offPath  = FALSE;

    while (rec != NULL && curIndex < getLength()) {

	// Stop if the node in the current record is not the next node
	// in the path
	if (rec->getBase() != getNode(curIndex)) {
	    offPath = TRUE;
	    break;
	}

	// Go to the next record and path node, IF we're following a
	// PARENT notification
	if (rec->getPrevious() != NULL &&
	    rec->getPrevious()->getType() != SoNotRec::PARENT) {
	    break;
	}
	prevRec = rec;
	rec = rec->getPrevious();

	curIndex++;
    }

    // If not all notified nodes are on the path, we have to do some
    // extra testing
    if (offPath) {

	const SoNode	*node;
	int		index;

	// The "rec" record points to a node that is off the path.
	// Find out the index of this node in the parent node, which
	// is pointed to by the previous record (which is guaranteed
	// to exist and be on the path).
	node = (const SoNode *) rec->getBase();
	index= ((const SoNode *)prevRec->getBase())->getChildren()->find(node);

	// If the node is to the right of the path, the change does
	// not affect the path
	if (index > getIndex(curIndex))
	    return FALSE;

	// If it is to the left, it doesn't affect the path if any of
	// the notification records go through a separator-type object:
	else {
	    while (TRUE) {
		if (! node->affectsState())
		    return FALSE;

		rec = rec->getPrevious();
		if (rec == NULL || rec->getType() != SoNotRec::PARENT)
		    break;

		node = (const SoNode *) rec->getBase();
	    }
	}
    }

    // If we made it this far, it's a relevant notification
    return TRUE;
}