/*-
 *
 * This plugin was written by Andrew Sampson for the Golem window manager,
 * using code from the fspanel application and the Golem pager plugin
 *
 * Portions Copyright (c) 2000-2001 Peter Zelezny	(fspanel)
 * Portions Copyright (c) 2001 Jordan DeLong		(pager plugin)
 *
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "wm.h"
#include "taskbar.h"



/* decoration group for taskbar windows */
static dgroup_t	*taskbar_dgroup	= NULL;

/* initial position for the taskbar... 0 == bottom, 1 == top */
static int taskbar_at_top = 0;

/* should we display icons? */
static int taskbar_icons = 1;


char *taskbar_atom_names[] = {
	"KWM_WIN_ICON",
	"_MOTIF_WM_HINTS",
	"_WIN_WORKSPACE",
	"_WIN_HINTS",
	"_WIN_LAYER",
	"_NET_CLIENT_LIST",
	"_WIN_CLIENT_LIST",
	"_WIN_WORKSPACE_COUNT",
	"_WIN_STATE",
	"WM_STATE"
};

#define ATOM_COUNT (sizeof (taskbar_atom_names) / sizeof (taskbar_atom_names[0]))

Atom taskbar_atoms[ATOM_COUNT];

#define atom_KWM_WIN_ICON taskbar_atoms[0]
#define atom__MOTIF_WM_HINTS taskbar_atoms[1]
#define atom__WIN_WORKSPACE taskbar_atoms[2]
#define atom__WIN_HINTS taskbar_atoms[3]
#define atom__WIN_LAYER taskbar_atoms[4]
#define atom__NET_CLIENT_LIST taskbar_atoms[5]
#define atom__WIN_CLIENT_LIST taskbar_atoms[6]
#define atom__WIN_WORKSPACE_COUNT taskbar_atoms[7]
#define atom__WIN_STATE taskbar_atoms[8]
#define atom_WM_STATE taskbar_atoms[9]


void scale_icon (taskbar_t *tb, task *tk);
void get_task_hinticon (taskbar_t *tb, task *tk);
void get_task_kdeicon (taskbar_t *tb, task *tk);
void get_task_icon( taskbar_t *tb, task *tk );


/* setup the taskbar system */
void taskbar_initialize( int at_top, int show_icons ) {

	taskbar_at_top = at_top;
    taskbar_icons = show_icons;

	taskbar_dgroup = &dgroup_empty;
}




/* create a taskbar */
taskbar_t* taskbar_create( plugin_t *plugin, screen_t *screen, desktop_t *desktop ) {

	XSetWindowAttributes attr;
	clientflags_t flags;
	taskbar_t *tb;

	if( !screen )
            return NULL;
        if( !plugin )
            return NULL;


        tb = calloc(1, sizeof(taskbar_t));
	if ( !tb )
		return NULL;

        scr_width = screen->width;
        scr_depth = DefaultDepth ( display, DefaultScreen(display) );

	attr.background_pixel = palette[0];

	tb->bottom_position = (screen->height - 1) - TASKBAR_HEIGHT;
        tb->at_top = taskbar_at_top;

	tb->win = XCreateWindow (
		/* display */ display,
		/* parent  */ screen->root,
		/* x       */ 0,
		/* y       */ (tb->at_top ? 0 : tb->bottom_position ),
		/* width   */ (screen->width - 1),
		/* height  */ TASKBAR_HEIGHT,
		/* border  */ 0,
		/* depth   */ CopyFromParent,
		/* class   */ InputOutput,
		/* visual  */ CopyFromParent,
		/*value mask*/ CWBackPixel,
		/* attribs */ &attr );

	XSelectInput(display, tb->win, ExposureMask | ButtonReleaseMask | ButtonPressMask);
	plugin_setcontext( plugin, tb->win );

	XInternAtoms (display, taskbar_atom_names, ATOM_COUNT, False, taskbar_atoms);

	/* create client_t for our taskbar */
	bzero(&flags, sizeof(clientflags_t));
	flags.internal = 1;
	flags.nofocus = 1;
	flags.noresize = 1;
	flags.noiconify = 1;
	flags.nodelete = 1;
	tb->client = client_add(screen, tb->win, &flags, taskbar_dgroup);
	if (!tb->client)
		goto free1;


	/* map windows */
	tb->client->state = NormalState;
	XMapWindow(display, tb->win);
	XMapWindow(display, tb->client->frame);

	return tb;

free1:
	XDestroyWindow(display, tb->win);
	free( tb );
	return NULL;

}



/* clean up after a taskbar */
void taskbar_delete(taskbar_t *taskbar) {
	task *current_task, *tmp;

	/* get rid of tasks */
	current_task = taskbar->task_list;
	while (current_task) {
		tmp = current_task->next;
		taskbar_rmtask(taskbar, current_task, current_task->client);
		current_task = tmp;
	}

	/* get rid of our window */
	XDestroyWindow(display, taskbar->win);
   plugin_rmcontext(taskbar->win);
   printf("removing taskbar from client list\n");
	client_rm(taskbar->client);
	free(taskbar);
}



/* add a task to a taskbar */
void taskbar_addtask(plugin_t *plugin, taskbar_t *tb, client_t *client)
{
	task *tk, *list;

	if( !plugin || !tb || !client )
            return;

    tk = calloc (1, sizeof (task));
	tk->client = client;
//fixme	tk->focused = focus;
	tk->focused = 0;

	if( client->state == IconicState )
    {
       tk->iconified = 1;
    }
    else
    {
       tk->iconified = 0;
    }

    tk->icon = tk->mask = None;
    tk->icon_copied = 0;
    taskbar_client_propertynotify( tb, tk, client );

    /* now append it to our linked list */
	tb->num_tasks++;

	list = tb->task_list;
	if (!list)
	{
		tb->task_list = tk;
		return;
	}
	while (1)
	{
       if (!list->next)
       {
			list->next = tk;
            tk->prev = list;
			return;
       }
       list = list->next;
	}


}

/*
 * find task for the given client.
 * return NULL if not found.
 */
task *taskbar_findtask(taskbar_t *taskbar, client_t *client)
{
	task *tk;

	/* find the task for this client */
	tk = taskbar->task_list;
	while (tk) {
		if (tk->client == client)
			return tk;
		tk = tk->next;
	}
	return NULL;
}

/*
 * remove a task from a taskbar; if tk is null we find
 * it ourselves.
 */
void taskbar_rmtask(taskbar_t *taskbar, task *tk, client_t *client) {

	/* if they supplied a task, use it */
	if (tk)
		goto found;

	/* find the task for this client */
	tk = taskbar_findtask(taskbar, client);
	if (!tk)
		return;

found:
	/* unlink */
	if (tk->prev)
		tk->prev->next = tk->next;
	if (tk->next)
		tk->next->prev = tk->prev;
	if (taskbar->task_list == tk)
		taskbar->task_list = tk->next;

        /* free mem used by task */
        /* name */
        if( tk->name ) {
            free( tk->name );
            tk->name = NULL;
        }
        /* icon */
        if( tk->icon != None )
        {
	if (tk->icon_copied)
		{
	/* if this icon is not the "default" */
			XFreePixmap (display, tk->icon);
			if (tk->mask != None)
				XFreePixmap (display, tk->mask);
		}
        }
	free(tk);
	taskbar->num_tasks--;

}



/* the client has emitted a property-notify, so we need to fetch
 * its name and icon again
 */
void taskbar_client_propertynotify( taskbar_t *tb, task *tk,
                                    client_t *client ) {

	if( !tb || !client )
            return;

	if( tk )
		goto found;

	/* find the task for this client */
	tk = tb->task_list;
	while (tk) {
		if (tk->client == client)
			goto found;
		tk = tk->next;
	}
	return;

found:

	if( tk->name )
            free( tk->name );

        if( !client->store_name )
            tk->name = strdup( " " );
        else
            tk->name = strdup( client->store_name );

	if( tk->icon == generic_icon || tk->icon == None ) {
            get_task_icon( tb, tk );
        }

}


/* clicks on the taskbar window */
void taskbar_click(taskbar_t *tb, int x, int y) {

	task *tk;


/* desktop switching (not impl.)
	if (y > 3 && y < WINHEIGHT - 3)
	{
		if (x >= right_arrow_x && x < right_arrow_x + 9)
		{
			switch_desk (tb, +1);
			return;
		}

		if (x >= left_arrow_x && x < left_arrow_x + 9)
		{
			switch_desk (tb, -1);
			return;
		}
	}
*/

	/* clicked left grill */
	if (x < GRILL_WIDTH)
	{
		if (tb->hidden)
			tb->hidden = 0;
/* disabled, for now...
		else
			tb->at_top = !tb->at_top;
*/
		move_taskbar (tb);
		return;
	}

	/* clicked right grill */
	if (x + GRILL_WIDTH > TASKBAR_WIDTH)
	{
		tb->hidden = !tb->hidden;
		move_taskbar (tb);
		return;
	}

	tk = tb->task_list;
	while (tk)
	{
		if (x > tk->pos_x && x < tk->pos_x + tk->width)
		{
			if (tk->client->state == IconicState)
			{
/*				tk->iconified = 0;
				tk->focused = 1;*/
               action_restore( tk->client );
			}
            else
			{
/*				if (tk->focused)
				{
                   tk->iconified = 1;
                   tk->focused = 0;*/
					action_iconify( tk->client );
/*				} else
				{
					tk->focused = 1;
					XRaiseWindow (display, tk->client->window);
					focus_setfocused( tk->client );
				}*/
			}
			XSync( display, False );
			gui_draw_task (tb, tk);
		}
        else
		{
//			if (tk->focused)
//			{
//				tk->focused = 0;
				gui_draw_task (tb, tk);
//			}
		}

		tk = tk->next;
	}


}

/* handle exeposures */
void taskbar_expose( taskbar_t *tb, GC gc, XExposeEvent *e )
{
	task *tk;
	int x, width, taskw;
	int under = 0;
    int tasks_on_current_ws = 0; /* a hack ...*/

	tb->fore_gc = gc;

    width = TASKBAR_WIDTH - 20 - (GRILL_WIDTH * 2);  // - time_width
	x = 20;

	if (tb->num_tasks == 0)
		goto clear;

//fixme - count up tasks on current workspace, not total...
// could probably do something like (pseudocode):
// list_length( currentWorkspace->w_foclist )
	taskw = width / tb->num_tasks;
	if (taskw > MAX_TASK_WIDTH)
	{
		taskw = MAX_TASK_WIDTH;
		under = 1;
	}

	tk = tb->task_list;
	while (tk)
	{
       if( tb->client->screen->desktop->current_space != NULL )
       {
//          printf("current: %d --", tb->client->screen->desktop->current_space );
       }
       if( tk->client->workspace == NULL )
       {
//          printf("\tnull worspace\n");
       }
       else
       {
//          printf("\tworkspace: %d\n", tk->client->workspace );
       }


       // iconfied apps have a null worksspace. non-icons have a
       // workspace which can be compared to the curent workspace if
       // you are wondering whether to draw them or not. The real question
       // is ... why is iconification, null'ing the workspace.

       if( tk->client->workspace ==
           tb->client->screen->desktop->current_space ||
           tk->client->workspace == NULL )
       {
          //         if( tk->client->state == IconicState )
          //{
             tk->pos_x = x;
             tk->width = taskw - 1;
             gui_draw_task (tb, tk);
             x += taskw;
             tasks_on_current_ws ++;
             //}
       }
       else
       {
          tk->pos_x = -1;
          tk->width = 0;
       }

       tk = tk->next;
	}

	if( under || tasks_on_current_ws != tb->num_tasks )
	{
clear:
		gui_draw_vline (tb, x);
		set_foreground (tb, 0);
		fill_rect (tb, x + 2, 0, TASKBAR_WIDTH, TASKBAR_HEIGHT);
	}

	gui_draw_vline (tb, GRILL_WIDTH - 2);
	gui_draw_vline (tb, TASKBAR_WIDTH - (GRILL_WIDTH + 2) );

	draw_grill (tb, 2);
	draw_grill (tb, TASKBAR_WIDTH + 2 - GRILL_WIDTH );

}



void *
get_prop_data (Window win, Atom prop, Atom type, int *items)
{
	Atom type_ret;
	int format_ret;
	unsigned long items_ret;
	unsigned long after_ret;
	unsigned char *prop_data;

	prop_data = 0;

	XGetWindowProperty (display, win, prop, 0, 0x7fffffff, False,
				  type, &type_ret, &format_ret, &items_ret,
				  &after_ret, &prop_data);
	if (items)
		*items = items_ret;

	return prop_data;
}


void
set_foreground( taskbar_t *tb, int index )
{
	XSetForeground (display, tb->fore_gc, palette[index]);
}


void
draw_line (taskbar_t *tb, int x, int y, int a, int b)
{
	XDrawLine (display, tb->win, tb->fore_gc, x, y, a, b);
}


void
draw_dot (taskbar_t *tb, int x, int y)
{
	set_foreground (tb, 4);
	XDrawPoint (display, tb->win, tb->fore_gc, x, y);
	set_foreground (tb, 3);
	XDrawPoint (display, tb->win, tb->fore_gc, x + 1, y + 1);
}

void
draw_grill (taskbar_t *tb, int x)
{
	int y = 0;
	while (y < TASKBAR_HEIGHT - 4)
	{
		y += 3;
		draw_dot (tb, x + 3, y);
		draw_dot (tb, x, y);
	}
}

void
fill_rect (taskbar_t *tb, int x, int y, int a, int b)
{
	XFillRectangle (display, tb->win, tb->fore_gc, x, y, a, b);
}


void
gui_draw_vline (taskbar_t * tb, int x)
{
	set_foreground (tb, 4);
	draw_line (tb, x, 0, x, TASKBAR_HEIGHT);
	set_foreground (tb, 3);
	draw_line (tb, x + 1, 0, x + 1, TASKBAR_HEIGHT);
}


void
gui_draw_task (taskbar_t * tb, task * tk)
{
	int len;
	int x;
	int taskw;
	int drawicon;

#ifdef HAVE_XPM
	drawicon = taskbar_icons;
#else
	drawicon = 0;
#endif

	if (!tk)
		return;
	if (!tk->name)
		return;
        if( tk->pos_x < 1 )
	return;

	x = tk->pos_x;
	taskw = tk->width;

        if( taskw < (ICONWIDTH * 3 ) )
            drawicon = 0;

	gui_draw_vline (tb, x);

/*set_foreground (3); *//* it's already 3 from gui_draw_vline() */
	draw_line (tb, x + 1, 0, x + taskw, 0);

	set_foreground (tb, 1);
	draw_line (tb, x + 1, TASKBAR_HEIGHT - 1, x + taskw, TASKBAR_HEIGHT - 1);

/* original scheme had focused-window-indented	if (tk->focused) { */
/* new scheme has minimized-windows-indented */
	if (tk->client->state == IconicState )
    {
		x++;
		/*set_foreground (1);*/		  /* mid gray */
		fill_rect (tb, x + 3, 3, taskw - 5, TASKBAR_HEIGHT - 6);
		set_foreground (tb, 3);		  /* white */
		draw_line (tb, x + 2, TASKBAR_HEIGHT - 2, x + taskw - 2, TASKBAR_HEIGHT - 2);
		draw_line (tb, x + taskw - 2, 2, x + taskw - 2, TASKBAR_HEIGHT - 2);
		set_foreground (tb, 0);
		draw_line (tb, x + 1, 2, x + 1, TASKBAR_HEIGHT - 2);
		set_foreground (tb, 4);		  /* darkest gray */
		draw_line (tb, x + 2, 2, x + taskw - 2, 2);
		draw_line (tb, x + 2, 2, x + 2, TASKBAR_HEIGHT - 3);
	} else {
		set_foreground (tb, 0);		  /* mid gray */
		fill_rect (tb, x + 2, 1, taskw - 1, TASKBAR_HEIGHT - 2);
	}


	{
		register int text_x = x + TEXTPAD + TEXTPAD;
                if( drawicon )
                    text_x += ICONWIDTH;

		/* check how many chars can fit */
		len = strlen (tk->name);
		while (XTextWidth (xfs, tk->name, len) >= taskw - (text_x - x) - 2
				 && len > 0)
			len--;

//		if (tk->iconified)
//		{
//			/* draw task's name dark (iconified) */
//			set_foreground (tb, 3);
//			XDrawString (display, tb->win, tb->fore_gc, text_x, text_y + 1, tk->name,
//							 len);
//			set_foreground (tb, 4);
//		} else
		{
			set_foreground (tb, 5);
		}

		/* draw task's name here */
		XDrawString (display, tb->win, tb->fore_gc, text_x, text_y, tk->name, len);
	}

	if ( !drawicon || !tk->icon)
		return;

#ifdef HAVE_XPM
	/* draw the task's icon */
	XSetClipMask (display, tb->fore_gc, tk->mask);
	XSetClipOrigin (display, tb->fore_gc, x + TEXTPAD, (TASKBAR_HEIGHT - ICONHEIGHT) / 2);
	XCopyArea (display, tk->icon, tb->win, tb->fore_gc, 0, 0, ICONWIDTH, ICONHEIGHT,
				  x + TEXTPAD, (TASKBAR_HEIGHT - ICONHEIGHT) / 2);
	XSetClipMask (display, tb->fore_gc, None);
#endif
}


void
move_taskbar (taskbar_t * tb)
{
	int x, y;

	x = tb->client->x;
        y = tb->client->y;


	if( tb->hidden ) {
		x = TASKBAR_WIDTH - GRILL_WIDTH - 2;
        } else {
	x = 0;
        }

	if( tb->at_top ) {
		y = 0;
        } else {
		y = tb->bottom_position;
        }

	XMoveWindow (display, tb->client->frame, x, y);
}




void scale_icon (taskbar_t *tb, task *tk)
{
        int xx, yy, x, y, w, h, d, bw;
	Pixmap pix, mk = None;
	XGCValues gcv;
	GC mgc;

	XGetGeometry (display, tk->icon, &pix, &x, &y, &w, &h, &bw, &d);
	pix = XCreatePixmap (display, tb->win, ICONWIDTH, ICONHEIGHT, scr_depth);

	if (tk->mask != None)
	{
		mk = XCreatePixmap (display, tb->win, ICONWIDTH, ICONHEIGHT, 1);
		gcv.subwindow_mode = IncludeInferiors;
		gcv.graphics_exposures = False;
		mgc = XCreateGC (display, mk, GCGraphicsExposures | GCSubwindowMode, &gcv);
	}

	set_foreground (tb, 3);

	/* this is my simple & dirty scaling routine */
	for (y = ICONHEIGHT - 1; y >= 0; y--)
	{
		yy = (y * h) / ICONHEIGHT;
		for (x = ICONWIDTH - 1; x >= 0; x--)
		{
			xx = (x * w) / ICONWIDTH;

			if (d != scr_depth)
				XCopyPlane (display, tk->icon, pix, tb->fore_gc, xx, yy, 1, 1, x, y, 1);
			else
				XCopyArea (display, tk->icon, pix, tb->fore_gc, xx, yy, 1, 1, x, y);

			if (mk != None)
				XCopyArea (display, tk->mask, mk, mgc, xx, yy, 1, 1, x, y);
		}
	}

	if (mk != None)
	{
		XFreeGC (display, mgc);
		tk->mask = mk;
	}

        // don't worry about tk->icon, it gets freed in get_task_hinticon
        tk->icon = pix;
}

void get_task_hinticon (taskbar_t *tb, task *tk)
{
	XWMHints *hin;

	tk->icon = None;
	tk->mask = None;

	hin = (XWMHints *) get_prop_data (tk->client->window, XA_WM_HINTS, XA_WM_HINTS, 0);
	if (hin)
	{
		if ((hin->flags & IconPixmapHint))
		{
			if ((hin->flags & IconMaskHint))
			{
				tk->mask = hin->icon_mask;
			}

			tk->icon = hin->icon_pixmap;
			tk->icon_copied = 1;
			scale_icon (tb, tk);
		}
		XFree (hin);
	}

	if (tk->icon == None)
	{
		tk->icon = generic_icon;
		tk->mask = generic_mask;
	}
}

void get_task_kdeicon (taskbar_t *tb, task *tk)
{
	unsigned long *data;

   /* fixme - this function is horribly, horribly broken...
      I don't know what's wrong with it */

	data = get_prop_data (tk->client->window, atom_KWM_WIN_ICON, atom_KWM_WIN_ICON, 0);
	if (data)
	{
		tk->icon = data[0];
		tk->mask = data[1];
		XFree (data);
	}
}

void
get_task_icon( taskbar_t *tb, task *tk )
{

#ifdef HAVE_XPM


	if( tk->icon_copied && tk->icon != None ) {
            XFreePixmap( display, tk->icon );
            XFreePixmap( display, tk->mask );
        }
        tk->icon_copied = 0;
        tk->icon = None;
        tk->mask = None;

//	get_task_kdeicon (tb, tk);
	if (tk->icon == None) {
		get_task_hinticon (tb, tk);
        }


#else
	tk->icon = tk->mask = None;
#endif

}
