/*
 * Copyright 1990 by Baylor College of Medicine ALL RIGHTS RESERVED. 
 *
 * This program is subject to a license agreement between 
 * Baylor College of Medicine and MIT. Any use inconsistent with
 * said license and any use by persons other than the faculty, 
 * students and staff at MIT or any use on a computer not operated 
 * as part of the Athena Computing Environment (ACE) is expressly 
 * prohibited.
 */
/****************************************************************
 * File: browser.c
 * Date: 04/30/91
 *
 * Description:
 *   This file contains several support functions for managing
 *   the browser.
 *
 * Revisions:
 ****************************************************************/
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <Xm/Xm.h>
#include <Xm/ScrolledW.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <Browser.h>
#include <VList.h>
#include <Vt.h>
#include <srvlib.h>

#include "VnsP.h"

#define VNS_DATABASE  1
#define VNS_NOTEBOOK  2
#define VNS_PAGE      3
#define VNS_OBJECT    4

typedef struct _VnsId
{
	int type;         /* type of node */
	VnsDatabase *db;  /* pointer to the database */
	int nid;          /* notebook id */
	int pid;          /* page id */
} VnsId;

typedef struct _BrowserInfo
{
	VnsContext *vns;     /* vns context info */
	Tree *tree;          /* pointer to the browser tree */
	Widget browser;      /* browser widget */
	Node *current_node;  /* poiinter to currently selected node */
	VnsId *id;
	VnsNotebook *nl;
	VnsPage *pl;
	void (*cb)();
	XtPointer cb_data;
} BrowserInfo;

/****************************************************************
 *               Private Functions
 ****************************************************************/

static
int
check_nid(node,nid)
	Node *node;
	int *nid;
{
	VnsId *id = (VnsId *)node->user_data;

	switch (id->type)
	{
		case VNS_NOTEBOOK:
			if (id->nid == *nid)
			{
				return 0;
			}
			break;
		default:
			break;
	}
	return 1;
}

static
Node *
find_notebook_in_browser(bi,nid)
	BrowserInfo *bi;
	int nid;
{
	return XmBrowserSearchTree(bi->browser,bi->tree,check_nid,(XtPointer)&nid);
}

static
int
check_pid(node,my_id)
	Node *node;
	VnsId *my_id;
{
	VnsId *id = (VnsId *)node->user_data;

	switch (id->type)
	{
		case VNS_PAGE:
			if ((id->nid == my_id->nid) && (id->pid == my_id->pid))
			{
				return 0;
			}
			break;
		default:
			break;
	}
	return 1;
}

static
Node *
find_page_in_browser(bi,nid,pid)
	BrowserInfo *bi;
	int nid;
	int pid;
{
	VnsId *id = (VnsId *)malloc(sizeof(VnsId));

	id->db = NULL;    /* this should be changed */
	id->nid = nid;
	id->pid = pid;

	return XmBrowserSearchTree(bi->browser,bi->tree,check_pid,(XtPointer)id);
}

/****************************************************************
 *               Dynamic Event Callbacks
 ****************************************************************/

static
void
note_create(bi,nl)
	BrowserInfo *bi;
	VnsNotebook *nl;
{
	Node *root;
	VnsId *new_id = (VnsId *)malloc(sizeof(VnsId));

	new_id->type = VNS_NOTEBOOK;
	new_id->db = NoteToDb(nl);
	new_id->nid = nl->nid;
	new_id->pid = 0;

	root = XmBrowserGetRootNode(bi->browser,bi->tree);

	XmBrowserAddNode(bi->browser,bi->tree,root,nl->info.title,"N",(XtPointer)new_id);
}

static
void
note_modify(bi,nl)
	BrowserInfo *bi;
	VnsNotebook *nl;
{
	Node *node;

	/* we need to find the notebook node and change the name */
	node = find_notebook_in_browser(bi,nl->nid);
	if (node != NULL)
	{
		/* only update if notebook name has changed */
		if (strcmp(node->node_title,nl->info.title) != 0)
		{
			free(node->node_title);
			node->node_title = strdup(nl->info.title);

			XmBrowserUpdateNode(bi->browser,node);
		}
	}
}

static
void
note_destroy(bi,nl)
	BrowserInfo *bi;
	VnsNotebook *nl;
{
	Node *node;

	/* we need to remove the notebook node from the tree */
	node = find_notebook_in_browser(bi,nl->nid);
	if (node != NULL)
	{
		XmBrowserDeleteNode(bi->browser,node);
	}
}

static
void
page_create(bi,pl)
	BrowserInfo *bi;
	VnsPage *pl;
{
	/*
	 * This section will be very complicated to implemented. First,
	 * we cannot be send the VnsPage structure pointer for this
	 * event if this implies that the page was created in the database
	 * The VnsPage structure only represents pages that have been
	 * visually rendered. Next, we need to determine if the page
	 * has any links to it. We then need to scan this list of pages
	 * to see if they are displayed in the browser and if they are
	 * displaying their children. In this case, we then insert the
	 * child page as a node in the browser.
	 *
	 * A tougher problem is reprogramming links. Since a user is allowed
	 * to reprogram links interactively, the browser should reflect the
	 * changes in topology. This requires that we "monitor" all activities
	 * concerning links (i.e., when they are dangling, when they are
	 * reprogrammed, when they are simply deleted, etc.). The use of
	 * callbacks will have to be expanded to reflect these changes.
	 */
	printf("page_create:\n");
	/* we need to determine if the page should be shown 
	 * if so, then we need to create a node and insert it.
	 */
}

static
void
page_modify(bi,pl)
	BrowserInfo *bi;
	VnsPage *pl;
{
	Node *node;

	/* we need to find the page and modify the name */
	node = find_page_in_browser(bi,pl->parent->nid,pl->pid);
	if (node != NULL)
	{
		/* only update if notebook name has changed */
		if (strcmp(node->node_title,pl->info.title) != 0)
		{
			free(node->node_title);
			node->node_title = strdup(pl->info.title);

			XmBrowserUpdateNode(bi->browser,node);
		}
	}
}

static
void
page_destroy(bi,pl)
	BrowserInfo *bi;
	VnsPage *pl;
{
	Node *node;

	/*
	 * we need to remove the page from the browser.
	 * but... there may be more than one showing since
	 * we can have cycles in the browser. food for though
	 */
	node = find_page_in_browser(bi,pl->parent->nid,pl->pid);
	while (node != NULL)
	{
		XmBrowserDeleteNode(bi->browser,node);
		node = find_page_in_browser(bi,pl->parent->nid,pl->pid);
	}
}

/****************************************************************
 *               Browser Support Functions
 ****************************************************************/

static
void
check_for_children(w,bi,info)
	Widget w;
	BrowserInfo *bi;
	XmBrowserCallbackStruct *info;
{
	VnsId *id;

	id = (VnsId *)info->node->user_data;

	switch (id->type)
	{
		case VNS_DATABASE:
			{
				long *notes;
				int num_notes;

				srv_get_allowed_notebooks(id->db->srv,&notes,&num_notes);

				if (num_notes > 0)
				{
					info->has_children = TRUE;
					free((char *)notes);
				}
				else
				{
					info->has_children = FALSE;
				}
			}
			break;
		case VNS_NOTEBOOK:
			{
				VnsNotebook *nl = *(VnsFindNotebook(id->db,id->nid));

				if (nl != NULL)
				{
					if (nl->info.home_page > 0)
					{
						info->has_children = TRUE;
					}
					else
					{
						info->has_children = FALSE;
					}
				}
				else
				{
					info->has_children = FALSE;
				}
			}
			break;
		case VNS_PAGE:
			{
				long *notes;
				long *pages;
				int nlinks;

				srv_inq_page_links(id->db->srv,id->nid,id->pid,&notes,&pages,&nlinks);

				if (nlinks > 0)
				{
					info->has_children = TRUE;
					free((char *)notes);
					free((char *)pages);
				}
				else
				{
					info->has_children = FALSE;
				}
			}
			break;
		default:
			fprintf(stderr,"check_for_children: VnsId has invalid type\n");
			break;
	}
}

static
void
get_children(w,bi,info)
	Widget w;
	BrowserInfo *bi;
	XmBrowserCallbackStruct *info;
{
	VnsDatabase *db;
	VnsId *id;

	id = (VnsId *)info->node->user_data;
	db = id->db;

	switch (id->type)
	{
		case VNS_DATABASE:
			{
				VnsNotebook *nl = db->first_note;

				/* notebooks are allready loaded so we just add them to the browser */
				while (nl != NULL)
				{
					VnsId *new_id = (VnsId *)malloc(sizeof(VnsId));

					new_id->type = VNS_NOTEBOOK;
					new_id->db = db;
					new_id->nid = nl->nid;
					new_id->pid = 0;

					XmBrowserAddNode(bi->browser,info->node->tree,info->node,
						nl->info.title,"N",(XtPointer)new_id);

					nl = nl->next;
				}
			}
			break;
		case VNS_NOTEBOOK:
			{
				Notebook_Info ninfo;

				srv_inq_notebook(db->srv,id->nid,&ninfo);

				if (srv_validate_page(db->srv,id->nid,ninfo.home_page) == 1)
				{
					Page_Info pinfo;
					VnsId *new = (VnsId *)malloc(sizeof(VnsId));

					srv_inq_page(db->srv,id->nid,ninfo.home_page,&pinfo);

					new->type = VNS_PAGE;
					new->db = db;
					new->nid = id->nid;
					new->pid = ninfo.home_page;

					XmBrowserAddNode(bi->browser,info->node->tree,info->node,
						pinfo.title,NULL,(XtPointer)new);
				}
			}
			break;
		case VNS_PAGE:
			{
				long *notes;
				long *pages;
				int nlinks;

				srv_inq_page_links(db->srv,id->nid,id->pid,&notes,&pages,&nlinks);

				if (nlinks > 0)
				{
					int i;

					for (i = 0; i < nlinks; i++)
					{
						Page_Info pinfo;
						VnsId *new = (VnsId *)malloc(sizeof(VnsId));

						new->type = VNS_PAGE;
						new->db = db;
						new->nid = notes[i];
						new->pid = pages[i];

						srv_inq_page(db->srv,new->nid,new->pid,&pinfo);

						XmBrowserAddNode(bi->browser,info->node->tree,info->node,
							pinfo.title,NULL,(XtPointer)new);
					}
					free((char *)notes);
					free((char *)pages);
				}
				else
				{
					info->has_children = FALSE;
				}
			}
			break;
		default:
			fprintf(stderr,"get_children: VnsId has invalid type\n");
			break;
	}
}

static
void
select_node(w,bi,info)
	Widget w;
	BrowserInfo *bi;
	XmBrowserCallbackStruct *info;
{
	switch(info->reason)
	{
		case XmCR_SINGLE:
			bi->current_node = info->node;
			bi->id = (VnsId *)info->node->user_data;
			switch (bi->id->type)
			{
				case VNS_DATABASE:
					break;
				case VNS_NOTEBOOK:
					/* call callback function */
					if (bi->cb != NULL)
					{
						bi->cb(bi->cb_data,bi->id->nid,-1);
					}
					break;
				case VNS_PAGE:
					/* call callback function */
					if (bi->cb != NULL)
					{
						bi->cb(bi->cb_data,bi->id->nid,bi->id->pid);
					}
					break;
			}
			break;
		case XmCR_DOUBLE:
			bi->current_node = info->node;
			bi->id = (VnsId *)info->node->user_data;
			switch (bi->id->type)
			{
				case VNS_DATABASE:
					{
						char msg[100];
						int port = srv_port(bi->id->db->srv);
						char *db_name = srv_database(bi->id->db->srv);
						char *hostname = srv_host(bi->id->db->srv);

						sprintf(msg,"You are connnected on port %d to database %s\n located on %s",port,db_name,hostname);
						VtMessage(bi->vns->toplevel,msg);
					}
					break;
				case VNS_NOTEBOOK:
					bi->nl = *(VnsFindNotebook(bi->id->db,bi->id->nid));
					if (srv_validate_page(bi->id->db->srv,bi->id->nid,bi->nl->info.home_page) == 1)
					{
						VnsAddPage(bi->nl,bi->nl->info.home_page,(struct return_list *)NULL,
							(char *)NULL,(VnsPage *)NULL);
					}
					else
					{
						VtError(bi->vns->toplevel,"Home page is invalid for selected notebook");
					}
					break;
				case VNS_PAGE:
					bi->nl = *(VnsFindNotebook(bi->id->db,bi->id->nid));
					if (srv_validate_page(bi->id->db->srv,bi->id->nid,bi->id->pid) == 1)
					{
						VnsAddPage(bi->nl,bi->id->pid,(struct return_list *)NULL,(char *)NULL,
							(VnsPage *)NULL);
					}
					else
					{
						VtError(bi->vns->toplevel,"Page is invalid for selected notebook");
					}
					break;
			}
			break;
		default:
			break;
	}
}

static
void
destroy_node(w,bi,info)
	Widget w;
	BrowserInfo *bi;
	XmBrowserCallbackStruct *info;
{
	free((char *)info->node->user_data);
}

Widget
VnsBuildBrowser(vns,parent,width,height,cb,cb_data)
	VnsContext *vns;
	Widget parent;
	int width;
	int height;
	void (*cb)();
	XtPointer cb_data;
{
	BrowserInfo *bi = (BrowserInfo *)malloc(sizeof(BrowserInfo));
	Widget frm, sw;

	/* add some callbacks */
	VnsAddCallback(vns,CB_NOTEBOOK_CREATE,note_create,(VnsPointer)bi);
	VnsAddCallback(vns,CB_NOTEBOOK_MODIFY,note_modify,(VnsPointer)bi);
	VnsAddCallback(vns,CB_NOTEBOOK_DELETE,note_destroy,(VnsPointer)bi);
	VnsAddCallback(vns,CB_PAGE_CREATE,page_create,(VnsPointer)bi);
	VnsAddCallback(vns,CB_PAGE_MODIFY,page_modify,(VnsPointer)bi);
	VnsAddCallback(vns,CB_PAGE_DELETE,page_destroy,(VnsPointer)bi);

	bi->vns = vns;
	bi->nl = NULL;
	bi->pl = NULL;
	bi->id = NULL;
	bi->current_node = NULL;
	bi->cb = cb;
	bi->cb_data = cb_data;

	sw = XtVaCreateManagedWidget("sw",xmScrolledWindowWidgetClass,parent,
		XmNscrollingPolicy,XmAUTOMATIC,
		XtNstretchHoriz,True,
		XtNstretchVert,True,
		XmNwidth,width,
		XmNheight,height,
		NULL) ;

	bi->browser = XtVaCreateManagedWidget("Browser",browserWidgetClass,sw,
		XmNborderWidth,1,
		XmNdoubleClickDelay,400,
		XmNhorizontalSpacing,10,
		XmNverticalSpacing,4,
		XmNoutlineIndention,5,
		XtNstretchHoriz,True,
		XtNstretchVert,True,
		XmNwidth,width - 6,
		XmNheight,height - 6,
		XmNuserData,bi,
		NULL);

	bi->tree = XmBrowserCreateTree(bi->browser,"Links");

	XmBrowserAddCallback(bi->browser,bi->tree,XmNsingleSelectionCallback,
		select_node,(XtPointer)bi);
	XmBrowserAddCallback(bi->browser,bi->tree,XmNdoubleClickCallback,
		select_node,(XtPointer)bi);
	XmBrowserAddCallback(bi->browser,bi->tree,XmNdestroyNodeCallback,
		destroy_node,(XtPointer)bi);
	XmBrowserAddCallback(bi->browser,bi->tree,XmNgetChildrenCallback,
		get_children,(XtPointer)bi);
	XmBrowserAddCallback(bi->browser,bi->tree,XmNcheckForChildrenCallback,
		check_for_children,(XtPointer)bi);

	{
		VnsId *root_id = (VnsId *)malloc(sizeof(VnsId));
		char *db_name = srv_database(vns->current_db->srv);

		root_id->type = VNS_DATABASE;
		root_id->db = vns->current_db;
		root_id->nid = 0;
		root_id->pid = 0;

		XmBrowserAddNode(bi->browser,bi->tree,NULL,db_name,"D",(XtPointer)root_id);
	}

	XmBrowserSetTree(bi->browser,bi->tree);

	return bi->browser;
}
