/*This line lets emacs recognize this as -*- C -*- Code
 ******************************************************************************
 *  Project:  Tcl Modules
 *  Created:  Oct 18 1992
 *   Author:  Leif Hedstrom<leif@ifm.liu.se>
 * --------
 *
 * DESCRIPTION:
 *    Module command to merge/remove resources from the X11 resource
 *    manager. The database is update internally, ie. its not done at
 *    evaluation of string modulecmd returns. It will do something like
 *    "xrdb -merge"  using the default display ($DISPLAY).
 *
 * NOTE:
 *    Fragments of this code are from the original xrdb source, Copyright
 *    1987 & 1991 by DIGITAL EQUIPMENT CORPORATION. Xrdb was written and
 *    modified by:
 *	 Jim Gettys, August 28, 1987
 *	 Phil Karlton, January 5, 1987
 *	 Bob Scheifler, February, 1991
 *
 * TODO/BUGS:
 *    + The command only handles screen independant resources.
 *
 * $Log: cmdXResource.c,v $
 *
 *
 * Revision 1.5  1993/01/21  02:47:40  jlf
 * Intermixed ifdef's more so that only the stub is called when X11LIBS is
 * not defined.
 *
 * Revision 1.4  1993/01/21  01:50:14  jlf
 * ifdef'd all of the source code based on whether the X11 libraries are
 * available.  If they're not, then a stub is left in place.
 *
 * Revision 1.3  1993/01/21  00:38:59  jlf
 * Fixed nasty bug in storeResProp.
 *
 * Revision 1.2  1992/11/18  23:01:11  jlf
 * Changed to use the different #defines that Configure provides
 * for the cpp name and added a stub for when there are no X11 libraries.
 *
 * Revision 1.1  1992/11/05  23:41:36  jlf
 * Initial revision
 *
 *****************************************************************************/
static char Id[] =
    "$Id: cmdXResource.c,v 2.0 1993/02/21 00:01:05 jlf Exp jlf $";

/*
 *  Included files and defines, structures, typedefs and enums.
 */
#include "global.h"

#ifdef HAS_X11LIBS
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xmu/SysUtil.h>
#endif

#include <unistd.h>
#include <regexp.h>

typedef int ErrType;
enum _errMsgId { NO_ERROR, ERR_DISPLAY, ERR_MALLOC, ERR_EXTRACT, ERR_PARSE };

#ifndef Bool
   typedef int Bool;
#endif
enum _boolean  { FALSE, TRUE };

#ifdef HAS_X11LIBS
#define MAXHOSTNAME	 255
#define INIT_BUF_SIZE	 5000
#define Resolution(pixels, mm)	(((pixels * 100000 / mm) + 50) / 100)

typedef struct _Buffer {
  char *buff;
  int  room, used;
} Buffer;

typedef struct _ResourceDB {
  Tcl_HashTable *data;
  Window root;
  Atom prop;
} ResourceDB;



/*
 *  Global (static) variables.
 */
static Display *dpy	  =   NULL;
static char *defines	  =   NULL;
static int def_base	  =   0;
static Buffer buffer	  = { NULL, 0, 0 };
static ResourceDB resDB	  = { NULL, (Window) 0, (Atom) 0 };


/*
 *  Here are some routines that adds DEFINES to the define buffer.
 *  This code is mainly the same as in the original xrdb.c
 */
void addDef(char *title, char *value)
{
  register Bool quote;

  strcat(defines, " -D");
  strcat(defines, title);
  if (value && *value) {
    quote = (value && strchr(value,' '));
    strcat(defines, (quote ? "=\"" : "="));
    strcat(defines, value);
    if (quote)
      strcat(defines,"\"");
  }
}

void addNum(char *title, int value)
{
    char num[20];

    sprintf(num, "%d", value);
    addDef(title, num);
}

void doDisplayDefines()
{
  char client[MAXHOSTNAME], server[MAXHOSTNAME], *colon;

  XmuGetHostname(client, MAXHOSTNAME);
  strcpy(server, XDisplayName(NULL));
  if (colon = strchr(server, ':'))
    *colon = '\0';
  if (!*server)
    strcpy(server, client);
  addDef("HOST", server);
  addDef("SERVERHOST", server);
  addDef("CLIENTHOST", client);
  addNum("VERSION", ProtocolVersion(dpy));
  addNum("REVISION", ProtocolRevision(dpy));
  addDef("VENDOR", ServerVendor(dpy));
  addNum("RELEASE", VendorRelease(dpy));
}

void doScreenDefines(int scrno)
{
  register Screen *screen;
  register Visual *visual;
    
  screen = ScreenOfDisplay(dpy, scrno);
  visual = DefaultVisualOfScreen(screen);
  addNum("WIDTH", screen->width);
  addNum("HEIGHT", screen->height);
  addNum("X_RESOLUTION", Resolution(screen->width,screen->mwidth));
  addNum("Y_RESOLUTION", Resolution(screen->height,screen->mheight));
  addNum("PLANES", DisplayPlanes(dpy, scrno));
  addNum("BITS_PER_RGB", visual->bits_per_rgb);
  switch(visual->class) {
  case StaticGray:
    addDef("CLASS", "StaticGray");
    break;
  case GrayScale:
    addDef("CLASS", "GrayScale");
    break;
  case StaticColor:
    addDef("CLASS", "StaticColor");
    addDef("COLOR", NULL);
    break;
  case PseudoColor:
    addDef("CLASS", "PseudoColor");
    addDef("COLOR", NULL);
    break;
  case TrueColor:
    addDef("CLASS", "TrueColor");
    addDef("COLOR", NULL);
    break;
  case DirectColor:
    addDef("CLASS", "DirectColor");
    addDef("COLOR", NULL);
    break;
  }
}


/*
 *  This routine will append a string to a buffer. This is used when
 *  we will update the internal X11 resource propery. If the buffer is
 *  filled up, we will reallocate some more room.
 */
void appendToBuf(char *str, char *delim, register int dlen)
{
  register int len = strlen(str);

  while (buffer.used + (len + dlen) > buffer.room) {
    buffer.room *= 2;
    buffer.buff = (char *)realloc(buffer.buff, buffer.room*(sizeof(char)));
  }
  strncpy(buffer.buff + buffer.used, str, len);
  if (delim)
    strncpy(buffer.buff + buffer.used + len, delim, dlen);
  buffer.used += (len + dlen);
}


/*
 *  Read resource from a file, which normally is a pipe opened with popen.
 */
void readFile(register FILE *input, Bool do_cpp)
{
  register int bytes;

  while (!feof(input) &&
	 (bytes = fread(buffer.buff, 1, buffer.room - buffer.used, input)))
    if ((buffer.used += bytes) >= buffer.room) {
      buffer.room *= 2;
      buffer.buff = (char *)realloc(buffer.buff, buffer.room*(sizeof(char)));
    }
  buffer.buff[buffer.used] = '\0';
  do_cpp ? pclose(input) : fclose(input);
}


/*
 *  The next routine are used to manipulate the resource database that we
 *  work with INTERNALLY in this module. I think the regular expressions
 *  could be extended to handle the multi-line resources, but this also
 *  works as intended. Using the "|" oper. in a regexp could make it slower!
 */
ErrType getEntries(Tcl_HashTable *data, register char *buf, Bool remove)
{
  static regexp *res_exp = NULL;
  register Tcl_HashEntry *entry;
  char *end;
  int new_res;

  if (!res_exp)
    res_exp  = regcomp("[ \t]*([^ \t]*)[ \t]*:[ \t]*(.*)[ \t]*");
  for (end = buf; *end; end++)
    if (*end == '\\' && *(end+1) == '\n')
      end++;
    else if (*end == '\n')
      *end = '\0';
  while (buf <= end)
    if (*buf == '#' || *buf == '!' || !*buf)
      while (*buf++) ;
    else if (!regexec(res_exp,buf))
      return ERR_PARSE;
    else {
      buf = *(res_exp->endp) + 1;
      *(res_exp->endp[1]) = '\0';
      *(res_exp->endp[2]) = '\0';
      if (remove) {
	if (entry = Tcl_FindHashEntry(data, res_exp->startp[1])) {
	  free(Tcl_GetHashValue(entry));
	  Tcl_DeleteHashEntry(entry);
	}
      } else {
	entry=Tcl_CreateHashEntry(data, res_exp->startp[1], &new_res);
	if (!new_res)
	  free(Tcl_GetHashValue(entry));
	Tcl_SetHashValue(entry, strdup(res_exp->startp[2]));
      }
    }
  return NO_ERROR;
}


/*
 *  Finally, we can now update the X11 resource property, adding
 *  new resources.
 */
void storeResProp(register ResourceDB *rdb)
{
  Tcl_HashSearch search;
  register int mode = PropModeReplace;
  register int max = (XMaxRequestSize(dpy) << 2) - 28;
  register Tcl_HashEntry *entry = Tcl_FirstHashEntry(rdb->data, &search);
  char *buf = buffer.buff;

  buffer.used = 0;
  while (entry) {
    appendToBuf(Tcl_GetHashKey(rdb->data, entry), ":\t", 2);
    appendToBuf((char *)Tcl_GetHashValue(entry),"\n", 1);
    entry = Tcl_NextHashEntry(&search);
  }
  if (buffer.used > max) {
    XGrabServer(dpy);
    do {
      XChangeProperty(dpy, rdb->root, rdb->prop, XA_STRING, 8, mode, buf, max);
      buf += max;
      mode = PropModeAppend;
    } while ((buffer.used -= max) > max);
  }
  XChangeProperty(dpy, rdb->root, rdb->prop, XA_STRING, 8, mode,buf,
		  buffer.used);
  if (mode != PropModeReplace)
    XUngrabServer(dpy);
}


/*
 *  First, we have to find the resources already loaded into the X11
 *  resource property. This routine currently only handles one screen,
 *  the default screen for the DISPLAY. This routine should only be
 *  called if resDB.data is NULL.
 */
ErrType getOld(register char **buf)
{
  if (!(resDB.data = (Tcl_HashTable *)malloc(sizeof(Tcl_HashTable))))
    return ERR_MALLOC;
  Tcl_InitHashTable(resDB.data, TCL_STRING_KEYS);
  resDB.root = RootWindow(dpy, 0);
  resDB.prop = XA_RESOURCE_MANAGER;
  *buf = XResourceManagerString(dpy);
  return NO_ERROR;
}


/*
 *  Initilize buffers if not already done, or reinitialize some variables
 *  if buffers already exists.
 */
ErrType initBuffers(register Bool is_file, char	 **xres)
{
  if (!dpy && !(dpy = XOpenDisplay(NULL)))
    return ERR_DISPLAY;
  if (!resDB.data) {
    if (getOld(xres))
      return ERR_MALLOC;
    if (getEntries(resDB.data, *xres, FALSE))
      return ERR_EXTRACT;
  }
  if (!buffer.buff) {
    if (!(buffer.buff = malloc(INIT_BUF_SIZE*sizeof(char))))
      return ERR_MALLOC;
    buffer.room = INIT_BUF_SIZE;
  }
  buffer.used = 0;
  if (defines)
    defines[def_base] = '\0';
  else if (is_file) {
    if (!(defines = malloc(BUFSIZ*sizeof(char))))
	return ERR_MALLOC;
    sprintf(defines, "%s %s ", CPPSTDIN, CPPMINUS);
    doDisplayDefines();
    doScreenDefines(DefaultScreen(dpy));
    def_base = strlen(strcat(defines, " "));
  }
  return NO_ERROR;
}


/*
 *  Update the resource property if everything is ok. This routine should
 *  be called when all properies have been defines or updated. Remember
 *  that this routine always will be called, even if there was no
 *  "x-resource" command in the module!
 */
void xresourceFinish(register Bool no_errors)
{
  if (resDB.data && no_errors)
    storeResProp(&resDB);
  if (dpy)
    XCloseDisplay(dpy);
}
#endif

/*
 *  This is the main entry point.
 */
int cmdXResource(ClientData client_data,
		Tcl_Interp *interp,
		int argc,
		char *argv[])
{
  register FILE *inp;
  Bool is_file, do_cpp = TRUE;
  int opt_ind = 1;
  char *xres = NULL;
  ErrType err;
  static const char *errMsg[] = { NULL,
				  "no DISPLAY defined ",
				  "malloc() failed in cmdXResource.c ",
				  "could not extract existing resources ",
				  "could not parse: " };

  if (argc > 1 && !strcmp(argv[1], "-nocpp")) {
    do_cpp = FALSE;
    opt_ind++;
  }
  if(argc <= opt_ind) {
    Tcl_AppendResult(interp, "wrong # args: should be \"", *argv,
		     " [ -nocpp ] strings\"", NULL);
    return TCL_ERROR;
  }
  while (opt_ind < argc) {
    is_file = (access(argv[opt_ind], R_OK & F_OK) == 0);
#ifdef HAS_X11LIBS
    if(flags & M_DISPLAY)
      fprintf(stderr, (is_file ? "Read X11 resources from '%s'\n" : 
		       "Merge the X11 resource '%s'\n"), argv[opt_ind]);
    else {
      if (err = initBuffers(is_file, &xres)) {
	Tcl_AppendResult(interp, errMsg[err], NULL);
	return TCL_ERROR;
      }
      if (!is_file)
	strcpy(buffer.buff, argv[opt_ind]);
      else {
	inp = (do_cpp ? popen(strcat(defines, argv[opt_ind]), "r") : 
	       fopen(argv[opt_ind], "r"));
	readFile(inp, do_cpp);
      }
      if (err = getEntries(resDB.data, buffer.buff, flags & M_REMOVE)) {
	Tcl_AppendResult(interp, errMsg[err], argv[opt_ind], NULL);
	return TCL_ERROR;
      }
    }
#else
    if(flags & M_DISPLAY)
      fprintf(stderr, (is_file ? "Ignore X11 resources from '%s'\n" : 
		       "Ignore the X11 resource '%s'\n"), argv[opt_ind]);
#endif
   opt_ind++; 
  }
  return TCL_OK;
}

