/*
 * macros.c --
 *
 * Defines and retrieves macros
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/utils/macros.c,v 1.2 2010/06/24 12:37:58 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <ctype.h>
#ifdef XLIB
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#endif

#include "utils/magic.h"
#include "utils/main.h"
#include "utils/utils.h"
#include "utils/hash.h"
#include "utils/malloc.h"
#include "utils/macros.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"

/* C99 compat */
#include "textio/textio.h"

/* Define the macro client table */

HashTable MacroClients;

/*
 *---------------------------------------------------------
 * MacroInit ---
 *
 *	Initialize the macro hash table
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Memory allocated for the hash table MacroClients.
 *
 * Note:
 *	Hash type changed from WORDKEYS to STRINGKEYS.
 *	This is slightly less efficient but allows the
 *	layout window client to be divided into separate
 *	macro clients for each tool type, accessed by
 *	name.
 *
 *---------------------------------------------------------
 */

void
MacroInit()
{
    HashInit(&MacroClients, 4, HT_STRINGKEYS);
}

/*
 *---------------------------------------------------------
 * MacroDefineByName ---
 *
 *	This procedure defines a macro.  The macro table is
 *	defined by name, not by client.  MacroDefine() is a
 *	wrapper for this routine.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The string passed is copied and considered to be the
 *	macro definition for the character.
 *---------------------------------------------------------
 */

void
MacroDefineByName(
    char *clientName,	/* window client name */
    int xc,		/* full (X11) keycode of macro with modifiers */
    char *str,		/* ...and the string to be attached to it */
    char *help,		/* ...and/or the help text for the macro */
    bool imacro)	/* is this an interactive macro? */
{
    HashEntry *h;
    HashTable *clienttable, newTable;
    macrodef *oldMacro, *newMacro;

    /* If a macro exists, delete the old string and redefine it */
    h = HashFind(&MacroClients, (char *)clientName);
    clienttable = (HashTable *)HashGetValue(h);
    if (clienttable == NULL)
    {
	clienttable = (HashTable *)mallocMagic(sizeof(HashTable));
	HashInit(clienttable, 32, HT_WORDKEYS);
	HashSetValue(h, clienttable);
    }
    h = HashFind(clienttable, (char *)INT2CD(xc));
    oldMacro = (macrodef *)HashGetValue(h);
    if (oldMacro != NULL)
    {
	if (oldMacro->macrotext != NULL)
	    freeMagic(oldMacro->macrotext);
	if (oldMacro->helptext != NULL) {
	    freeMagic(oldMacro->helptext);
	    oldMacro->helptext = NULL;
	}
	newMacro = oldMacro;
    }
    else
	newMacro = (macrodef *)mallocMagic(sizeof(macrodef));

    HashSetValue(h, newMacro);
    newMacro->interactive = imacro;
    newMacro->macrotext = StrDup((char **)NULL, str);
    if (help != NULL)
	newMacro->helptext = StrDup((char **)NULL, help);
    else
	newMacro->helptext = NULL;
}

/*
 *------------------------------------------------------------------
 * MacroDefine ---
 *
 *	This procedure is a simple wrapper for MacroDefineByName().
 *	The window client is given as a client ID.  This limits
 *	macros to one set per client type.  To allow a more
 *	flexible macro handling, if the client is a layout window
 *	(DBWclientID), then the name is instead taken from the
 *	name of the tool currently active.  This allows macros
 *	to be uniquely defined for each tool.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The string passed is copied and considered to be the
 *	macro definition for the character.
 *------------------------------------------------------------------
 */

void
MacroDefine(
    WindClient client,	/* window client type */
    int xc,		/* full (X11) keycode of macro with modifiers */
    char *str,		/* ...and the string to be attached to it */
    char *help,		/* ...and/or the help text for the macro */
    bool imacro)	/* is this an interactive macro? */
{
    char *clientName = NULL;

    if (client == DBWclientID)
	clientName = DBWGetButtonHandler();
    if (clientName == NULL)
	clientName = WindGetClientName(client);

    MacroDefineByName(clientName, xc, str, help, imacro);
}

/*
 *---------------------------------------------------------
 * MacroDefineHelp ---
 *
 *	This procedure defines the help text for a macro.
 *	A macro must already exist for the specified key.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The string passed is copied and considered to be the
 *	macro definition for the character.
 *---------------------------------------------------------
 */

void
MacroDefineHelp(
    WindClient client,	/* window client type */
    int xc,		/* full (X11) keycode of macro with modifiers */
    char *help)		/* ...and/or the help text for the macro */
{
    HashEntry *h;
    HashTable *clienttable;
    macrodef *curMacro;
    char *clientName = NULL;

    if (client == DBWclientID)
	clientName = DBWGetButtonHandler();
    if (clientName == NULL)
	clientName = WindGetClientName(client);

    /* If a macro exists, delete the old string and redefine it */
    h = HashFind(&MacroClients, (char *)clientName);
    clienttable = (HashTable *)HashGetValue(h);
    if (clienttable == NULL) return;

    h = HashFind(clienttable, (char *)INT2CD(xc));
    curMacro = (macrodef *)HashGetValue(h);
    if (curMacro == NULL) return;

    if (curMacro->helptext != NULL)
	freeMagic(curMacro->helptext);

    if (help == NULL)
	curMacro->helptext = NULL;
    else
	curMacro->helptext = StrDup((char **)NULL, help);
}

/*---------------------------------------------------------
 * MacroRetrieve:
 *	This procedure retrieves a macro.
 *
 * Results:
 *	A pointer to a new Malloc'ed string is returned.
 *	This structure should be freed when the caller is
 *	done with it.
 *
 * Side Effects:
 *	None.
 *---------------------------------------------------------
 */

char *
MacroRetrieve(
    WindClient client,		/* window client type */
    int xc,			/* the extended name of the macro */
    bool *iReturn)		/* TRUE if macro is interactive */
{
    HashEntry *h;
    HashTable *clienttable;
    macrodef *cMacro;
    char *clientName = NULL;

    if (client == DBWclientID)
	clientName = DBWGetButtonHandler();
    if (clientName == NULL)
	clientName = WindGetClientName(client);
    if (clientName == NULL)
	return NULL;

    /* If a macro exists, delete the old string and redefine it */
    h = HashLookOnly(&MacroClients, (char *)clientName);
    if (h != NULL)
    {
	clienttable = (HashTable *)HashGetValue(h);
	if (clienttable != NULL)
	{
	    h = HashLookOnly(clienttable, (char *)INT2CD(xc));
	    if (h != NULL)
	    {
		cMacro = (macrodef *)HashGetValue(h);
		if (cMacro != NULL)
		{
		    if (iReturn != NULL)
			*iReturn = cMacro->interactive;
		    return StrDup((char **)NULL, cMacro->macrotext);
		}
	    }
	}
    }
    if (iReturn != NULL) *iReturn = FALSE;
    return (char *)NULL;
}

/*---------------------------------------------------------
 * MacroRetrieveHelp:
 *	This procedure retrieves the help text for a macro.
 *
 * Results:
 *	A pointer to a new Malloc'ed string is returned.
 *	This structure should be freed when the caller is
 *	done with it.
 *
 * Side Effects:
 *	None.
 *---------------------------------------------------------
 */

char *
MacroRetrieveHelp(
    WindClient client,		/* window client type */
    int xc)			/* the extended name of the macro */
{
    HashEntry *h;
    HashTable *clienttable;
    macrodef *cMacro;
    char *clientName = NULL;

    if (client == DBWclientID)
	clientName = DBWGetButtonHandler();
    if (clientName == NULL)
	clientName = WindGetClientName(client);

    /* If a macro exists, delete the old string and redefine it */
    h = HashLookOnly(&MacroClients, (char *)clientName);
    if (h != NULL)
    {
	clienttable = (HashTable *)HashGetValue(h);
	if (clienttable != NULL)
	{
	    h = HashLookOnly(clienttable, (char *)INT2CD(xc));
	    if (h != NULL)
	    {
		cMacro = (macrodef *)HashGetValue(h);
		if (cMacro != NULL)
		    if (cMacro->helptext != NULL)
			return StrDup((char **)NULL, cMacro->helptext);
	    }
	}
    }
    return (char *)NULL;
}

/*---------------------------------------------------------
 * MacroSubsitute:
 *	Make a substitution of one string for another
 *	in a macro string.  This is really just a string
 *	manipulation;  it doesn't have anything specifically
 *	to do with macros.
 *
 * Results:
 *	A new string pointer to the macro contents.
 *
 * Side Effects:
 *	None.
 *---------------------------------------------------------
 */

char *
MacroSubstitute(
    char *macrostr,
    char *searchstr,
    char *replacestr)
{
    char *found, *last, *new;
    int expand, length, oldlength, srchsize;

    if (macrostr == (char *)NULL) return NULL;

    oldlength = strlen(macrostr);
    srchsize = strlen(searchstr);
    expand = strlen(replacestr) - srchsize;
    last = macrostr;
    length = oldlength;
    while ((found = strstr(last, searchstr)) != NULL)
    {
	length += expand;
	last = found + srchsize;
    }
    if (length > oldlength)
    {
	new = (char *)mallocMagic(length + 1);
	*new = '\0';
	last = macrostr;
	while ((found = strstr(last, searchstr)) != NULL)
	{
	   *found = '\0';
	   strcat(new, last);
	   strcat(new, replacestr);
	   last = found + srchsize;
	}
	strcat(new, last);
	freeMagic(macrostr);
	return new;
    }
    else
	return macrostr;
}


/*---------------------------------------------------------
 * MacroDelete:
 *	This procedure deletes a macro.
 *
 * Results:	None.
 *
 * Side Effects:
 *	The string that defines a macro is deleted.  This means
 *	that if anybody still has a pointer to that string
 *	they are in trouble.
 *---------------------------------------------------------
 */

void
MacroDelete(
    WindClient client,	/* window client type */
    int xc)		/* the extended name of the macro */
{
    HashEntry *h;
    HashTable *clienttable;
    macrodef *cMacro;
    char *clientName = NULL;

    if (client == DBWclientID)
	clientName = DBWGetButtonHandler();
    if (clientName == NULL)
	clientName = WindGetClientName(client);

    h = HashLookOnly(&MacroClients, (char *)clientName);
    if (h != NULL)
    {
	clienttable = (HashTable *)HashGetValue(h);
	if (clienttable != NULL)
	{
	    h = HashLookOnly(clienttable, (char *)INT2CD(xc));
	    if (h != NULL)
	    {
		cMacro = (macrodef *)HashGetValue(h);
		if (cMacro != NULL)
		{
		    if (cMacro->macrotext != NULL)
			freeMagic(cMacro->macrotext);
		    if (cMacro->helptext != NULL)
			freeMagic(cMacro->helptext);
		    HashSetValue(h, NULL);
		    freeMagic(cMacro);
		}
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 * MacroCopy --
 *	Copy all macros from one table to another.
 *	This is used with the "tool" command to simplify the process
 *	of defining new macro sets for button and keypress actions
 *	for a new tool type.  It copies all of the macros from the
 *	table for the client passed as (WindClient type) "client" to
 *	the table for the client named by (string) "clientkey".
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	New hash entries are created.
 * ----------------------------------------------------------------------------
 */

void
MacroCopy(
    WindClient client,	/* Current window client */
    char *clientkey)	/* Name of client to copy macros to */
{
    HashTable *clienttable;
    HashTable *copytable;
    HashEntry *h, *he;
    HashSearch hs;
    char *clientName;
    int cKey;
    macrodef *cMacro;

    if (clientkey == NULL) return;
    if (client == (WindClient)NULL) return;

    if (client == DBWclientID)
        clientName = DBWGetButtonHandler();
    if (clientName == NULL)
        clientName = WindGetClientName(client);

    h = HashLookOnly(&MacroClients, (char *)clientName);
    if (h == NULL) return;	/* No clients, nothing can be done */

    clienttable = (HashTable *)HashGetValue(h);
    if (clienttable == NULL) return;	/* Also nothing can be done */

    HashStartSearch(&hs);
    while (TRUE)
    {
	he = HashNext(clienttable, &hs);
	if (he == NULL) break;
	cMacro = (macrodef *)HashGetValue(he);
	cKey = (int)CD2INT(he->h_key.h_ptr);
	MacroDefineByName(clientkey, cKey, cMacro->macrotext,
			cMacro->helptext, cMacro->interactive);
    }
}

/*
 * ----------------------------------------------------------------------------
 * MacroName --
 * 	Convert an extended keycode to a string.
 *
 * Results:
 *	The string.
 *
 * Side effects:
 *	The string is malloc'ed from memory, and must be free'd later.
 * ----------------------------------------------------------------------------
 */

char *
MacroName(
    int xc)
{
    char *vis;
    static char hex[17] = "0123456789ABCDEF";

#ifdef XLIB
    char *str;
    extern Display *grXdpy;
    KeySym ks = xc & 0xffff;
    int kmod = xc >> 16;

    str = NULL;
    if (grXdpy != NULL) {
        ks = xc & 0xffff;
	if (ks != NoSymbol) str = XKeysymToString(ks);
    }
    if (str != NULL)
    {
	vis = (char *) mallocMagic( sizeof(char) * (strlen(str) + 32) );
	vis[0] = '\0';
	if (kmod & Mod1Mask) strcat(vis, "Meta_");
	if (kmod & ControlMask) strcat(vis, "Control_");
	if (kmod & LockMask) strcat(vis, "Capslock_");
	if (kmod & ShiftMask) strcat(vis, "Shift_");
	strcat(vis, "XK_");
	strcat(vis, str);
	return (vis);
    }
#endif

    vis = (char *) mallocMagic( sizeof(char) * 6 );
    if (xc < (int)' ')
    {
	vis[0] = '^';
	vis[1] = (char)xc + '@';
	vis[2] = '\0';
    }
    else if (xc == 0x7F)
    {
	vis[0] = '<';
	vis[1] = 'd';
	vis[2] = 'e';
	vis[3] = 'l';
	vis[4] = '>';
	vis[5] = '\0';
    }
    else if (xc < 0x80)
    {
	vis[0] = (char)xc;
	vis[1] = '\0';
    }
    else
    {
	vis = (char *) mallocMagic( sizeof(char) * 8 );
	vis[0] = '0';
	vis[1] = 'x';
#ifdef XLIB
	vis[2] = hex[ (kmod & 0xf)];
#else
	vis[2] = '0';
#endif
	vis[3] = hex[ (xc & 0x0f000) >> 12];
	vis[4] = hex[ (xc & 0x00f00) >>  8];
	vis[5] = hex[ (xc & 0x000f0) >>  4];
	vis[6] = hex[ (xc & 0x0000f)      ];
	vis[7] = '\0';
    }
    return(vis);
}


/*
 * ----------------------------------------------------------------------------
 * MacroKey:
 * 	Convert a string to an extended keycode.
 *
 * Results:
 *	An extended macro name.
 *
 * Side effects:
 *	If the display being used is not X11, a warning is printed the
 *	first time this function is called.
 * ----------------------------------------------------------------------------
 */

int
MacroKey(
    char *str,
    int *verbose)
{
    static int warn = 1;

#ifdef XLIB
    int kc;
    char *vis;
    extern Display *grXdpy;
    KeySym ks;
    int kmod = 0;

    *verbose = 1;
    if (grXdpy != NULL)
    {
	vis = str;
	while( (*vis) != '\0' )
	{
	    if (!strncmp(vis, "Meta_", 5))
	    {
		kmod |= Mod1Mask;
		vis += 5;
	    }
	    else if (!strncmp(vis, "Alt_", 4))
	    {
		kmod |= Mod1Mask;
		vis += 4;
	    }
	    else if (!strncmp(vis, "Control_", 8))
	    {
		kmod |= ControlMask;
		vis += 8;
	    }
	    else if (((*vis) == '^') && (*(vis + 1) != '\0'))
	    {
		kmod |= ControlMask;
		vis++;
	    }
	    else if (!strncmp(vis, "Capslock_", 9))
	    {
		kmod |= LockMask;
		vis += 9;
	    }
	    else if (!strncmp(vis, "Shift_", 6))
	    {
		kmod |= ShiftMask;
		vis += 6;
	    }
	    else if (*vis == '\'')
	    {
		// If single quotes are used to protect the integrity
		// of the macro character, strip them now.

		char *aptr = strrchr(vis, '\'');
		if (aptr != NULL && aptr != vis)
		{
		   vis++;
		   *aptr = '\0';
		}
		else break;	// Don't hang on an unmatched quote
	    }
	    else break;
	}
	if (!strncmp(vis, "XK_", 3)) vis += 3;

	/* We're converting all ASCII back into X Keycodes 	*/
	/* The original macro (.magicrc) file format allows	*/
	/* embedded control characters, so we have to handle	*/
	/* those.  For regular ASCII characters, the keysym =	*/
	/* the ASCII value.					*/

	if ((*(vis + 1)) == '\0')
	{
	    /* single ASCII character handling */

	    char tc = *vis;

	    /* Revert Control and Shift characters to ASCII	*/
	    /* unless other modifiers are present. Always make	*/
	    /* characters with Control and Shift uppercase.	*/

	    if (kmod & (ControlMask | ShiftMask))
	    {
		tc = toupper(tc);
		if (kmod & ShiftMask)
		    kc = (int)tc;
		else if (kmod & ControlMask)
		    kc = (int)(tc - 'A' + 1);
		else
		    kc = (int)tc;

		if (!(kmod & (Mod1Mask | LockMask)))
		    if (!(kmod & ShiftMask) || !(kmod & ControlMask))
		       kmod = 0;
	    }
	    else
		kc = (int)tc;
	}
	else if (!strncmp(vis, "<del>", 5))
	    /* Because, weirdly, there is no keysymdef for the "Delete" key */
	    kc = (int)0x7f;
	else
	{
	    /* X11 keysym name handling */

	    /* If macro "Button" is used, then prepend	*/
	    /* "Pointer" to match the keysymdef.	*/
	    /* (Added by NP 10/20/04)			*/

	    char *pointerStr = NULL;
	    if (!strncmp(vis, "Button", 6))
	    {
		pointerStr = (char *)mallocMagic(9 + strlen(str));
		strcpy(pointerStr, "Pointer_");
		strcat(pointerStr, vis);
		vis = pointerStr;
	    }
	    ks = XStringToKeysym(vis);
	    kc = (ks != NoSymbol) ? ks & 0xffff : 0;
	    if (pointerStr != NULL) freeMagic(pointerStr);
	}
	return (kc | (kmod << 16));
    }
#endif

    *verbose = 1;
    if (strlen(str) == 1)
    {
	return (int)str[0];
    }
    else if (strlen(str) == 2 && *str == '^')
    {
	return (int)str[1] - 'A' + 1;
    }
    if (warn)
    {
	if (strcasecmp(MainDisplayType, "NULL") || (TxTkConsole == TRUE))
	    TxPrintf("Extended macros are unavailable"
			" with graphics type \"%s\".\n", MainDisplayType);
    }
    warn = 0;
    *verbose = 0;
    return(0);
}


/*
 * ----------------------------------------------------------------------------
 * CommandLineTranslate
 * 	Convert X11 definitions to readline controls
 *
 * Results:
 *	A converted character (integer)
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

int
TranslateChar(
    int key)
{
    int rval = key;

#ifdef XLIB
    switch (key)
    {
	case XK_Left:
	    rval = (int)'\002';	/* Ctrl-B */
	    break;
	case XK_Right:
	    rval = (int)'\006';	/* Ctrl-F */
	    break;
	case XK_Up:
	    rval = (int)'\020';	/* Ctrl-P */
	    break;
	case XK_Down:
	    rval = (int)'\016';	/* Ctrl-N */
	    break;
	case XK_BackSpace:
	case XK_Delete:
	    rval = (int)'\010';	/* Ctrl-H */
	    break;
	case XK_Home:
	    rval = (int)'\001';	/* Ctrl-A */
	    break;
	case XK_End:
	    rval = (int)'\005';	/* Ctrl-E */
	    break;
    }
#endif
    return rval;
}
