/* 
 *  WindowMaker window manager
 * 
 *  Copyright (c) 1997 Alfredo K. Kojima
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include "wconfig.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <math.h>

#include <wraster.h>

#include "generic/wwmlib.h"
#include "WindowMaker.h"
#include "GNUstep.h"
#include "screen.h"
#include "wcore.h"
#include "window.h"
#include "funcs.h"
#include "defaults.h"
#include "dialog.h"

/**** global variables *****/

extern char *DisplayName;

extern WPreferences wPreferences;

extern Time LastTimestamp;



#ifdef OFFIX_DND
extern Atom _XA_DND_SELECTION;
#endif


#ifdef USECPP
static void
putdef(char *line, char *name, char *value)
{
    if (!value) {
	wWarning(_("could not define value for %s for cpp"), name);
	return;
    }
    strcat(line, name);
    strcat(line, value);
}



static void
putidef(char *line, char *name, int value)
{
    char tmp[64];
    sprintf(tmp, "%i", value);
    strcat(line, name);
    strcat(line, tmp);
}


static char*
username()
{
    char *tmp;
    
    tmp = getlogin();
    if (!tmp) {
	struct passwd *user;

	user = getpwuid(getuid());
	if (!user) {
	    wSysError(_("could not get password entry for UID %i"), getuid());
	    return NULL;
	}
	if (!user->pw_name) {
	    return NULL;
	} else {
	    return user->pw_name;
	}
    }
    return tmp;
}
       
char *
MakeCPPArgs(char *path)
{
    int i;
    char buffer[MAXLINE], *buf, *line;
    Visual *visual;
    
    line = wmalloc(MAXLINE);
    *line = 0;
    i=1;
    if ((buf=getenv("HOSTNAME"))!=NULL) {
	if (buf[0]=='(') {
	    wWarning(_("your machine is misconfigured. HOSTNAME is set to %s"),
		     buf);
	} else 
	  putdef(line, " -DHOST=", buf);
    } else if ((buf=getenv("HOST"))!=NULL) {
	if (buf[0]=='(') {
	    wWarning(_("your machine is misconfigured. HOST is set to %s"),
		     buf);
	} else 
	  putdef(line, " -DHOST=", buf);
    }
    buf = username();
    if (buf)
      putdef(line, " -DUSER=", buf);
    putidef(line, " -DUID=", getuid());
    buf = XDisplayName(DisplayString(dpy));
    putdef(line, " -DDISPLAY=", buf);
    putdef(line, " -DWM_VERSION=", VERSION);
    
    visual = DefaultVisual(dpy, DefaultScreen(dpy));
    putidef(line, " -DVISUAL=", visual->class);
    
    putidef(line, " -DDEPTH=", DefaultDepth(dpy, DefaultScreen(dpy)));

    putidef(line, " -DSCR_WIDTH=", WidthOfScreen(DefaultScreenOfDisplay(dpy)));
    putidef(line, " -DSCR_HEIGHT=", 
	    HeightOfScreen(DefaultScreenOfDisplay(dpy)));

    #if 0
    strcpy(buffer, path);    
    buf = strrchr(buffer, '/');
    if (buf) *buf = 0; /* trunc filename */
    putdef(line, " -I", buffer);
    #endif



    /* this should be done just once, but it works this way */
    strcpy(buffer, DEF_CONFIG_PATHS);
    buf = strtok(buffer, ":");

    do {
      char fullpath[MAXLINE];

      if (buf[0]!='~') {
	strcpy(fullpath, buf);
      } else {
	char * gethomedir();
	/* home is statically allocated. Don't free it! */
	char *home = gethomedir();
	
	strcpy(fullpath, home);
	strcat(fullpath, &(buf[1]));
      }

      putdef(line, " -I", fullpath);

    } while ((buf = strtok(NULL, ":"))!=NULL);
    
#undef arg
#ifdef DEBUG
    puts("CPP ARGS");
    puts(line);
#endif
    return line;
}
#endif /* USECPP */


WWindow*
NextFocusWindow(WScreen *scr)
{
    WWindow *tmp, *wwin, *closest, *min;
    Window d;
    
    wwin = scr->focused_window;
    tmp = wwin->prev;
    closest = NULL;
    min = wwin;
    d = 0xffffffff;
    while (tmp) {
	if ((tmp->flags.mapped||(tmp->flags.shaded&&!tmp->flags.miniaturized))
	    && tmp->window_flags.focusable) {
	    if (min->client_win > tmp->client_win)
	      min = tmp;
	    if (tmp->client_win > wwin->client_win
		&& (!closest
		    || (tmp->client_win - wwin->client_win) < d)) {
		closest = tmp;
		d = tmp->client_win - wwin->client_win;
	    }
	}
	tmp = tmp->prev;
    }
    if (!closest||closest==wwin)
      return min;
    return closest;
}


WWindow*
PrevFocusWindow(WScreen *scr)
{
    WWindow *tmp, *wwin, *closest, *max;
    Window d;
    
    wwin = scr->focused_window;
    tmp = wwin->prev;
    closest = NULL;
    max = wwin;
    d = 0xffffffff;
    while (tmp) {
	if ((tmp->flags.mapped||(tmp->flags.shaded&&!tmp->flags.miniaturized))
	    && tmp->window_flags.focusable) {
	    if (max->client_win < tmp->client_win)
	      max = tmp;
	    if (tmp->client_win < wwin->client_win
		&& (!closest
		    || (wwin->client_win - tmp->client_win) < d)) {
		closest = tmp;
		d = wwin->client_win - tmp->client_win;
	    }
	}
	tmp = tmp->prev;
    }
    if (!closest||closest==wwin)
      return max;
    return closest;
}

#if 1
/*
 *  XFetchName Wrapper
 *
 */

Status MyXFetchName(dpy, win, winname)
Display *dpy;
Window win;
char **winname;
{
    XTextProperty text_prop;
    char **list;
    int num;

	if (XGetWMName(dpy, win, &text_prop) != 0 && text_prop.value) {
		if (text_prop.encoding == XA_STRING)
			*winname = (char *)text_prop.value;
		else {
			text_prop.nitems = strlen((char *)text_prop.value);
			if (XmbTextPropertyToTextList(dpy, &text_prop, &list, &num) >=
				Success && *list && num > 0) {
				XFree(text_prop.value);
				*winname = strdup(*list);
				XFreeStringList(list);
			} else
				*winname = (char *)text_prop.value;
		}
		return 1;
    }
	*winname = NULL;
    return 0;  
}

/*
 *  XGetIconName Wrapper
 *
 */

Status MyXGetIconName(dpy, win, iconname)
Display *dpy;
Window win;
char **iconname;
{
    XTextProperty text_prop;
    char **list;
    int num;
    
	if (XGetWMIconName(dpy, win, &text_prop) != 0 && text_prop.value) {
		if (text_prop.encoding == XA_STRING)
			*iconname = (char *)text_prop.value;
		else {
			text_prop.nitems = strlen((char *)text_prop.value);
			if (XmbTextPropertyToTextList(dpy, &text_prop, &list, &num) >=
				Success && *list && num > 0) {
				XFree(text_prop.value);
				*iconname = strdup(*list);
				XFreeStringList(list);
			} else
				*iconname = (char *)text_prop.value;
		}
		return 1;
    }
	*iconname = NULL;
    return 0;
}
    
    
#if 0
    Atom actual_type;
    int actual_format;
    unsigned long nitems, bytesafter;
    char **list;
    int num;
    XTextProperty text_prop;
    Status status_ret;

    if (XGetWindowProperty(dpy, win, XA_WM_ICON_NAME, 0L, 200L, False,
			   AnyPropertyType, &actual_type, &actual_format, 
			   &nitems, &bytesafter, (unsigned char **) iconname)
	== Success && actual_type != None) {
	text_prop.value = (unsigned char*)*iconname;
	text_prop.encoding = actual_type;
	text_prop.format = actual_format;
	text_prop.nitems = nitems;
	if (text_prop.value && text_prop.value[0] == '\0') {
	    *iconname = (char*)text_prop.value;
	    return 1;
	}
	status_ret = XmbTextPropertyToTextList(dpy, &text_prop, &list, &num);
	if (status_ret == Success && num > 0 && *list) {
	    *iconname = *list;
	    return 1;
	} else if (status_ret > Success) {
	    *iconname = (char *)text_prop.value;
	    return 1;
	} else 
	  *iconname = NULL;
    } else
      *iconname = NULL;
    return 0;
}
#endif

#endif /* I18N_MB */


static void eatExpose()
{
    XEvent event, foo;
    
    /* compress all expose events into a single one */
    
    if (XCheckMaskEvent(dpy, ExposureMask, &event)) {
	/* ignore other exposure events for this window */
	while (XCheckWindowEvent(dpy, event.xexpose.window, ExposureMask,
			       &foo));
	/* eat exposes for other windows */
	eatExpose();
	
	event.xexpose.count = 0;
	XPutBackEvent(dpy, &event);
    }
}

void
SlideWindow(Window win, int from_x, int from_y, int to_x, int to_y)
{
    float dx, dy, x=from_x, y=from_y, sx, sy;

    dx = (float)(to_x-from_x);
    dy = (float)(to_y-from_y);
    sx = (dx == 0 ? 0 : fabs(dx)/dx);
    sy = (dy == 0 ? 0 : fabs(dy)/dy);

    if (fabs(dx) > fabs(dy)) {
	if (dx!=0) {
	    dy = (dy*ICON_SLIDE_ANIMATION_STEP)/dx;
	    dx = ICON_SLIDE_ANIMATION_STEP;
	}
    } else {
	if (dy!=0) {
	    dx = (dx*ICON_SLIDE_ANIMATION_STEP)/dy;
	    dy = ICON_SLIDE_ANIMATION_STEP;
	}
    }
    dx = fabs(dx) * sx;
    dy = fabs(dy) * sy;

    if (dx == 0.0 && to_x != from_x) x = to_x;
    if (dy == 0.0 && to_y != from_y) y = to_y;
    while ((int)x != to_x || (int)y != to_y) {
	x += dx;
	y += dy;
	if ((dx<0 && (int)x < to_x)
	    || (dx>0 && (int)x > to_x))
	  x = (float)to_x;
	if ((dy<0 && (int)y < to_y)
	    || (dy>0 && (int)y > to_y))
	  y = (float)to_y;
	XMoveWindow(dpy, win, (int)x, (int)y);
	XFlush(dpy);
#if (ICON_SLIDE_ANIMATION_DELAY>0)
	wmsleep(ICON_SLIDE_ANIMATION_DELAY);
#endif
    }
    XMoveWindow(dpy, win, to_x, to_y);

    XSync(dpy, 0);
    /* compress expose events */
    eatExpose();
}


char*
ShrinkString(WFont *font, char *string, int width)
{
    int w, w1=0;
    int p;
    char *pos;
    char *text;
    int p1, p2, t;

#ifdef I18N_MB
    return strdup(string);
#else
    p = strlen(string);
    w = MyTextWidth(font->font, string, p);
    text = wmalloc(strlen(string)+8);
    strcpy(text, string);
    if (w<=width)
      return text;

    pos = strchr(text, ' ');
    if (!pos)
      pos = strchr(text, ':');

    if (pos) {
	*pos = 0;
	p = strlen(text);
	w1=MyTextWidth(font->font, text, p);
	if (w1>width) {
	    w1 = 0;
	    p = 0;
	    *pos = ' ';
	    *text = 0;
	} else {
	    *pos = 0;
	    width -= w1;
	    p++;
	}
	string += p;
	p=strlen(string);
    } else {
	*text=0;
    }
    strcat(text, "...");
    width -= MyTextWidth(font->font, "...", 3);
    
    pos = string;
    p1=0;
    p2=p;
    t = (p2-p1)/2;
    while (p2>p1 && p1!=t) {
	w = MyTextWidth(font->font, &string[p-t], t);
	if (w>width) {
	    p2 = t;
	    t = p1+(p2-p1)/2;
	} else if (w<width) {
	    p1 = t;
	    t = p1+(p2-p1)/2;
	} else 
	  p2=p1=t;
    }
    strcat(text, &string[p-p1]);
    return text;
#endif /* I18N_MB */
}


char*
FindImage(char **paths, char *file)
{
    char *tmp, *path;
    
    tmp = strrchr(file, ':');
    if (tmp) {
	*tmp = 0;
	path = find_file_in_list(paths, file);
	*tmp = ':';
    }
    if (!tmp || !path) {
	path = find_file_in_list(paths, file);
    }
    return path;
}



RImage*
wGetImageForWindowName(WScreen *scr, char *wclass, char *winstance)
{
    char *file_name;
    char *path;
    int w, h;
    RImage *image, *tmp;

    file_name = wDefaultGetIconFile(scr, winstance, wclass);
    if (!file_name)
	return NULL;
    
    path = FindImage(wPreferences.pixmap_path, file_name);
    
    if (!path) {
	wWarning(_("could not find icon file \"%s\""), file_name);
	return NULL;
    }
    
    image = RLoadImage(scr->rcontext, path, 0);
    free(path);
    
    if (wPreferences.icon_size!=ICON_WIDTH && image) {
	w = image->width*wPreferences.icon_size/ICON_WIDTH;
	h = image->height*wPreferences.icon_size/ICON_HEIGHT;
	
	tmp = RScaleImage(image, w, h);
	RDestroyImage(image);
	image = tmp;
    }
    return image;
}


char*
FlattenStringList(char **list, int count)
{
    int i, j;
    char *flat_string;

    j = 0;
    for (i=0; i<count; i++) {
	if (list[i]!=NULL)
	    j += strlen(list[i]);
    }
    
    flat_string = malloc(j+count+1);
    if (!flat_string) {
	return NULL;
    }

    strcpy(flat_string, list[0]);
    for (i=1; i<count; i++) {
	if (list[i]!=NULL) {
	    strcat(flat_string, " ");
	    strcat(flat_string, list[i]);
	}
    }
    
    return flat_string;
}



static void
timeup(void *foo)
{
    *(int*)foo=1;
}

static char*
getselection(WScreen *scr)
{
    XEvent event;
    int timeover=0;
    WMagicNumber *id;
    
#ifdef DEBUG
    puts("getting selection");
#endif
    wRequestSelection(dpy, scr->no_focus_win, LastTimestamp);
    /* timeout on 1 sec. */
    id = wAddTimerHandler(1000, timeup, &timeover);
    while (!timeover) {
	wNextEvent(&event);
	if (event.type == SelectionNotify 
	    && event.xany.window==scr->no_focus_win) {
	    wDeleteTimerHandler(id);
#ifdef DEBUG
	    puts("selection ok");
#endif
	    return wGetSelection(dpy, scr->no_focus_win);
	} else {
	    DispatchEvent(&event);
	}
    }
    wWarning(_("selection timed-out"));
    return NULL;
}


static char*
getuserinput(WScreen *scr, char *line, int *ptr)
{
    char *ret;
    char *tmp;
    int i;
    char buffer[256];
    
    if (line[*ptr]!='(') {
	tmp = _("Program Arguments");
    } else {
	i = 0;
	while (line[*ptr]!=0 && line[*ptr]!=')') {
	    (*ptr)++;
	    if (line[*ptr]!='\\') {
		buffer[i++] = line[*ptr];
	    } else {
		(*ptr)++;
		if (line[*ptr]==0)
		    break;
	    }
	}
	if (i>0)
	    buffer[i-1] = 0;
	tmp = (char*)buffer;
    }

    ret = NULL;
    if (wInputDialog(scr, tmp, _("Enter command arguments:"), &ret)!= WDB_OK)
	return NULL;
    else
	return ret;
}


#ifdef OFFIX_DND
static char*
get_dnd_selection(WScreen *scr)
{
    XTextProperty text_ret;
    int result;
    char **list;
    char *flat_string;
    int count;
    
    result=XGetTextProperty(dpy, scr->root_win, &text_ret, _XA_DND_SELECTION);
    
    if (result==0 || text_ret.value==NULL || text_ret.encoding==None
	|| text_ret.format==0 || text_ret.nitems == 0) {
	wWarning(_("unable to get dropped data from DND drop"));
	return NULL;
    }
    

    XTextPropertyToStringList(&text_ret, &list, &count);
    
    if (!list || count<1) {
	XFree(text_ret.value);
	wWarning(_("error getting dropped data from DND drop"));
	return NULL;
    }

    flat_string = FlattenStringList(list, count);
    if (!flat_string) {
	wWarning(_("out of memory while getting data from DND drop"));
    }
    
    XFreeStringList(list);
    XFree(text_ret.value);
    return flat_string;
}
#endif /* OFFIX_DND */


#define S_NORMAL 0
#define S_ESCAPE 1
#define S_OPTION 2

/* 
 * state    	input   new-state	output
 * NORMAL	%	OPTION		<nil>
 * NORMAL	\	ESCAPE		<nil>
 * NORMAL	etc.	NORMAL		<input>
 * ESCAPE	any	NORMAL		<input>
 * OPTION	s	NORMAL		<selection buffer>
 * OPTION	w	NORMAL		<selected window id>
 * OPTION	a	NORMAL		<input text>
 * OPTION	d	NORMAL		<OffiX DND selection object>
 * OPTION	etc.	NORMAL		%<input>
 */
#define TMPBUFSIZE 64
char*
ExpandOptions(WScreen *scr, char *cmdline)
{
    int ptr, optr, state, len, olen;
    char *out, *nout;
    char *selection=NULL;
    char *user_input=NULL;
#ifdef OFFIX_DND
    char *dropped_thing=NULL;
#endif
    char tmpbuf[TMPBUFSIZE];
    int slen;

    len = strlen(cmdline);
    olen = len+1;
    out = malloc(olen);
    if (!out) {
	wWarning(_("out of memory during expansion of \"%s\""));
	return NULL;
    }
    *out = 0;
    ptr = 0; 			       /* input line pointer */
    optr = 0;			       /* output line pointer */
    state = S_NORMAL;
    while (ptr < len) {
	switch (state) {
	 case S_NORMAL:
	    switch (cmdline[ptr]) {
	     case '\\':
		state = S_ESCAPE;
		break;
	     case '%':
		state = S_OPTION;
		break;
	     default:
		state = S_NORMAL;
		out[optr++]=cmdline[ptr];
		break;
	    }
	    break;
	 case S_ESCAPE:
	    switch (cmdline[ptr]) {
	     case 'n':
		out[optr++]=10;
		break;
		
	     case 'r':
		out[optr++]=13;
		break;
		
	     case 't':
		out[optr++]=9;
		break;
		
	     default:
		out[optr++]=cmdline[ptr];
	    }
	    state = S_NORMAL;
	    break;
	 case S_OPTION:
	    state = S_NORMAL;
	    switch (cmdline[ptr]) {
	     case 'w':
		if (scr->focused_window
		    && scr->focused_window->flags.focused) {
		    sprintf(tmpbuf, "0x%x", 
			    (unsigned int)scr->focused_window->client_win);
		    slen = strlen(tmpbuf);
		    olen += slen;
		    nout = realloc(out,olen);
		    if (!nout) {
			wWarning(_("out of memory during expansion of \"%w\""));
			goto error;
		    }
		    out = nout;
		    strcat(out,tmpbuf);
		    optr+=slen;
		} else {
		    out[optr++]=' ';
		}
		break;
		
	     case 'a':
		ptr++;
		user_input = getuserinput(scr, cmdline, &ptr); 
		if (user_input) {
		    slen = strlen(user_input);
		    olen += slen;
		    nout = realloc(out,olen);
		    if (!nout) {
			wWarning(_("out of memory during expansion of \"%a\""));
			goto error;
		    }
		    out = nout;
		    strcat(out,user_input);
		    optr+=slen;
		}
		break;

#ifdef OFFIX_DND
	     case 'd':
		if (!dropped_thing) {
		    dropped_thing = get_dnd_selection(scr);
		}
		if (!dropped_thing) {
		    scr->flags.dnd_data_convertion_status = 1;
		    goto error;
		}
		slen = strlen(dropped_thing);
		olen += slen;
		nout = realloc(out,olen);
		if (!nout) {
		    wWarning(_("out of memory during expansion of \"%d\""));
		    goto error;
		}
		out = nout;
		strcat(out,dropped_thing);
		optr+=slen;
		break;
#endif /* OFFIX_DND */
		
	     case 's':
		if (!selection) {
		    if (!XGetSelectionOwner(dpy, XA_PRIMARY)) {
			wWarning(_("selection not available"));
			goto error;
		    }
		    selection = getselection(scr);
		}
		if (!selection) {
		    goto error;
		}
		slen = strlen(selection);
		olen += slen;
		nout = realloc(out,olen);
		if (!nout) {
		    wWarning(_("out of memory during expansion of \"%s\""));
		    goto error;
		}
		out = nout;
		strcat(out,selection);
		optr+=slen;
		break;
	     default:
		out[optr++]='%';
		out[optr++]=cmdline[ptr];
	    }
	    break;
	}
	out[optr]=0;
	ptr++;
    }
    if (selection)
      XFree(selection);
    return out;
    
    error:
    free(out);
    if (selection)
      XFree(selection);
    return NULL;
}
