/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */


#define M12N

#ifdef MOZILLA_CLIENT

/*
 * xp_cntxt.c
 * Contains various XP context related functions
 * Recomended when FE communicates with lib code in a complex way
 */

#include "xp.h"
#include "shist.h"
#include "glhist.h"
#include "ssl.h"
#include "libimg.h"             /* Image Lib public API. */
#include "libmocha.h"
#include "libevent.h"
#include "intl_csi.h"
#include "xp_ncent.h"

static XP_List * xp_GlobalContextList = NULL;
static int32 global_context_id = 0;


void
XP_InitializeContext(MWContext *context)
{
	if (context != NULL)
	{
		context->context_id = ++global_context_id;
      context->INTL_tag = INTL_TAG;
	}
}

/* WARNING ! the mac FE does not call this ! */
MWContext *
XP_NewContext(void)
{
	MWContext *context;

	context = XP_NEW_ZAP(MWContext);
	XP_ASSERT(NULL != context);
	if (NULL == context)
	{
		return NULL;
	}
	context->INTL_CSIInfo = INTL_CSICreate();
	if (NULL == context->INTL_CSIInfo)
	{
		XP_FREE(context);
		return NULL;
	}
	XP_InitializeContext(context);
	return context;
}
/* WARNING ! the mac FE does not call this ! */
void
XP_DeleteContext(MWContext *context)
{
  XP_ASSERT(context);
  INTL_CSIDestroy(context->INTL_CSIInfo);
  XP_FREE(context);
}

/* give access to the global context list to other code too.
 */
XP_List *XP_GetGlobalContextList(void)
{
	return(xp_GlobalContextList);
}

/* cx_AddChildContext
 * Adds child context to the parents children list
 * NULL-safe
 */
void cx_AddChildContext(MWContext *parent, MWContext * child)
{
	if ((parent == NULL) || (child == NULL))
		return;
	if (parent->grid_children == NULL)
		parent->grid_children = XP_ListNew();
	if (parent->grid_children == NULL)
		return;
	XP_ListAddObject(parent->grid_children, child);
}

/* cx_RemoveChildContext
 * Removes child context from its parent
 * NULL-safe
 */
void cx_RemoveChildContext(MWContext * child)
{
	if ((child == NULL) || (child->grid_parent == NULL))
		return;
	XP_ListRemoveObject(child->grid_parent->grid_children, child);
}

/*
 * if the passed context is in the global context list
 * TRUE is returned.  Otherwise false
 */
Bool XP_IsContextInList(MWContext *context)
{
    if (context == NULL)
        return FALSE;

    if (xp_GlobalContextList == NULL)
        return(FALSE);

    return((XP_ListFindObject(xp_GlobalContextList, context)
			? TRUE : FALSE));
}

/*
 * The passed context is added to the global context list
 */
void XP_AddContextToList(MWContext *context)
{
	if (context == NULL)
		return;

	if (xp_GlobalContextList == NULL)
		xp_GlobalContextList = XP_ListNew();

	if (xp_GlobalContextList == NULL)
		return;

	XP_ListRemoveObject(xp_GlobalContextList, context);	/* No dups */
	XP_ListAddObject(xp_GlobalContextList, context);
	cx_AddChildContext(context->grid_parent, context);
}

void XP_UpdateParentContext(MWContext * child)
{
	cx_AddChildContext(child->grid_parent, child);
}
/*
 * Removes the context from the context list
 * Notifies all its children
 */
void XP_RemoveContextFromList(MWContext *context)
{
	int howMany;
	int i;

	if (context == NULL)
		return;
	cx_RemoveChildContext(context);	/* Its parent does not point to it any more */
	/* Now take care of the children, this should not really happen */
	if (context->grid_children)
	{
		howMany = XP_ListCount(context->grid_children);
		for (i = 1; i <= howMany; i++)
		{
			MWContext * child;
			child = (MWContext *)XP_ListGetObjectNum(context->grid_children, 1);
			if (child)
				child->grid_parent = NULL;
			XP_ListRemoveObject(context->grid_children, child);
		}
		XP_ListDestroy(context->grid_children);
		context->grid_children = NULL;
	}
	XP_ListRemoveObject(xp_GlobalContextList, context);
	
	/*  Remove from last active stack.
	 *  Remove any nav center info about the context.
	 */
	XP_RemoveContextFromLastActiveStack(context);
	XP_RemoveNavCenterInfo(context);
}

/* the following was adapted from xp_FindNamedContextInChildren, but
simplified for simply matching pointers instead of names. */
Bool
XP_IsChildContext(MWContext* parent,  MWContext* child)
{
	int i;
	if (parent == child) {
		return TRUE;
	}
	if (parent->grid_children) {
		int count = XP_ListCount(parent->grid_children);
		for (i = 1; i <= count; i++) {
			MWContext* tchild =
				(MWContext*)XP_ListGetObjectNum(parent->grid_children, i);
			if (child == tchild) {
				return TRUE;
			}
			else{
				XP_Bool found = XP_IsChildContext(tchild, child);
				if (found) return found;
			}
		}
	}
	return FALSE;
}


MWContext*
xp_FindNamedContextInChildren(MWContext* self, char* name, MWContext* excludeChild)
{
	int i;
/*
	XP_TRACE(("Searching for %s: context %0x, name=%s, title=%s\n",
			  (name ? name : ""), self,
			  (self->name ? self->name : ""), (self->title ? self->title : "")));
*/
	if (self->name && XP_STRCMP(self->name, name) == 0) {
/*
		XP_TRACE(("Found %s context %0x name=%s\n", (name ? name : ""),
				  self, self->name));
*/
		return self;
	}
	if (self->grid_children) {
		int count = XP_ListCount(self->grid_children);
		for (i = 1; i <= count; i++) {
			MWContext* child =
				(MWContext*)XP_ListGetObjectNum(self->grid_children, i);
			if (child != excludeChild) {
				MWContext* found = xp_FindNamedContextInChildren(child, name, NULL);
				if (found) return found;
			}
		}
	}
	return NULL;
}

/*
 * Finds a context that should be loaded with the URL, given
 * a name and current (refering) context.
 *
 * If the context returned is not NULL, name is already assigned to context
 * structure. You should load the URL into this context.
 *
 * If you get back a NULL, you should create a new window
 *
 * Both context and current context can be null.
 * Once the grids are in, there should be some kind of a logic that searches
 * siblings first. 
 */

MWContext * XP_FindNamedContextInList(MWContext * context, char *name)
{
	int i;
	
	if ((name == NULL) || (xp_GlobalContextList == NULL))
		return context;

	/*
	 * Check for special magic window target names
	 */
	if (name[0] == '_')
	{
		if (XP_STRNCMP(name, "_self", 5) == 0)
		{
			return context;
		}
		else if (XP_STRNCMP(name, "_parent", 7) == 0)
		{
			if ((context)&&(context->grid_parent))
			{
				return context->grid_parent;
			}
			else
			{
				return context;
			}
		}
		else if (XP_STRNCMP(name, "_top", 4) == 0)
		{
			MWContext *top;

			top = context;
			while ((top)&&(top->grid_parent))
			{
				top = top->grid_parent;
			}
			return top;
		}
		else if (XP_STRNCMP(name, "_blank", 6) == 0)
		{
			return NULL;
		}
		/* else, search for the name, below */
	}
	
	{
		MWContext* cx = context;
		MWContext* found;
		if (context) {
			/* If our current context has the right name, go there */
			if (cx->name && 
				(XP_STRCMP(cx->name, name) == 0))
				return cx;
			found = xp_FindNamedContextInChildren(cx, name, NULL);
			if (found) return found;
			while (cx->is_grid_cell) {
				MWContext* parent = cx->grid_parent;
				found = xp_FindNamedContextInChildren(parent, name, cx);
				if (found) return found;
				cx = parent;
			}
		}

		/* otherwise, just get any other context */
		for (i=1; i<= XP_ListCount(xp_GlobalContextList); i++)
		{
			MWContext* compContext = (MWContext *)XP_ListGetObjectNum(xp_GlobalContextList, i);
			/* Only search other top-level contexts that aren't the one we just came from: */
			if (!compContext->is_grid_cell && compContext != cx) {
				found = xp_FindNamedContextInChildren(compContext, name, NULL);
				if (found) return found;
			}
		}
	}
	return NULL;
}


/*
 * Finds a context that should be loaded with the URL, given
 * a type and current (refering) context.  Return NULL if there is none.
 */
MWContext * XP_FindContextOfType (MWContext * context, MWContextType type)
{
	int i;

	/* The other types aren't "real" - they don't have windows.  (Actually,
	   neither do all of these, but it's damned useful to be able to find the
	   biff context...) */
	XP_ASSERT (type == MWContextBrowser ||
			   type == MWContextMail ||
			   type == MWContextNews ||
			   type == MWContextMessageComposition ||
			   type == MWContextBookmarks ||
			   type == MWContextAddressBook ||
			   type == MWContextBiff ||
			   type == MWContextMailMsg ||
			   type == MWContextNewsMsg ||
			   type == MWContextEditor ||
			   type == MWContextPane); 
	/* Added MWContextEditor, needed for bug 61630  */

	/* If our current context has the right type, go there */
	if (context && context->type == type)
		return context;
	
	/* otherwise, just get any other context */
	for (i=1; i<= XP_ListCount(xp_GlobalContextList); i++)
	{
		MWContext * compContext = (MWContext *)XP_ListGetObjectNum(xp_GlobalContextList, i);
		if (compContext->type == type)
			return compContext;
	}
	return NULL;
}

MWContext*
XP_FindSomeContext()
{
    MWContext* cx = XP_FindContextOfType(NULL, MWContextBrowser);
    if (!cx)
		cx = XP_FindContextOfType(NULL, MWContextMail);
    if (!cx)
		cx = XP_FindContextOfType(NULL, MWContextNews);
    if (!cx)
		cx = XP_FindContextOfType(NULL, MWContextPane);
#if 0	/* dp says don't do this */
    if (!cx)
		cx = fe_all_MWContexts->context;
#endif
    return cx;
}

/* Returns the first non-grid parent of a given context (can be the context
 * itself if it's not a grid cell). 
 */
MWContext *
XP_GetNonGridContext(MWContext *context)
{
    MWContext* ctxt = context;
    while (ctxt && ctxt->is_grid_cell)
	ctxt = ctxt->grid_parent;
    return ctxt;
}

/* FE should call XP_FindNamedAnchor before getting a URL 
 * that is going into an HTML window (output_format is FO_CACHE_AND_PRESENT
 *
 * if named anchor is found:
 * XP_FindNamedAnchor 
 *					- updates context history, and global history properly
 * if it is not found, we do not touch any arguments
 *
 * Usage:
 * In your GetURL routine
 * if (XP_FindNamedAnchor(context, url, &x, &y))
 *  {
 * 		SetDocPosition(x,y);
 *		if (url->history_num == 0)	// if you are not handling go back this way, do not do this
 *			SHIST_AddDocument( &fContext, SHIST_CreateHistoryEntry( url, he->title) );
 *		else
 *			SHIST_SetCurrent(&fContext->hist, url->history_num);
 *      SetURLTextField(url->address);
 *      SetURLTextFieldTitle( ... url->is_netsite ... );
 *		NET_FreeURLStruct(url);
 *      RegenerateHistoryMenuAndDialogBoxContents()
 *  }
 * else
 *		NET_GetURL
 */
Bool XP_FindNamedAnchor(MWContext * context, URL_Struct * url, 
							int32 *xpos, int32 *ypos)
{
#ifdef MOZ_NGLAYOUT
  XP_ASSERT(0);
#else
	History_entry *he;
	
	if (!context)
		return FALSE;

	he = SHIST_GetCurrent (&context->hist);
	
	if (he
		&& he->address
		&& url
		&& url->address
		&& XP_STRCHR(url->address, '#') /* it must have a named hash */
		&& url->method == URL_GET_METHOD
		&& url->force_reload == NET_DONT_RELOAD
		&& LO_LocateNamedAnchor(context, url, xpos, ypos))
	{
		GH_UpdateGlobalHistory( url );
		/* don't do this - jwz. */
		/* NET_FreeURLStruct( url ); */
		return TRUE;
	}
#endif /* MOZ_NGLAYOUT */
	return FALSE;	
}

/* XP_RefreshAnchors
 * call it after loading a new URL to refresh all the anchors
 */
void XP_RefreshAnchors()
{
#ifdef MOZ_NGLAYOUT
  XP_ASSERT(0);
#else
	int i;
	for (i=1; i<= XP_ListCount(xp_GlobalContextList); i++)
	{
		MWContext * compContext = (MWContext *)XP_ListGetObjectNum(xp_GlobalContextList, i);
		LO_RefreshAnchors(compContext);
	}
#endif /* MOZ_NGLAYOUT */
}

/* XP_InterruptContext
 * interrupts this context, and all the grid cells it encloses
 */
void XP_InterruptContext(MWContext * context)
{
	int i = 1;
	MWContext * child;

	if (context == NULL)
		return;

	/* Interrupt the children recursively first.
	 * This avoids the scenario where the context is destroyed during
	 *  an interrupt and we end up dereferencing a freed context.
	 * This also avoids crazy hacks like caching the grid_children
	 *  context list until after the interrupt, and then doing the
	 *  below loop.  That can also crash if the grid_children context
	 *  list is freed off....
	 * Bug fix 58770
	 */
	while(child = (MWContext *)XP_ListGetObjectNum(context->grid_children, i++))
		XP_InterruptContext(child);

	NET_InterruptWindow(context);
    if (context->img_cx)
        IL_InterruptContext(context->img_cx);
#ifdef MOZ_NGLAYOUT
  XP_ASSERT(0);
#else
	ET_InterruptContext(context);
#endif /* MOZ_NGLAYOUT */
}

Bool XP_IsContextBusy(MWContext * context)
{
	int i = 1;
	MWContext * child;
	
	if (context == NULL)
		return FALSE;

	if (NET_AreThereActiveConnectionsForWindow(context))
		return TRUE;
	
	while ((child = (MWContext*)XP_ListGetObjectNum (context->grid_children,
													 i++)))
		if (XP_IsContextBusy(child))
			return TRUE;
	
	return FALSE;
}

Bool XP_IsContextStoppable(MWContext * context)
{
	int i = 1;
	MWContext * child;
	
	if (context == NULL)
		return FALSE;

	if (NET_AreThereActiveConnectionsForWindow(context))
		return TRUE;
	
#ifndef MOZ_NGLAYOUT
	if (LM_IsActive(context))
		return TRUE;
#endif /* MOZ_NGLAYOUT */

	while ((child = (MWContext*)XP_ListGetObjectNum (context->grid_children,
													 i++)))
		if (XP_IsContextStoppable(child))
			return TRUE;
	
	return FALSE;
}

/*
 *	Will return the lowest security status of the context
 *		and it's children.
 *	Lowest means lowest in security, not in numberic value.
 *
 *	Possible returns are:
 *	SSL_SECURITY_STATUS_NOOPT
 *	SSL_SECURITY_STATUS_OFF
 *	SSL_SECURITY_STATUS_ON_HIGH
 *	SSL_SECURITY_STATUS_ON_LOW
 */
int XP_GetSecurityStatus(MWContext *pContext)	{
	History_entry *pHistEnt;
	MWContext *pChild;
	int iRetval, iIndex;

	/*
	 *	No context, no security.
	 */
	if(pContext == NULL)	{
		return(SSL_SECURITY_STATUS_NOOPT);
	}

	/*
	 *	Obtain the context's current history entry (it holds
	 *		security info).
	 */
	pHistEnt = SHIST_GetCurrent(&(pContext->hist));
	if(pHistEnt == NULL)	{
		/*
		 *	Nothing loaded, Confusion ensues here.
		 *	If we've a parent context, then our security
		 *		is a noopt (shouldn't be counted towards
		 *		the whole).
		 *	If we've no parent context, then our security
		 *		is off.
		 *	This allows grids to have empty panes, but to
		 *		still be secure if all grids which have
		 *		something loaded are secure.
		 */
		if(pContext->grid_parent == NULL)	{
			return(SSL_SECURITY_STATUS_OFF);
		}
		else	{
			return(SSL_SECURITY_STATUS_NOOPT);
		}
	}

	/*
	 *	Our security status.
	 */
	iRetval = pHistEnt->security_on;

	/*
	 *	If we're a grid parent, our security status
	 *		is a noopt (doesn't affect the whole).
	 *	This allows the parent grid document to be non
	 *		secure, and all the inner grids to be secure,
	 *		and therefore the document should still be
	 *		considered secure as a whole.
	 */
	if(XP_ListIsEmpty(pContext->grid_children))	{
		return(iRetval);
	}
	iRetval = SSL_SECURITY_STATUS_NOOPT;

	/*
	 *	Go through each child, getting it's security status.
	 *	We combine them all and return that value.
	 */
	iIndex = 1;
	while((pChild = (MWContext *)XP_ListGetObjectNum(
		pContext->grid_children, iIndex++)))	{

		switch(XP_GetSecurityStatus(pChild))	{
		case SSL_SECURITY_STATUS_NOOPT:
			/*
			 *	No definable security status.  Don't
			 *		change our current value.
			 */
			break;
		case SSL_SECURITY_STATUS_OFF:
			/*
			 *	No need in continuing if we're turning
			 *		off security altogether.
			 */
			return(SSL_SECURITY_STATUS_OFF);
			break;
		case SSL_SECURITY_STATUS_ON_LOW:
			/*
			 *	Change the return value to lower security.
			 */
			iRetval = SSL_SECURITY_STATUS_ON_LOW;
			break;
		case SSL_SECURITY_STATUS_ON_HIGH:
			/*
			 *	If we're currently noopt, then we should
			 *		change the security status to HIGH.
			 *	Otherwise, don't change the cumulative status
			 *		which could be lower than HIGH.
			 */
			if(iRetval == SSL_SECURITY_STATUS_NOOPT)	{
				iRetval = SSL_SECURITY_STATUS_ON_HIGH;
			}
			break;
#ifdef FORTEZZA
		case SSL_SECURITY_STATUS_FORTEZZA:
			/*
			 * If any fortezza, set to fortezza..
			 */
			iRetval = SSL_SECURITY_STATUS_FORTEZZA;
			break;
#endif
		default:
			/*
			 *	Shouldn't be here, unless new security status
			 *		introduced.
			 */
			XP_ASSERT(0);
			break;
		}
	}

	/*
	 *	Context is somewhat secure, either high or low.
	 *	This could also be noopt, meaning no definable security,
	 *		so turn off your UI as if SSL_SECURITY_STATUS_OFF.
	 *	We can't just change the return value of
	 *		SSL_SECURITY_STATUS_NOOPT to OFF because this
	 *		breaks the recursive correctness of this routine.
	 *	Handle it accordingly.
	 */
	return(iRetval);
}

/*
 *	Count the contexts of the said type.
 *	The second parameter is a flag indicating wether or not the contexts
 *		counted can have parent contexts (top level context switch).
 */
int XP_ContextCount(MWContextType cxType, XP_Bool bTopLevel)
{
	int iRetval = 0;
	int iTraverse;
	MWContext *pContext;

	/*
	 *	Loop through the contexts.
	 */
	for(iTraverse = 1; iTraverse <= XP_ListCount(xp_GlobalContextList); iTraverse++)	{
		pContext = (MWContext *)XP_ListGetObjectNum(xp_GlobalContextList, iTraverse);
		if(cxType == MWContextAny || pContext->type == cxType)	{
			/*
			 *	See if there's to be no parent.
			 */
			if(bTopLevel == FALSE || pContext->is_grid_cell == FALSE)	{
				iRetval++;
			}
		}
	}
	
	return(iRetval);
}

#endif /* MOZILLA_CLIENT */
