/*********************************************************************
 *
 *         EZWGL, the EZ Widget and Graphics Library
 *
 *             Copyright (C) 1996, 1997  Maorong Zou
 *  
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **********************************************************************/
/*
 *  June 1996.  Beta Release.
 *  Sept 1996.  Release Version 1.0
 *  Dec 1996.  Release Version 1.1 Beta
 *  April 1997.  Release Version 1.2
 *  November 1997.  Release Version 1.3
 */
/*****************************************************************
 ***                                                           ***
 ***     Manipulating Dir Trees.                               ***
 ***                                                           ***
 *****************************************************************/
#define _EZ_TREE_DIR_C_

#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include "EZ_Widget.h"
#include "EZ_WidgetConfig.h"

/*********************************************************************
 * 
 *  Functions implemented in this file:  ?????
 */
EZ_TreeNode      *EZ_CreateDirTree MY_ANSIARGS((char *pattern));
void             *(*EZ_SetDirTreeFileNodeCreator()) MY_ANSIARGS((void (*cr)
								 MY_ANSIARGS(( char *name))  ));
void             *(*EZ_SetDirTreeDirectoryNodeCreator()) MY_ANSIARGS((void (*cr)
								 MY_ANSIARGS(( char *name))  ));
void             *EZ_DefaultDirTreeFileNodeCreator MY_ANSIARGS(( char *name));
void             *EZ_DefaultDirTreeDirectoryNodeCreator MY_ANSIARGS(( char *name));

char             *EZ_GetDirTreeNodeFullPath MY_ANSIARGS((EZ_TreeNode *name));

void             EZ_SetDirTreeOpenDirectoryMode MY_ANSIARGS((int mode));
void             EZ_OpenCloseDirTreeDirectoryNodeDefault MY_ANSIARGS((EZ_Item *item, void *data));
void             EZ_OpenCloseDirTreeDirectoryNodeCollapse MY_ANSIARGS((EZ_Item *item, void *data));

/*************************************************************
 *
 * Need to think more about this: how to make the task of 
 * creating a dir-tree simple? 
 *
 *  DTFileNodeCreator: a function which will be called to
 *  create a DisplayItem given file name.
 */
static EZ_TreeNode *CompleteDirTreeBranch MY_ANSIARGS((EZ_TreeNode *node,
						       char *path, int start));
static void        InitDefaultDirTreeItemProperty MY_ANSIARGS((void));
static void        OpenCloseDirTreeDirectoryNodeWork MY_ANSIARGS((EZ_Item *item, int collapsflag));
								  
static void        *createDirNode MY_ANSIARGS((char *name));
static void        *createFileNode MY_ANSIARGS((char *name));

/* these are used for default dir tree */
#include "xpms/folderC.xpm"
#include "xpms/folderO.xpm"
#include "xpms/file.xpm"

static EZ_TextProp *dirItemProperty = NULL;
static EZ_TextProp *fileItemProperty = NULL;
static EZ_Bitmap *folderCBitmap = NULL;
static EZ_Bitmap *folderOBitmap = NULL;
static EZ_Bitmap *fileBitmap = NULL;
static int OpenDirNodeMode = EZ_DIR_NODE_OPEN_DIRECTORY_DEFAULT;  /* how to open a dir node */
static void *(*DTFileNodeCreator) MY_ANSIARGS((char *name))
     = EZ_DefaultDirTreeFileNodeCreator;
static void *(*DTDirectoryNodeCreator) MY_ANSIARGS((char *name))
     = EZ_DefaultDirTreeDirectoryNodeCreator;

/*************************************************************
 *
 * given a pathname (may contain glob characters or ~),
 * create a (partial) DirTree.
 */
EZ_TreeNode *EZ_CreateDirTree(pat)
     char *pat;
{
  char *pathname = NULL, pattern[1024], cpy[1024], *patterns[16];
  int position,  npatterns, ii;
  EZ_TreeNode *root = NULL, *cnode = NULL;
  /*
   * step 1, process pat.
   */
  if(pat == NULL || *pat == 0)  return((EZ_TreeNode *)NULL);  /* nothing */
  (void)strcpy(cpy, pat); /* make a copy of pat, cpy is going to be modified */
  if( (npatterns = EZ_CountSetFilePatterns(cpy, patterns)) == 0)  return((EZ_TreeNode *)NULL);

  for(ii = 0; ii < npatterns && ii < 16; ii++)
    {
      (void) strcpy(pattern, patterns[ii]);

      position = EZ_ProcessFileSelectorPattern(&pathname, pattern);  /* pattern may be modified */
      if( pathname == (char *)NULL) return(root); /* should never happen */
      /* 
       * pathname is the parent-dir name of pattern, ended with a '/'
       * postion is the index after the last / before globing characters in pattern.
       */

      if(ii == 0)  /* create a branch from / to the point before glob chars */
	{
	  /* 
	   * Step 2.  the common part. 
	   */
	  EZ_TreeNode *tmp;
	  char *p0, *p1, *end, *qs, *qt;
	  char tfname[1024];
	
	  end = pathname + position;  /* no braching up to here */
	  p0 = p1 = pathname;
	  while( p1 < end)
	    {
	      if(*p1 == '/')
		{
		  if(p1 == pathname) /* the initial / */
		    {
		      tfname[0]= '/'; tfname[1] = 0;
		    }
		  else
		    {
		      qs = pathname;
		      qt = tfname;
		      while(qs < p1) { *qt++ = *qs++; } *qt = 0;
		    }
		  {
		    struct stat finfo;
		    if((stat(tfname, &finfo) >= 0) && S_ISDIR (finfo.st_mode))
		      tmp = (EZ_TreeNode *) createDirNode(tfname);
		    else tmp = (EZ_TreeNode *) createFileNode(tfname);

		    if(tmp != (EZ_TreeNode *)NULL)
		      { 
			EZ_Item *item = EZ_TreeNodeItem(tmp);
			EZ_TreeNodeParent(tmp) = cnode;
			if(root == NULL) {root = tmp; }
			else EZ_TreeNodeChildren(cnode) = tmp;
			cnode = tmp;
			if(item)
			  {
			    if(p0 == pathname) EZ_ItemID(item) = (char *)EZ_AllocCopyString(NULL);
			    else EZ_ItemID(item) = (char *)EZ_AllocCopyString((tfname+(p0-pathname)));
			  }
		      }
		  }
		  p0 = p1+1;
		}
	      p1++;
	    }
	  if(root == NULL)  return( (EZ_TreeNode *)NULL); /* something is terribly wrong !! */
	}
      else  /* root is already set */
	{
	  char *p0, *p1, *end;
	  char tfname[1024];
	  int pos = (position > 0 ? position - 1: 0);  /* must share a common / */

	  end = pathname + pos;  /* no braching up to here */
	  p1 = pathname;
	  p0 = tfname;
	  while( p1 < end) *p0++ = *p1++; *p0 = 0;

	  cnode = CompleteDirTreeBranch(root, tfname, 1);  /* start after the initial / */
	  if(cnode == NULL) cnode = root;
	}
      
      /*
       * step 3. Now do the globing and create nodes or subtrees.
       */
      if(position < strlen(pattern)) 
	{
	  char **dirlist;
	  dirlist = (char **) EZ_GlobFileName(pattern);
	  if(dirlist)
	    {
	      int j=0;
	      while(dirlist[j] != NULL) j++;
	      qsort(dirlist, j, sizeof(char *), EZ_CmpStr);
	      
	      for(j = 0; dirlist[j] != NULL; j++)
		{
		  (void)CompleteDirTreeBranch(cnode, dirlist[j], position);
		  (void)free((char *)dirlist[j]);  /* not allocated by my_malloc */
		}
	      (void)free((char *)dirlist);  /* not allocated by my_malloc */
	    }
	}
    }
  if( pathname != (char *)NULL) (void) my_free((char *)pathname);
  return(root);
}

static EZ_TreeNode *CompleteDirTreeBranch(pnode, path, start)
     EZ_TreeNode *pnode;  /* the branch being created will start at this node */
     char    *path;   /* complete path name */ 
     int     start;   /* position of path to begin with, must not be the leading / */
{
  EZ_TreeNode *cnode, *children, *node_ret = NULL;
  char *p0, *p1, *end, *cptr, *t;
  char tname[1024];
  int length, done = 0, find;

  if(pnode == (EZ_TreeNode *)NULL || path == (char *)NULL) return(NULL);
  length = strlen(path);
  if(length < start) return(NULL);  /* nothing need to be done */
 
  cnode = pnode;
  end = path + length;
  p0 = path + start;
  while(!done)
    {
      /* 1. grab the next field */
      p1 = p0;
      while(*p1 != '/'  &&  p1 < end) p1++;
      cptr = tname;
      t = p0;
      while(t < p1) { *cptr++ = *t++;} *cptr = 0;
      if(p1 >= end || *p1 == 0) done = 1;

      /* 2. search to see if there is already a tree-node for it */
      find = 0;
      children = EZ_TreeNodeChildren(cnode);
      while(children)
	{
	  EZ_Item *item = EZ_TreeNodeItem(children);
	  if(item && EZ_ItemID(item) && !strcmp(tname,EZ_ItemID(item)))
	    {
	      find = 1;
	      node_ret = cnode = children;
	      break;
	    }
	  children = EZ_TreeNodeSibling(children);
	}
      
      /* 2.1. if not, complete the rest */
      if(find == 0) 
	{
	  EZ_TreeNode *tnode;
	  while(1)
	    {
	      /* 1. get the full path for this item */
	      cptr = tname;
	      t = path;
	      while(t <= p1) { *cptr++ = *t++;} *cptr = 0;
	      /* 2. create the item and set it up */
	      {
		struct stat finfo;
		if((stat(tname, &finfo) >= 0) && S_ISDIR (finfo.st_mode))
		  tnode = (EZ_TreeNode *) createDirNode(tname);
		else tnode = (EZ_TreeNode *) createFileNode(tname);
	      }
	      node_ret = tnode;
	      if(tnode)
		{
		  EZ_TreeNode *sib;
		  EZ_Item *item = EZ_TreeNodeItem(tnode);

		  cptr = tname;
		  while( p0 < p1) { *cptr++ = *p0++;} *cptr = 0;
		  if(item) EZ_ItemID(item) = EZ_AllocCopyString(tname);
		  EZ_TreeNodeType(tnode) = EZ_TreeNodeType(cnode);
		  EZ_TreeNodeParent(tnode) = cnode;		  
		  EZ_TreeNodeIndent(tnode) = EZ_TreeNodeIndent(cnode);
		  EZ_TreeNodeSpacing(tnode) = EZ_TreeNodeSpacing(cnode);

		  sib = EZ_TreeNodeChildren(cnode);
		  if(sib == (EZ_TreeNode *)NULL) EZ_TreeNodeChildren(cnode) = tnode;
		  else
		    {
		      while(EZ_TreeNodeSibling(sib)) 
			sib = EZ_TreeNodeSibling(sib);
		      EZ_TreeNodeSibling(sib) = tnode;
		    }
		}
	      p1++; 
	      if(p1 >= end) { /* have done all */ return(node_ret); break;}
	      
	      p0 = p1; 
	      while(*p1 != '/' && p1 < end)  p1++;
	    }
	} 
      p0 = p1+1;
    }
  return(node_ret);
}

/*********************************************************************
 *
 * Open a dir node.
 */
void EZ_OpenCloseDirTreeDirectoryNodeDefault(item, data)
     EZ_Item *item;
     void *data;
{
  OpenCloseDirTreeDirectoryNodeWork(item, 0);
}
void EZ_OpenCloseDirTreeDirectoryNodeCollapse(item, data)
     EZ_Item *item;
     void *data;
{
  OpenCloseDirTreeDirectoryNodeWork(item, 1);
}

/*
 * the work
 */
static void  OpenCloseDirTreeDirectoryNodeWork(ditem, collapsing)
     EZ_Item *ditem; int collapsing;
{
  if(ditem)
    {
      char path[1024], *pathptr;
      EZ_Item *theItem = ditem;
      EZ_TreeNode *node, *snode = (EZ_TreeNode *)EZ_ItemPrivateData(ditem);
      EZ_TreeNode *children;
      EZ_Widget *widget;
      EZ_TreeNode *root = snode;
      int idx, neww, newh;
      int widgetOK = 0;
      int openIt = 1;
      int PntrGrabed = 0;
      Window win = (Window )NULL;

      if(snode == NULL) return; /* something is wrong */

      node = snode;
      children = EZ_TreeNodeChildren(node);      
      widget = (theItem == NULL ? NULL : EZ_ItemParentWidget(theItem));
      
      if(widget && EZ_LookupWidgetFromAllHT(widget) == widget)
	{
	  if(EZ_WidgetMapped(widget))
	    {
	      widgetOK = 1;
	      win = EZ_WidgetWindow(widget);
	      if( XGrabPointer(EZ_Display,
			       win,
			       True,
			       0,
			       GrabModeAsync,
			       GrabModeAsync, 
			       None,
			       EZ_WatchCursor, 
			       CurrentTime) == GrabSuccess)
		PntrGrabed = 1;
	    }
	}
      
      /* find root, record its current geometry */
      if(children)  /* destroy all descendants */
	{
	  EZ_TreeDestroyNodeDescendants(node);
	  if(theItem && EZ_ItemPixmap(theItem) == folderOBitmap) 
	    EZ_ItemPixmap(theItem) = folderCBitmap;
	  while(EZ_TreeNodeParent(root)) root = EZ_TreeNodeParent(root);
	  openIt = 0;
	}

      if(collapsing)
	{
	  EZ_TreeNode *tmp, *parent, *children, *sibling;

	  parent = EZ_TreeNodeParent(node);
	  if(parent)
	    {
	      children = EZ_TreeNodeChildren(parent);
	      if(node == children)
		{
		  sibling = EZ_TreeNodeSibling(children);
		  EZ_TreeNodeSibling(children) = NULL;
		}
	      else
		{
		  tmp = sibling = children;
		  while( EZ_TreeNodeSibling(tmp) != node)
		    tmp = EZ_TreeNodeSibling(tmp);
		  EZ_TreeNodeSibling(tmp) = EZ_TreeNodeSibling(node);
		  EZ_TreeNodeSibling(node) = NULL;
		  EZ_TreeNodeChildren(parent) = node;
		}
	      while(sibling)
		{
		  EZ_TreeNodeParent(sibling) = NULL;
		  tmp = EZ_TreeNodeSibling(sibling);
		  EZ_TreeDestroySubtree(sibling);
		  sibling = tmp;
		}
	    }
	}


      if(openIt | collapsing) /* now open the directory */
	{
	  path[1023] = 0;
	  path[1022] = '*';
	  path[1021] = '/';
	  idx = 1020;

	  if(theItem && EZ_ItemPixmap(theItem) == folderCBitmap)
	    EZ_ItemPixmap(theItem) = folderOBitmap;	  
	  while(node) 
	    {
	      EZ_Item *item;
	      char *ptr, *tstr;

	      item = EZ_TreeNodeItem(node);
	      if(item) tstr = EZ_ItemID(item);
	      else tstr = NULL;
	      /* printf("{%s}", tstr);*/
	      
	      if(tstr && *tstr)
		{
		  ptr = tstr + (strlen(tstr) -1);
		  while( ptr >= tstr) { path[idx--] = *ptr--;}
		  path[idx--] = '/';
		}
	      root = node;
	      if(item) EZ_ItemDirty(item) = 1;
	      node = EZ_TreeNodeParent(node);	  
	    }
	  pathptr = path + (idx + 1);
	  idx = 1022 - (int)(pathptr - path);
	  /* printf("[%s] %d [%s]\n",pathptr, idx, pathptr+idx); */
	  /* now open this directory and create nodes */
	  {
	    char **dirlist = (char **) EZ_GlobFileName(pathptr);
	    if(dirlist)
	      {
		int j=0;
		while(dirlist[j] != NULL) j++;
		qsort(dirlist, j, sizeof(char *), EZ_CmpStr);
		
		for(j = 0; dirlist[j] != NULL; j++)
		  {
		    (void) CompleteDirTreeBranch(snode, dirlist[j], idx);
		    (void) free((char *)dirlist[j]);
		  }
	      }
	    (void)free((char *)dirlist); 
	  }
	}
      
      if(widgetOK)
	{
	  EZ_ComputeSubtreeSize(root, &neww, &newh);
	  EZ_ClearWidgetSizeComputedFlag(widget);
	  EZ_DrawWidget(widget);
	  if(PntrGrabed) XUngrabPointer(EZ_Display, CurrentTime);
	}
    }
}

/***************************************************************************
 *
 * Default setting for creating a fileItem.
 */
void *EZ_DefaultDirTreeFileNodeCreator(filename)
     char *filename;
{
  EZ_TreeNode *node;
  EZ_Item *item;
  char *fname;
  int length, endslash = 0;

  if(dirItemProperty == NULL) InitDefaultDirTreeItemProperty();

  if(filename == (char *)NULL || *filename == 0) return((void *)NULL);
  
  length = strlen(filename);
  if(length > 1)
    {
      if(filename[length-1] == '/')
	{
	  endslash = 1;
	  filename[length-1] = 0;
	}
      fname = (char *)strrchr(filename,'/');
      if(fname == NULL)  fname = filename;
      else fname++;
    }
  else  fname = filename;

  item = EZ_CreateLabelItem(fname, fileItemProperty);
  if(item) 
    {
      node = EZ_CreateTreeNode(NULL, item);
      EZ_ItemPrivateData(item) = (void *)node;
    }
  else node = NULL;
  
  if(endslash) filename[length-1] = '/';
  return((void *)node);
}

/***************************************************************************
 *
 * Default setting for creating a directoryItem.
 */
void *EZ_DefaultDirTreeDirectoryNodeCreator(filename)
     char *filename;
{
  EZ_TreeNode *node;
  EZ_Item *item;
  char *fname;
  int length, endslash = 0;

  if(dirItemProperty == NULL) InitDefaultDirTreeItemProperty();

  if(filename == (char *)NULL || *filename == 0) return((void *)NULL);
  
  length = strlen(filename);
  if(length > 1)
    {
      if(filename[length-1] == '/')
	{
	  endslash = 1;
	  filename[length-1] = 0;
	}
      fname = (char *)strrchr(filename,'/');
      if(fname == NULL)  fname = filename;
      else fname++;
    }
  else  fname = filename;

  item = EZ_CreateLabelItem(fname, dirItemProperty);
  if(item)
    {
      node = EZ_CreateTreeNode(NULL, item);  
      EZ_ItemPrivateData(item) = (void *)node;
    }
  else node = NULL;

  if(endslash) filename[length-1] = '/';
  return((void *)node);
}

/*************************************************************/
static void InitDefaultDirTreeItemProperty()
{
  folderCBitmap = EZ_CreateLabelPixmapFromXpmData(closed_folder_xpm);
  folderOBitmap = EZ_CreateLabelPixmapFromXpmData(opened_folder_xpm);
  fileBitmap = EZ_CreateLabelPixmapFromXpmData(file_xpm);
     
  dirItemProperty = EZ_GetTextProperty(EZ_FOREGROUND, "red", EZ_LABEL_PIXMAP, folderCBitmap, 0);
  fileItemProperty = EZ_GetTextProperty( EZ_LABEL_PIXMAP, fileBitmap, 0);
}

/*************************************************************/
void  EZ_SetDirTreeOpenDirectoryMode(mode)
     int mode;
{
  if(dirItemProperty == (EZ_TextProp *)NULL)
    InitDefaultDirTreeItemProperty();
  if(mode >= EZ_DIR_NODE_OPEN_DIRECTORY_BEGIN && 
     mode <= EZ_DIR_NODE_OPEN_DIRECTORY_END)   OpenDirNodeMode = mode;
  else
    {
      (void) fprintf(stderr, "EZ_SetDirTreeOpenDirectoryMode: invalid mode %d\n", mode);
      OpenDirNodeMode = EZ_DIR_NODE_OPEN_DIRECTORY_DEFAULT;
    }
}
/*************************************************************/
char *EZ_GetDirTreeNodeFullPath(dnode)
     EZ_TreeNode *dnode;
{
  static char pathname[1024];
  if(dnode)
    {
      EZ_TreeNode *node = dnode;

      int idx=1022;
      pathname[1023] = 0;
      
      while(node) 
	{
	  EZ_Item *item;
	  char *ptr, *tstr;

	  item = EZ_TreeNodeItem(node);
	  if(item) tstr = EZ_ItemID(item);
	  else tstr = (char *)NULL;

	  if(tstr && *tstr)
	    {
	      ptr = tstr + (strlen(tstr) -1);
	      while( ptr >= tstr) { pathname[idx--] = *ptr--;}
	      pathname[idx--] = '/';
	    }
	  node = EZ_TreeNodeParent(node);	  
	}
      return(pathname+(idx+1));
    }
  return(NULL);
}
/*************************************************************/
void *(*EZ_SetDirTreeFileNodeCreator(creator))()
     void *(*creator)();
{
  void *(*oldCreator)() = DTFileNodeCreator;

  DTFileNodeCreator = creator;
  return (oldCreator);
}
/*************************************************************/
void *(*EZ_SetDirTreeDirectoryNodeCreator(creator))()
     void *(*creator)();
{
  void *(*oldCreator)() = DTDirectoryNodeCreator;

  DTDirectoryNodeCreator = creator;
  return (oldCreator);
}
/********************************************************************/
static void *createDirNode(fname)
     char *fname;
{
  EZ_TreeNode *tnode = NULL;
  if(fname && DTDirectoryNodeCreator)
    {
      tnode = (EZ_TreeNode *)DTDirectoryNodeCreator(fname);
      if(tnode)
	{
	  EZ_Item *item = EZ_TreeNodeItem(tnode); 
	  if(item)
	    {
	      switch(OpenDirNodeMode)
		{
		case EZ_DIR_NODE_OPEN_DIRECTORY_NONE:
		  break;
		case EZ_DIR_NODE_OPEN_DIRECTORY_COLLAPSING:
		  {
		    EZ_CallBackStruct **funcList =  &(EZ_ItemCallback(item));
		    EZ_RemoveCallBackWork(funcList, (EZ_CallBack)EZ_OpenCloseDirTreeDirectoryNodeDefault, NULL);
		    EZ_AddCallBackWork(funcList, (EZ_CallBack)EZ_OpenCloseDirTreeDirectoryNodeCollapse, NULL, 0, 1);
		  }
		break;
		case EZ_DIR_NODE_OPEN_DIRECTORY_DEFAULT:
		default:
		  {
		    EZ_CallBackStruct **funcList =  &(EZ_ItemCallback(item));
		    EZ_RemoveCallBackWork(funcList, (EZ_CallBack)EZ_OpenCloseDirTreeDirectoryNodeCollapse, NULL);
		    EZ_AddCallBackWork(funcList, (EZ_CallBack)EZ_OpenCloseDirTreeDirectoryNodeDefault, NULL, 0, 1);
		  }
		break;
		}
	    }
	}
    }
  return( (void *)tnode);
}
/********************************************************************/
static void *createFileNode(fname)
     char *fname;
{
  EZ_TreeNode *tnode = NULL;
  if(fname && DTFileNodeCreator)
    tnode = (EZ_TreeNode *)DTFileNodeCreator(fname);

  return((void *)tnode);
}
/********************************************************************/

void EZ_ResetGVTreeDirC()
{
  dirItemProperty = NULL;
  fileItemProperty = NULL;
  folderCBitmap = NULL;
  folderOBitmap = NULL;
  fileBitmap = NULL;
  OpenDirNodeMode = EZ_DIR_NODE_OPEN_DIRECTORY_DEFAULT;
  DTFileNodeCreator = EZ_DefaultDirTreeFileNodeCreator;
  DTDirectoryNodeCreator = EZ_DefaultDirTreeDirectoryNodeCreator;
}
/********************************************************************/
#undef _EZ_TREE_DIR_C_
