/*
 * Copyright (c) 1999 Alfredo K. Kojima
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 * $Id: damain.c,v 1.8 2003/02/10 12:35:03 dalroi Exp $
 */

#include "dockapp.h"
#include "dautil.h"


#define MIN(a, b)	(a < b ? a : b)


char		*progName = NULL;
int		d_width, d_height;
int		d_windowed = 0;
int		d_timeout = 0;
DACallbacks	d_callbacks = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};

DARect		DANoRect = {0, 0, 0, 0};
Display		*DADisplay = NULL;
Window		DALeader = None;
Window		DAIcon = None;
Window		DAWindow = None;
int		DADepth = 0;
Visual		*DAVisual = NULL;
unsigned long	DAExpectedVersion = 0;
GC		DAGC = NULL, DAClearGC = NULL;
DARect		DAPreferredIconSizes = {-1, -1, 0, 0};
Atom		WM_DELETE_WINDOW;


void
DAOpenDisplay(char *display, int argc, char **argv)
{
    /* Open Connection to X Server */
    DADisplay = XOpenDisplay(display);
    if (!DADisplay) {
	printf("%s: could not open display %s!\n", progName,
		XDisplayName(display));
	exit(EXIT_FAILURE);
    }

    DADepth	= DefaultDepth(DADisplay, DefaultScreen(DADisplay));
    DAVisual	= DefaultVisual(DADisplay, DefaultScreen(DADisplay));
    DAGC	= DefaultGC(DADisplay, DefaultScreen(DADisplay));
}


void
DAProposeIconSize(unsigned width, unsigned height)
{
    XIconSize		*iconSizes;
    int			nrSizes = 0;

    d_width	= width;
    d_height	= height;

    /* Get the nearest allowed icon size if the WM specifies such */
    iconSizes = XAllocIconSize();
    if (XGetIconSizes(DADisplay, DefaultRootWindow(DADisplay), 
		&iconSizes, &nrSizes)) {
	int i;
	int da = -1;
	int min_w = -1, min_h = -1;
	int max_w = 0, max_h = 0;

	for (i = 0; i < nrSizes; i++)  {
	    int w1, h1, w, h;

	    if ((max_w < iconSizes[i].max_width) || 
		    (max_h < iconSizes[i].max_height)) {
		max_w = iconSizes[i].max_width;
		max_h = iconSizes[i].max_height;
	    }
	    
	    if ((min_w > iconSizes[i].min_width) || 
		    (min_h > iconSizes[i].min_height) || 
		    (min_w == -1)) {
		min_w = iconSizes[i].min_width;
		min_h = iconSizes[i].min_height;
	    }
	    
	    if ((width  > iconSizes[i].max_width) ||
		(width  < iconSizes[i].min_width) ||
		(height > iconSizes[i].max_height) ||
		(height < iconSizes[i].min_height))
		continue;
	    
	    w1 = (iconSizes[i].max_width  - width ) % iconSizes[i].width_inc;
	    h1 = (iconSizes[i].max_height - height) % iconSizes[i].height_inc;
	    w = MIN(w1, iconSizes[i].width_inc  - w1);
	    h = MIN(h1, iconSizes[i].height_inc - h1);

    	    if ((w * h < da) || (da == -1)) {
		d_width  = width  + w;
		d_height = height + h;
		da = w * h;
	    }
	}

	DAPreferredIconSizes.x = min_w;
	DAPreferredIconSizes.y = min_h;
	DAPreferredIconSizes.width  = max_w;
	DAPreferredIconSizes.height = max_h;

	if (da == -1) {	/* requested size is out of bounds */
	    warn("Requested icon-size (%d x %d) is out of the range "
		    "allowed by the window manager\n",
		    d_width, d_height);
	}
    }
    XFree(iconSizes);
}


void
DACreateIcon(char *name, unsigned width, unsigned height, int argc, char **argv)
{
    XClassHint		*classHint;
    XWMHints		*wmHints;
    XGCValues		gcv;
    unsigned long	valueMask;
    char		*resourceValue;

    d_width  = width;
    d_height = height;

    /* Create Windows */
    DALeader = XCreateSimpleWindow(DADisplay, DefaultRootWindow(DADisplay),
	    0, 0, width, height, 0, 0, 0);

    if (! d_windowed) {
	DAIcon = XCreateSimpleWindow(DADisplay, DefaultRootWindow(DADisplay),
		0, 0, width, height, 0, 0, 0);
	DAWindow = DAIcon;
    } else {
	DAIcon = None;
	DAWindow = DALeader;
    }

    /* Set ClassHint */
    if (!(classHint = XAllocClassHint()))
	printf("%s: can't allocate memory for class hints!\n", progName), exit(1);
    classHint->res_class = RES_CLASSNAME;
    classHint->res_name = name;

    XSetClassHint(DADisplay, DALeader, classHint);
    XFree(classHint);

    /* Set WMHints */
    if (!(wmHints = XAllocWMHints()))
	printf("%s: can't allocate memory for wm hints!\n", progName), exit(1);

    wmHints->flags = WindowGroupHint;
    wmHints->window_group = DALeader;

    if (!d_windowed) {
    	wmHints->flags |= IconWindowHint|StateHint;
	wmHints->icon_window = DAIcon;
    	wmHints->initial_state = WithdrawnState;
    }

    XSetWMHints(DADisplay, DALeader, wmHints);
    XFree(wmHints);

    /* Set WMProtocols */
    WM_DELETE_WINDOW = XInternAtom(DADisplay, "WM_DELETE_WINDOW", True);
    XSetWMProtocols(DADisplay, DALeader, &WM_DELETE_WINDOW, 1);
    
    /* Set Command to start the app so it can be docked properly */
    XSetCommand(DADisplay, DALeader, argv, argc);

    gcv.graphics_exposures = False;
    valueMask = GCGraphicsExposures;

    /* continue setting the foreground GC */
    resourceValue = XGetDefault(DADisplay, RES_CLASSNAME, "foreground");
    if (resourceValue) {
	gcv.foreground = DAGetColor(resourceValue);
	valueMask |= GCForeground;
    }

    XChangeGC(DADisplay, DAGC, valueMask, &gcv);

    /* set background GC values before setting value for foreground */
    resourceValue = XGetDefault(DADisplay, RES_CLASSNAME, "background");
    if (resourceValue) {
	gcv.foreground = DAGetColor(resourceValue);
    }

    DAClearGC = XCreateGC(DADisplay, DAWindow,
	    GCGraphicsExposures|GCForeground, &gcv);

    XFlush(DADisplay);
}


void
DAShow(void)
{
    DAShowWindow(DALeader);
}


void
DAShowWindow(Window window)

{
    XMapRaised(DADisplay, window);
    if ((window == DALeader) && !d_windowed) {
	XMapSubwindows(DADisplay, DAIcon);
    } else {
	XMapSubwindows(DADisplay, window);
    }

    XFlush(DADisplay);
}


static void
printHelp(char *prog, char *description, DAProgramOption *options, int count)
{
    int j, defCount;
    DAProgramOption defaultOptions[] = {
	{"-h", "--help", "shows this help text and exit", DONone, False, {NULL}},
	{"-v", "--version",  "shows program version and exit", DONone, False, {NULL}},
	{"-w", "--windowed", "runs the application in windowed mode", DONone, False, {NULL}}
    };
   
    printf("Usage: %s [OPTIONS]\n", prog);
    if (description)
	puts(description);

    defCount	= sizeof(defaultOptions)/sizeof(DAProgramOption);

    for (j = 0; j < count + defCount; j++)
    {
	char blank[30];
	int c;
	int i;

	if (j >= count) {
	    options = defaultOptions;
	    i = j - count;
	}
	else
	    i = j;

	if (options[i].shortForm && options[i].longForm)
	    c = printf("  %s, %s", options[i].shortForm, options[i].longForm);
	else if (options[i].shortForm)
	    c = printf("  %s", options[i].shortForm);
	else if (options[i].longForm)
	    c = printf("  %s", options[i].longForm);
	else
	    continue;

	if (options[i].type != DONone) {
	    switch (options[i].type) {
		case DOInteger:
		    c += printf(" <integer>");
		    break;
		case DOString:
		    c += printf(" <string>");
		    break;
		case DONatural:
		    c += printf(" <number>");
		    break;
	    }
	}

	memset(blank, ' ', 30);
	if (c > 29)
	    c = 1;
	blank[30-c] = 0;
	printf("%s %s\n", blank, options[i].description);
    }
}


void
DAParseArguments(int argc, char **argv, DAProgramOption *options,
	int count, char *programDescription, char *versionDescription)
{
    int i, j;
    int found = 0;

    progName	= argv[0];

    for (i = 1; i < argc; i++) {
	if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
	    printHelp(argv[0], programDescription, options, count), exit(0);
	if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0)
	    puts(versionDescription), exit(0);
	if (strcmp(argv[i], "-w") == 0 || strcmp(argv[i], "--windowed") == 0) {
	    d_windowed = 1;
	    continue;
	}

	found = 0;
	for (j = 0; j < count; j++) {
	    if ((options[j].shortForm && strcmp(options[j].shortForm, argv[i]) == 0) ||
		    (options[j].longForm && strcmp(options[j].longForm, argv[i]) == 0)) {
		found = 1;
		
		options[j].used = True;
		
		if (options[j].type == DONone)
		    break;
		
		i++;
		if (i >= argc)
		    printf("%s: missing argument for option '%s'\n", argv[0], argv[i-1]), exit(1);
		
		switch (options[j].type) {
		    case DOInteger:
			{
			    int integer;
			    
			    if (sscanf(argv[i], "%i", &integer) != 1)
				printf("%s: error parsing argument for option %s\n",
					argv[0], argv[i-1]), exit(1);
					*options[j].value.integer = integer;
			}
			break;
		    case DONatural:
			{
			    int integer;
			    
			    if (sscanf(argv[i], "%i", &integer) != 1)
				printf("%s: error oarsing argument for option %s\n",
					argv[0], argv[i-1]), exit(1);
					if (integer < 0)
					    printf("%s: argument %s must be >= 0\n",
						    argv[0], argv[i-1]), exit(1);
						    *options[j].value.integer = integer;
			}
			break;
		    case DOString:
			*options[j].value.string = argv[i];
			break;
		}
		break;
	    }
	}
	if (!found) {
	    printf("%s: unrecognized option '%s'\n", argv[0], argv[i]);
	    printHelp(argv[0], programDescription, options, count), exit(1);
	}
    }
}

