/*	Hypertext "Anchor" Object				HTAnchor.c
**	==========================
**
** An anchor represents a region of a hypertext document which is linked to
** another anchor in the same or a different document.
**
** History
**
**         Nov 1990  Written in Objective-C for the NeXT browser (TBL)
**	24-Oct-1991 (JFG), written in C, browser-independant 
**	21-Nov-1991 (JFG), first complete version
**
**	(c) Copyright CERN 1991 - See Copyright.html
*/

#define HASH_SIZE 101		/* Arbitrary prime. Memory/speed tradeoff */

#include "HTUtils.h"
#include "tcp.h"
#include <ctype.h>
#include "HTAnchor.h"
#include "HTParse.h"

#include "LYLeaks.h"

/*
 *	This is the hashing function used to determine which list in the
 *		adult_table a parent anchor should be put in.  This is a
 *		much simpler function than the original used.
 */
#define HASH_FUNCTION(cp_address) ((unsigned short int)strlen(cp_address) *\
	(unsigned short int)TOUPPER(*cp_address) % HASH_SIZE)

typedef struct _HyperDoc Hyperdoc;
#ifdef VMS
struct _HyperDoc {
	int junk;	/* VMS cannot handle pointers to undefined structs */
};
#endif

PRIVATE HTList **adult_table=0;  /* Point to table of lists of all parents */

/*				Creation Methods
**				================
**
**	Do not use "new" by itself outside this module. In order to enforce
**	consistency, we insist that you furnish more information about the
**	anchor you are creating : use newWithParent or newWithAddress.
*/

PRIVATE HTParentAnchor * HTParentAnchor_new
  NOARGS
{
  HTParentAnchor *newAnchor = 
    (HTParentAnchor *) calloc (1, sizeof (HTParentAnchor));  /* zero-filled */
  newAnchor->parent = newAnchor;
  return newAnchor;
}

PRIVATE HTChildAnchor * HTChildAnchor_new
  NOARGS
{
  return (HTChildAnchor *) calloc (1, sizeof (HTChildAnchor));  /* zero-filled */
}


/*	Case insensitive string comparison
**	----------------------------------
** On entry,
**	s	Points to one string, null terminated
**	t	points to the other.
** On exit,
**	returns	YES if the strings are equivalent ignoring case
**		NO if they differ in more than  their case.
*/

PRIVATE BOOL equivalent
  ARGS2 (CONST char *,s, CONST char *,t)
{
  if (s && t) {  /* Make sure they point to something */
    for ( ; *s && *t ; s++, t++) {
        if (TOUPPER(*s) != TOUPPER(*t))
	  return NO;
    }
    return TOUPPER(*s) == TOUPPER(*t);
  } else
    return s == t;  /* Two NULLs are equivalent, aren't they ? */
}


/*	Create new or find old sub-anchor
**	---------------------------------
**
**	Me one is for a new anchor being edited into an existing
**	document. The parent anchor must already exist.
*/

PUBLIC HTChildAnchor * HTAnchor_findChild
  ARGS2 (HTParentAnchor *,parent, CONST char *,tag)
{
  HTChildAnchor *child;
  HTList *kids;

  if (! parent) {
    if (TRACE) fprintf(stderr, "HTAnchor_findChild called with NULL parent.\n");
    return NULL;
  }
  if (kids = parent->children) {  /* parent has children : search them */
    if (tag && *tag) {		/* TBL */
	while (NULL != (child=(HTChildAnchor*)HTList_nextObject(kids))) {
	    if (equivalent(child->tag, tag)) { /* Case sensitive 920226 */
		if (TRACE) fprintf (stderr,
	       "Child anchor %p of parent %p with name `%s' already exists.\n",
		    (void*)child, (void*)parent, tag);
		return child;
	    }
	}
     }  /*  end if tag is void */
  } else  /* parent doesn't have any children yet : create family */
    parent->children = HTList_new ();

  child = HTChildAnchor_new ();
  if (TRACE) fprintf(stderr, "new Anchor %p named `%s' is child of %p\n",
       (void*)child, (int)tag ? tag : (CONST char *)"" , (void*)parent); /* int for apollo */
  HTList_addObject (parent->children, child);
  child->parent = parent;
  StrAllocCopy(child->tag, tag);
  return child;
}


/*	Create or find a child anchor with a possible link
**	--------------------------------------------------
**
**	Create new anchor with a given parent and possibly
**	a name, and possibly a link to a _relatively_ named anchor.
**	(Code originally in ParseHTML.h)
*/
PUBLIC HTChildAnchor * HTAnchor_findChildAndLink
  ARGS4(
       HTParentAnchor *,parent,	/* May not be 0 */
       CONST char *,tag,	/* May be "" or 0 */
       CONST char *,href,	/* May be "" or 0 */
       HTLinkType *,ltype	/* May be 0 */
       )
{
  HTChildAnchor * child = HTAnchor_findChild(parent, tag);

  if(TRACE)
      fprintf(stderr,"Entered HTAnchor_findChildAndLink\n");

  if (href && *href) {
    char * relative_to = HTAnchor_address((HTAnchor *) parent);
    DocAddress parsed_doc;
    HTAnchor * dest;

    parsed_doc.address = HTParse(href, relative_to, PARSE_ALL);
    parsed_doc.post_data = 0;
    parsed_doc.post_content_type = 0;
    dest = HTAnchor_findAddress(&parsed_doc);

    HTAnchor_link((HTAnchor *) child, dest, ltype);
    free(parsed_doc.address);
    free(relative_to);
  }
  return child;
}


/*	Create new or find old named anchor
**	-----------------------------------
**
**	Me one is for a reference which is found in a document, and might
**	not be already loaded.
**	Note: You are not guaranteed a new anchor -- you might get an old one,
**	like with fonts.
*/

HTAnchor * HTAnchor_findAddress ARGS1 (CONST DocAddress *,newdoc)
{
  char *tag = HTParse (newdoc->address, "", PARSE_ANCHOR);  /* Anchor tag specified ? */

  if(TRACE)
	fprintf(stderr,"Entered HTAnchor_findAddress\n");

  /* If the address represents a sub-anchor, we recursively load its parent,
     then we create a child anchor within that document. */
  if (*tag) {
    DocAddress parsed_doc;
    HTParentAnchor * foundParent;
    HTChildAnchor * foundAnchor;

    parsed_doc.address = HTParse(newdoc->address, "", 
		PARSE_ACCESS | PARSE_HOST | PARSE_PATH | PARSE_PUNCTUATION);
    parsed_doc.post_data = newdoc->post_data;
    parsed_doc.post_content_type = newdoc->post_content_type;
    
    foundParent = (HTParentAnchor *) HTAnchor_findAddress (&parsed_doc);
    foundAnchor = HTAnchor_findChild (foundParent, tag);
    free (parsed_doc.address);
    free (tag);
    return (HTAnchor *) foundAnchor;
  }
  
  else { /* If the address has no anchor tag, 
	    check whether we have this node */
    int hash;
    CONST char *p;
    HTList * adults;
    HTList *grownups;
    HTParentAnchor * foundAnchor;

    free (tag);
    
    /* Select list from hash table */
    hash = HASH_FUNCTION(newdoc->address);
    if (!adult_table)
        adult_table = (HTList**) calloc(HASH_SIZE, sizeof(HTList*));
    if (!adult_table[hash])
        adult_table[hash] = HTList_new();
    adults = adult_table[hash];

    /* Search list for anchor */
    grownups = adults;
    while (NULL != (foundAnchor=(HTParentAnchor*)HTList_nextObject(grownups))) {
       if (equivalent(foundAnchor->address, newdoc->address) &&
	   equivalent(foundAnchor->post_data, newdoc->post_data)) {
	if (TRACE) fprintf(stderr, "Anchor %p with address `%s' already exists.\n",
			  (void*) foundAnchor, newdoc->address);
	return (HTAnchor *) foundAnchor;
      }
    }
    
    /* Node not found : create new anchor */
    foundAnchor = HTParentAnchor_new ();
    if (TRACE) fprintf(stderr, "New anchor %p has hash %d and address `%s'\n",
    	(void*)foundAnchor, hash, newdoc->address);
    StrAllocCopy(foundAnchor->address, newdoc->address);
    if(newdoc->post_data)
	StrAllocCopy(foundAnchor->post_data, newdoc->post_data);
    if(newdoc->post_content_type)
	StrAllocCopy(foundAnchor->post_content_type, newdoc->post_content_type);
    HTList_addObject (adults, foundAnchor);
    return (HTAnchor *) foundAnchor;
  }
}


/*	Delete an anchor and possibly related things (auto garbage collection)
**	--------------------------------------------
**
**	The anchor is only deleted if the corresponding document is not loaded.
**	All outgoing links from parent and children are deleted, and this anchor
**	is removed from the sources list of all its targets.
**	We also try to delete the targets whose documents are not loaded.
**	If this anchor's source list is empty, we delete it and its children.
*/

PRIVATE void deleteLinks
  ARGS1 (HTAnchor *,me)
{
	/*
	 *	Memory leaks fixed.
	 *	05-27-94 Lynx 2-3-1 Garrett Arch Blythe
	 */

	/*
	 *	Anchor is NULL, do nothing.
	 */
	if(!me)	{
		return;
	}

	/*
	 *	Unregister me with our mainLink destination anchor's parent.
	 */
	if(me->mainLink.dest)	{
		auto HTParentAnchor *parent = me->mainLink.dest->parent;

		/*
		 *	Remove me from the parent's sources so that the
		 *		parent knows one less anchor is it's dest.
		 */
		if(!HTList_isEmpty(parent->sources))	{
			/*
			 *	Really should only need to deregister once.
			 */
			HTList_removeObject(parent->sources, (void *)me);
		}

		/*
		 *	Test here to avoid calling overhead.
		 *	If the parent has no loaded document, then we should
	 	 *		tell it to attempt to delete itself.
		 *	Don't do this jass if the anchor passed in is the same
		 *		as the anchor to delete.
		 *	Also, don't do this if the destination parent is our
		 *		parent.
		 */
		if(!parent->document && parent != (HTParentAnchor *)me
			&& me->parent != parent)	{
			HTAnchor_delete(parent);
		}

		/*
		 *	At this point, we haven't a mainLink.  Set it to be
		 *		so.
		 *	Leave the HTAtom pointed to by type up to other code to
		 *		handle (reusable, near static).
		 */
		me->mainLink.dest = NULL;
		me->mainLink.type = NULL;
	}

	/*
	 *	Check for extra destinations in our links list.
	 */
	if(!HTList_isEmpty(me->links))	{
		auto HTLink *target;
		auto HTParentAnchor *parent;

		/*
		 *	Take out our extra non mainLinks one by one, calling
		 *		their parents to know that they are no longer
		 *		the destination of me's anchor.
	 	 */
		while(target = (HTLink *)HTList_removeLastObject(me->links))
		{
			parent = target->dest->parent;

			if(!HTList_isEmpty(parent->sources))	{
				/*
				 *	Only need to tell destination parent
				 *		anchor once.
				 */
				HTList_removeObject(parent->sources,
					(void *)me);
			}

			/*
			 *	Avoid calling overhead.
			 *	If the parent hasn't a loaded document, then
			 *		we will attempt to have the parent
			 *		delete itself.
			 *	Don't call twice if this is the same anchor
			 *		that we are trying to delete.
			 *	Also, don't do this if we are trying to delete
			 *		our parent.
			 */
			if(!parent->document && (HTParentAnchor *)me
				!= parent && me->parent != parent)	{
				HTAnchor_delete(parent);
			}
		}

		/*
		 *	At this point, me no longer has any destination in
		 *		the links list.  Get rid of it.
		 */
		if(me->links)	{
			HTList_delete(me->links);
			me->links = NULL;
		}
	}

	/*
	 *	Catch in case links list exists but nothing in it.
	 */
	if(me->links)	{
		HTList_delete(me->links);
		me->links = NULL;
	}
}

PUBLIC BOOL HTAnchor_delete
  ARGS1 (HTParentAnchor *,me)
{
	/*
	 *	Memory leaks fixed.
	 *	05-27-94 Lynx 2-3-1 Garrett Arch Blythe
	 */
	auto HTChildAnchor *child;

	/*
	 *	Do nothing if nothing to do.
	 */
	if(!me)	{
		return(NO);
	}

	/*
	 *	Don't delete if document is loaded or being loaded.
	 */
	if(me->document || me->underway) {
		return(NO);
	}

	/*
	 *	Recursively try to delete destination anchors of this parent.
	 *	In any event, this will tell all destination anchors that we
	 *		no longer consider them a destination.
	 */
	deleteLinks((HTAnchor *)me);

	/*
	 *	There are still incoming links to this one (we are the
	 *		destination of another anchor).
	 *	Don't actually delete this anchor, but children are OK to
	 *		delete their links.
	 */
	if(!HTList_isEmpty(me->sources))	{
		/*
		 *	Delete all outgoing links from children, do not
		 *		delete the children though.
		 */
		if(!HTList_isEmpty(me->children))	{
			while(child = (HTChildAnchor *)HTList_removeLastObject(
				me->children))	{
				if(child != NULL)	{
					deleteLinks((HTAnchor *)child);
				}
			}
		}

		/*
		 *	Can't delete parent, still have sources.
		 */
		return(NO);
	}

	/*
	 *	No more incoming links : kill everything
	 *	First, recursively delete children and their links.
	 */
	if(!HTList_isEmpty(me->children))	{
		while(child = (HTChildAnchor *)HTList_removeLastObject(me->
			children))	{
			if(child)	{
				deleteLinks((HTAnchor *)child);
				if(child->tag)	{
					free(child->tag);
				}
				free(child);
			}
		}
	}

	/*
	 *	Delete our empty list of children.
	 */
	if(me->children)	{
		HTList_delete(me->children);
	}
	/*
	 *	Delete our empty list of sources.
	 */
	if(me->sources)	{
		HTList_delete(me->sources);
	}
	/*
	 *	Delete the methods list.
	 */
	if(me->methods)	{
		/*
	 	 *	Leave what the methods point to up in memory for
		 *		other code (near static stuff).
		 */
		HTList_delete(me->methods);
	}

	/*
	 *	Free up all allocated members.
	 */
	if(me->isIndexAction)	{
		free(me->isIndexAction);
	}

	if(me->title)	{
		free(me->title);
	}
	if(me->physical)	{
		free(me->physical);
	}
	if(me->post_data)	{
		free(me->post_data);
	}
	if(me->post_content_type)	{
		free(me->post_content_type);
	}
	if(me->owner)	{
		free(me->owner);
	}

	/*
	 *	Remove ourselves from the hash table's list.
	 */
	if(adult_table)	{
		auto unsigned short int usi_hash = HASH_FUNCTION(me->address);
		if(adult_table[usi_hash])	{
			HTList_removeObject(adult_table[usi_hash], (void *)me);
		}
	}

	/*
	 *	Original code wanted a way to clean out the HTFormat if no
	 *		longer needed (ref count?).  I'll leave it alone since
	 *		those HTAtom objects are a little harder to know where
	 *		they are being referenced all at one time. (near static)
 	 */

	/* free the address too */
	/* LJM 12-5-94 */
	if(me->address)
	    free(me->address);
	/*
	 *	Finally, kill the parent anchor passed in.
	 */
	free(me);
}


/*		Move an anchor to the head of the list of its siblings
**		------------------------------------------------------
**
**	This is to ensure that an anchor which might have already existed
**	is put in the correct order as we load the document.
*/

void HTAnchor_makeLastChild
  ARGS1(HTChildAnchor *,me)
{
  if (me->parent != (HTParentAnchor *) me) {  /* Make sure it's a child */
    HTList * siblings = me->parent->children;
    HTList_removeObject (siblings, me);
    HTList_addObject (siblings, me);
  }
}

/*	Data access functions
**	---------------------
*/

PUBLIC HTParentAnchor * HTAnchor_parent
  ARGS1 (HTAnchor *,me)
{
  return me ? me->parent : NULL;
}

void HTAnchor_setDocument
  ARGS2 (HTParentAnchor *,me, HyperDoc *,doc)
{
  if (me)
    me->document = doc;
}

HyperDoc * HTAnchor_document
  ARGS1 (HTParentAnchor *,me)
{
  return me ? me->document : NULL;
}


/* We don't want code to change an address after anchor creation... yet ?
void HTAnchor_setAddress
  ARGS2 (HTAnchor *,me, char *,addr)
{
  if (me)
    StrAllocCopy (me->parent->address, addr);
}
*/

char * HTAnchor_address
  ARGS1 (HTAnchor *,me)
{
  char *addr = NULL;
  if (me) {
    if (((HTParentAnchor *) me == me->parent) ||
    	!((HTChildAnchor *) me)->tag) {  /* it's an adult or no tag */
      StrAllocCopy (addr, me->parent->address);
    }
    else {  /* it's a named child */
      addr = malloc (2 + strlen (me->parent->address)
		     + strlen (((HTChildAnchor *) me)->tag));
      if (addr == NULL) outofmem(__FILE__, "HTAnchor_address");
      sprintf (addr, "%s#%s", me->parent->address,
	       ((HTChildAnchor *) me)->tag);
    }
  }
  return addr;
}



void HTAnchor_setFormat
  ARGS2 (HTParentAnchor *,me, HTFormat ,form)
{
  if (me)
    me->format = form;
}

HTFormat HTAnchor_format
  ARGS1 (HTParentAnchor *,me)
{
  return me ? me->format : NULL;
}



void HTAnchor_setIndex
  ARGS2 (HTParentAnchor *,me, char *,address)
{
  if (me) {
    me->isIndex = YES;
    StrAllocCopy(me->isIndexAction, address);
  }
}

BOOL HTAnchor_isIndex
  ARGS1 (HTParentAnchor *,me)
{
  return me ? me->isIndex : NO;
}



BOOL HTAnchor_hasChildren
  ARGS1 (HTParentAnchor *,me)
{
  return me ? ! HTList_isEmpty(me->children) : NO;
}

/*	Title handling
*/
CONST char * HTAnchor_title
  ARGS1 (HTParentAnchor *,me)
{
  return me ? me->title : 0;
}

void HTAnchor_setTitle
  ARGS2(HTParentAnchor *,me, CONST char *,title)
{
  StrAllocCopy(me->title, title);
}

void HTAnchor_appendTitle
  ARGS2(HTParentAnchor *,me, CONST char *,title)
{
  StrAllocCat(me->title, title);
}

/*	Owner handling
*/
CONST char * HTAnchor_owner
  ARGS1 (HTParentAnchor *,me)
{
  return (me ? me->owner : 0);
}

void HTAnchor_setOwner
  ARGS2(HTParentAnchor *,me, CONST char *,owner)
{
  StrAllocCopy(me->owner, owner);
}

/*	Link me Anchor to another given one
**	-------------------------------------
*/

BOOL HTAnchor_link
  ARGS3(HTAnchor *,source, HTAnchor *,destination, HTLinkType *,type)
{
  if (! (source && destination))
    return NO;  /* Can't link to/from non-existing anchor */
  if (TRACE) fprintf(stderr,
  		     "Linking anchor %p to anchor %p\n", source, destination);
  if (! source->mainLink.dest) {
    source->mainLink.dest = destination;
    source->mainLink.type = type;
  } else {
    HTLink * newLink = (HTLink *) calloc (1, sizeof (HTLink));
    if (newLink == NULL) outofmem(__FILE__, "HTAnchor_link");
    newLink->dest = destination;
    newLink->type = type;
    if (! source->links)
      source->links = HTList_new ();
    HTList_addObject (source->links, newLink);
  }
  if (!destination->parent->sources)
    destination->parent->sources = HTList_new ();
  HTList_addObject (destination->parent->sources, source);
  return YES;  /* Success */
}


/*	Manipulation of links
**	---------------------
*/

HTAnchor * HTAnchor_followMainLink
  ARGS1 (HTAnchor *,me)
{
  return me->mainLink.dest;
}

HTAnchor * HTAnchor_followTypedLink
  ARGS2 (HTAnchor *,me, HTLinkType *,type)
{
  if (me->mainLink.type == type)
    return me->mainLink.dest;
  if (me->links) {
    HTList *links = me->links;
    HTLink *link;
    while (NULL != (link=(HTLink*)HTList_nextObject(links)))
      if (link->type == type)
	return link->dest;
  }
  return NULL;  /* No link of me type */
}


/*	Make main link
*/
BOOL HTAnchor_makeMainLink
  ARGS2 (HTAnchor *,me, HTLink *,movingLink)
{
  /* Check that everything's OK */
  if (! (me && HTList_removeObject (me->links, movingLink)))
    return NO;  /* link not found or NULL anchor */
  else {
    /* First push current main link onto top of links list */
    HTLink *newLink = (HTLink*) calloc (1, sizeof (HTLink));
    if (newLink == NULL) outofmem(__FILE__, "HTAnchor_makeMainLink");
    memcpy (newLink, & me->mainLink, sizeof (HTLink));
    HTList_addObject (me->links, newLink);

    /* Now make movingLink the new main link, and free it */
    memcpy (& me->mainLink, movingLink, sizeof (HTLink));
    free (movingLink);
    return YES;
  }
}


/*	Methods List
**	------------
*/

PUBLIC HTList * HTAnchor_methods ARGS1(HTParentAnchor *, me)
{
    if (!me->methods) {
        me->methods = HTList_new();
    }
    return me->methods;
}

/*	Protocol
**	--------
*/

PUBLIC void * HTAnchor_protocol ARGS1(HTParentAnchor *, me)
{
    return me->protocol;
}

PUBLIC void HTAnchor_setProtocol ARGS2(HTParentAnchor *, me,
	void*,	protocol)
{
    me->protocol = protocol;
}

/*	Physical Address
**	----------------
*/

PUBLIC char * HTAnchor_physical ARGS1(HTParentAnchor *, me)
{
    return me->physical;
}

PUBLIC void HTAnchor_setPhysical ARGS2(HTParentAnchor *, me,
	char *,	physical)
{
    StrAllocCopy(me->physical, physical);
}
