/*-
 * Copyright (c) 2001 Jordan DeLong
 * 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"

/* font for titles */
#ifdef I18N
 XFontSet   titlefont;
#else
 XFontStruct	*titlefont;
#endif

/* list of decoration units */
static SLIST_HEAD(, decor) decor_list = SLIST_HEAD_INITIALIZER(&decor_list);

/* get our titlebar font */
void decor_init()
{
	char *deffont = "fixed";
#ifdef I18N
	titlefont = XLoadQueryFontSet(display, options.title_font ? options.title_font : deffont);
#else
	titlefont = XLoadQueryFont(display, options.title_font ? options.title_font : deffont);
#endif
	if (!titlefont)
   {
		if (options.title_font)
      {
#ifdef I18N
			titlefont = XLoadQueryFontSet(display, deffont);
#else
			titlefont = XLoadQueryFont(display, deffont);
#endif
			if (!titlefont)
				errx(1, "unable to load fallback titlefont 'fixed'");
		}
      else
      {
			errx(1, "unable to load titlefont 'fixed'");
      }
	}
}

/* free memory used for decor stuff */
void decor_shutdown()
{
	decor_t *decor;

	/* free the title font */
#ifdef I18N
	XFreeFontSet(display, titlefont);
#else
	XFreeFont(display, titlefont);
#endif

	/* free the list of decor_t */
	while (!SLIST_EMPTY(&decor_list))
   {
		decor = SLIST_FIRST(&decor_list);
		SLIST_REMOVE_HEAD(&decor_list, d_list);
		free(decor->ident);
		free(decor);
	}
}

/* function to add a decoration */
void decor_add(decor_t *decor)
{
	SLIST_INSERT_HEAD(&decor_list, decor, d_list);
}

/* find a decoration unit by name; used during rcfile parser */
decor_t *decor_ident(char *ident)
{
	decor_t *decor;

	SLIST_FOREACH(decor, &decor_list, d_list)
		if (strcmp(decor->ident, ident) == 0)
			return decor;

	return NULL;
}


/* get pos of a decoration unit based on it's type/edge and modifiers */
static void decor_getdims( client_t *client,
                           decor_t *decor,
                           int *x, int *y,
                           int *pwidth, int *pheight )
{
	dgroup_t *dgroup;
	int width = 0, height = 0;
	int vert = 0;

	dgroup = client->dgroup;
	switch (decor->edge) {
	case DE_TOP:
		*y = dgroup->top_space - decor->pixmap->height;
		break;
	case DE_LEFT:
		*x = dgroup->left_space - decor->pixmap->width;
		vert = 1;
		break;
	case DE_RIGHT:
		*x = dgroup->left_space + client->width;
		vert = 1;
		break;
	case DE_BOTTOM:
		*y = dgroup->top_space + client->height;
		break;
	}

	switch (decor->type) {
	case DA_FULL:
		if (vert) {
			width = decor->pixmap->width;
			height = client->height;
			*y = dgroup->top_space;
		} else {
			width = dgroup->left_space + client->width + dgroup->right_space;
			height = decor->pixmap->height;
			*x = 0;
		}
		break;
	case DA_NEAR:
		if (vert)
			*y = dgroup->top_space;
		else
			*x = 0;
		break;
	case DA_FAR:
		if (vert)
			*y = dgroup->top_space + client->height - decor->pixmap->height;
		else
			*x = dgroup->left_space + client->width
				+ dgroup->right_space - decor->pixmap->width;
		break;
	}

	if (!width || !height) {
		*pwidth = decor->pixmap->width;
		*pheight = decor->pixmap->height;
	} else {
		*pwidth = width;
		*pheight = height;
	}

	if (vert) {
		*y += decor->offset;
		*pheight += decor->lenmod;
		*y += client->height * decor->offsetmult;
		*pheight += client->height * decor->lenmult;

		*x += decor->soffset;
	} else {
		*x += decor->offset;
		*pwidth += decor->lenmod;
		*x += client->width * decor->offsetmult;
		*pwidth += client->width * decor->lenmult;

		*y += decor->soffset;
	}

	/* finally, make sure that the width/height are valid */
	if (*pwidth < 1) *pwidth = 1;
	if (*pheight < 1) *pheight = 1;
}

/* shape as a rect, not w/ mask combines (shapeunit) */
#define RECTSHAPE() do {									\
	rect[0].x = x; rect[0].y = y;								\
	rect[0].width = width;									\
	rect[0].height = height;								\
	XShapeCombineRectangles(display, client->frame, ShapeBounding, 0, 0, rect,		\
		1, ShapeUnion, Unsorted);							\
} while (0)

/* shape a decoration unit onto this client's frame */
static void decor_shapeunit(client_t *client, decor_t *decor, int idx)
{
	XRectangle rect[1];
	int x, y, width, height;
	int num;

	decor_getdims(client, decor, &x, &y, &width, &height);
	XMoveResizeWindow(display, client->decorwin[idx], x, y,
                      width ? width : 1, height ? height : 1);

	/* shape; if the width/height aren't the same as the decor we have a DA_FULL */
	if (width == decor->pixmap->width && height == decor->pixmap->height) {
		if (decor->flags.shaped)
			XShapeCombineMask(display, client->frame, ShapeBounding, x, y,
				decor->pixmap->shapemasks[client->screen->num], ShapeUnion);
		else
			RECTSHAPE();
	} else if (height != decor->pixmap->height) {
		if (decor->flags.shaped) {
			for (num = y; num <= y + height; num += decor->pixmap->height) {
				/* we also need to reshape the window itself for these */
				XShapeCombineMask(display, client->decorwin[idx], ShapeBounding, 0, num - y,
					decor->pixmap->shapemasks[client->screen->num], num == y ? ShapeSet : ShapeUnion);
				XShapeCombineMask(display, client->frame, ShapeBounding, x, num,
					decor->pixmap->shapemasks[client->screen->num], ShapeUnion);
			}
		} else {
			RECTSHAPE();
		}
	} else if (width != decor->pixmap->width) {
		if (decor->flags.shaped) {
			for (num = x; num <= x + width; num += decor->pixmap->width) {
				/* we also need to reshape the window itself for these */
				XShapeCombineMask(display, client->decorwin[idx], ShapeBounding, num - x, 0,
					decor->pixmap->shapemasks[client->screen->num], num == x ? ShapeSet : ShapeUnion);
				XShapeCombineMask(display, client->frame, ShapeBounding, num, y,
					decor->pixmap->shapemasks[client->screen->num], ShapeUnion);
			}
		} else {
			RECTSHAPE();
		}
	}
}

#undef RECTSHAPE

/* called on resizes and such to shape/size the client decor, etc */
void decor_shapesize(client_t *client)
{
	XRectangle rect[4];
	dgroup_t *dgroup;
	int cnt = 0;
	int i;

	dgroup = client->dgroup;

	/* fill in old chops on right and bottom */
	if (client->width > client->last_width) {
		rect[cnt].x = dgroup->left_space + client->last_width;
		rect[cnt].y = 0;
		rect[cnt].width = client->width - client->last_width;
		rect[cnt].height = dgroup->top_space + client->height;
		cnt++;
	}
	if (client->height > client->last_height) {
		rect[cnt].x = 0;
		rect[cnt].y = dgroup->top_space + client->last_height;
		rect[cnt].width = dgroup->left_space + client->width;
		rect[cnt].height = client->height - client->last_height;
		cnt++;
	}
	if (cnt == 2) {
		rect[cnt].x = dgroup->left_space + client->last_width;
		rect[cnt].y = dgroup->top_space + client->last_height;
		rect[cnt].width = client->width - client->last_width;
		rect[cnt].height = client->height - client->last_height;
	}
	XShapeCombineRectangles(display, client->frame, ShapeBounding, 0, 0, rect,
		cnt, ShapeUnion, Unsorted);

	/* chop off the decoration area */
	rect[0].x = 0;
	rect[0].y = 0;
	rect[0].width = dgroup->left_space + client->width + dgroup->right_space;
	rect[0].height = dgroup->top_space;
	rect[1].x = 0;
	rect[1].y = dgroup->top_space;
	rect[1].width = dgroup->left_space;
	rect[1].height = client->height;
	rect[2].x = dgroup->left_space + client->width;
	rect[2].y = dgroup->top_space;
	rect[2].width = dgroup->right_space;
	rect[2].height = client->height;
	rect[3].x = 0;
	rect[3].y = dgroup->top_space + client->height;
	rect[3].width = dgroup->left_space + client->width + dgroup->right_space;
	rect[3].height = dgroup->bottom_space;
	XShapeCombineRectangles(display, client->frame, ShapeBounding, 0, 0, rect,
		4, ShapeSubtract, Unsorted);


	for (i = 0; i < client->dgroup->decor_count; i++)
		decor_shapeunit(client, client->dgroup->decor[i], i);
}

/* the initial shaping of a client's decoration, etc */
static void decor_firstshape(client_t *client) {
	XRectangle rect[4];
	dgroup_t *dgroup;
	int i;

	dgroup = client->dgroup;

	/* chop off the decoration area */
	rect[0].x = 0;
	rect[0].y = 0;
	rect[0].width = dgroup->left_space + client->width + dgroup->right_space;
	rect[0].height = dgroup->top_space;
	rect[1].x = 0;
	rect[1].y = dgroup->top_space;
	rect[1].width = dgroup->left_space;
	rect[1].height = client->height;
	rect[2].x = dgroup->left_space + client->width;
	rect[2].y = dgroup->top_space;
	rect[2].width = dgroup->right_space;
	rect[2].height = client->height;
	rect[3].x = 0;
	rect[3].y = dgroup->top_space + client->height;
	rect[3].width = dgroup->left_space + client->width + dgroup->right_space;
	rect[3].height = dgroup->bottom_space;
	XShapeCombineRectangles(display, client->frame, ShapeBounding, 0, 0, rect,
		4, ShapeSubtract, Unsorted);

	for (i = 0; i < client->dgroup->decor_count; i++)
		decor_shapeunit(client, client->dgroup->decor[i], i);
}

/* create a decoration window according to it's tyoe and edge */
static void decor_makewin(client_t *client, decor_t *decor, int idx)
{
	XSetWindowAttributes attr;
	int mask = ButtonPressMask;
	int x, y;
	int width, height;

	decor_getdims(client, decor, &x, &y, &width, &height);

	attr.background_pixmap = decor->pixmap->pixmaps[client->screen->num];
	client->decorwin[idx] = XCreateWindow(display, client->frame, x, y,
                                         width, height, 0, CopyFromParent, CopyFromParent, CopyFromParent,
                                         CWBackPixmap, &attr);

	if (decor->flags.shaped)
		XShapeCombineMask(display, client->decorwin[idx], ShapeBounding, 0, 0,
			decor->pixmap->shapemasks[client->screen->num], ShapeSet);
	if (decor->flags.button)
		mask |= ButtonReleaseMask;
	if (client->dgroup->title == decor)
		mask |= ExposureMask;

	XSaveContext(display, client->decorwin[idx], client_context, (XPointer) client);
	XSaveContext(display, client->decorwin[idx], decor_context, (XPointer) decor);
	XSelectInput(display, client->decorwin[idx], mask);

	XMapWindow(display, client->decorwin[idx]);
}

/* called during client_add to add our decoration to a client */
void decor_decorate(client_t *client)
{
	int i;

	/*
	 * alloc the amount of windows neccessary for this dgroup,
	 * if there are any.
	 */
	if (!client->dgroup->decor_count)
		return;

	client->decorwin = calloc(client->dgroup->decor_count, sizeof(Window));
	for (i = 0; i < client->dgroup->decor_count; i++)
		decor_makewin(client, client->dgroup->decor[i], i);

	decor_firstshape(client);
}

/* undecorate this client: kill the decor wins and free mem */
void decor_undecor(client_t *client)
{
	int i;

	/*
	 * kill every decoration window we added, if we
	 * added any.
	 */
	if (!client->dgroup->decor_count)
   {
		return;
   }

	for (i = 0; i < client->dgroup->decor_count; i++)
   {
		XDeleteContext(display, client->decorwin[i], decor_context);
		XDeleteContext(display, client->decorwin[i], client_context);
		XDestroyWindow(display, client->decorwin[i]);
	}

	free(client->decorwin);
}

/* perform the operation specified by act */
static void decor_act(client_t *client, int act)
{
	switch (act)
   {
      case ACT_NONE:
         break;
      case ACT_MOVE:
         action_move(client);
         break;
      case ACT_RESIZE:
         action_resize(client);
         break;
      case ACT_DELETE:
         if (!client->flags.nodelete)
         {
            action_sendcmesg(client->window, WM_DELETE_WINDOW, CurrentTime);
         }
         break;
      case ACT_ICONIFY:
         action_iconify(client);
         break;
      case ACT_ZOOM:
         action_zoom(client);
         break;
      case ACT_SHADE:
         action_shade( client );
         break;
	}
}

/* handle a press on a button */
static void decor_buttonpress(client_t *client, decor_t *decor, int action)
{
	XEvent event;
	int idx;

	/* get the decor index */
	for (idx = 0; idx < client->dgroup->decor_count; idx++)
   {
		if (client->dgroup->decor[idx] == decor)
      {
			break;
      }
   }

	/* change the pixmap */
	if (decor->pressedmap)
   {
		XSetWindowBackgroundPixmap(display, client->decorwin[idx],
			decor->pressedmap->pixmaps[client->screen->num]);
		XClearWindow(display, client->decorwin[idx]);
	}

	/* wait for button release */

	while (1)
   {
		XMaskEvent(display, ButtonReleaseMask, &event);

		if (event.type == ButtonRelease)
      {
			if (decor->pressedmap)
         {
				XSetWindowBackgroundPixmap(display, client->decorwin[idx],
					decor->focpixmap->pixmaps[client->screen->num]);
				XClearWindow(display, client->decorwin[idx]);
			}
			if (event.xbutton.x > 0 && event.xbutton.y > 0 && event.xbutton.x < decor->pixmap->width
					&& event.xbutton.y < decor->pixmap->height
					&& event.xbutton.root == client->screen->root)
         {
				decor_act(client, action);
         }
			return;
		}
	}
}

static int is_double_click( client_t *client,
                            XButtonEvent *e  )
{
   if( (client->doubleclick.last_click_time > 0 ) &&
       (e->time - client->doubleclick.last_click_time
                        <= options.dclick_timeout /*msec*/ ) )
   {
      return 1;
   }

   return 0;
}


/* handle a press on a piece of decoration for a client */
void decor_handlepress(client_t *client, decor_t *decor, XButtonEvent *e)
{
	int action;

   /*
	 * handle button presses on client frame window (for mouse_modifier
	 * grabs), the client window to do focusing in click focus mode,
	 * or client decoration.
	 */

	if (e->button == Button3)
   {
      if( is_double_click( client, e ) )
      {
         action = decor->drclick_action;
      }
      else
      {
         action = decor->rclick_action;
      }
   }
	else if (e->button == Button2)
   {
      if( is_double_click( client, e ) )
      {
         action = decor->dmclick_action;
      }
      else
      {
         action = decor->mclick_action;
      }
   }
	else
   {
      if( is_double_click( client, e ) )
      {
         action = decor->drclick_action;
      }
      else
      {
         action = decor->lclick_action;
      }
   }

	if (decor->flags.button)
   {
      decor_buttonpress(client, decor, action);
   }
	else
   {
      decor_act(client, action);
   }

   /* Update the double click information for this client */
   /* Note should be one per button type ... */
   client->doubleclick.last_click_time = e->time;
   client->doubleclick.last_click_button = e->button;
}

/* modify decoration pixmaps to indicate a client is focused */
void decor_focus(client_t *client)
{
	int i;

	for (i = 0; i < client->dgroup->decor_count; i++)
   {
		if (client->dgroup->decor[i]->focpixmap != client->dgroup->decor[i]->pixmap)
      {
			XSetWindowBackgroundPixmap(display, client->decorwin[i],
				client->dgroup->decor[i]->focpixmap->pixmaps[client->screen->num]);
			XClearWindow(display, client->decorwin[i]);
			if (client->dgroup->title == client->dgroup->decor[i])
				decor_expose(client, client->dgroup->decor[i], NULL);
		}
	}
}

/* modify decoration pixmaps to indicate a client is unfocused (normal) */
void decor_unfocus(client_t *client)
{
	int i;

	for (i = 0; i < client->dgroup->decor_count; i++) {
		if (client->dgroup->decor[i]->focpixmap != client->dgroup->decor[i]->pixmap)
      {
			XSetWindowBackgroundPixmap(display, client->decorwin[i],
				client->dgroup->decor[i]->pixmap->pixmaps[client->screen->num]);
			XClearWindow(display, client->decorwin[i]);
			if (client->dgroup->title == client->dgroup->decor[i])
				decor_expose(client, client->dgroup->decor[i], NULL);
		}
	}
}

/* handle exposes on decoration */
void decor_expose(client_t *client, decor_t *decor, XExposeEvent *e)
{
	int idx;
#ifdef I18N
	XFontSetExtents *extent;

	extent = XExtentsOfFontSet(titlefont);
#endif

	/* we are just drawing the whole thing, so we only use count == 0 */
	if (e && e->count > 0)
		return;

	/* get the decor index */
	for (idx = 0; idx < client->dgroup->decor_count; idx++)
		if (client->dgroup->decor[idx] == decor)
			break;

	/*
	 * since we're just drawing a line of text, optimizing which characters we
	 * actually need to draw is kinda more trouble than it's worth.
	 */
	if (client->store_name)
#ifdef I18N
		XmbDrawString( display,
                     client->decorwin[idx],
                     titlefont,
                     client->screen->titlegc,
                     client->dgroup->title_x,
                     client->dgroup->title_y + (extent->max_logical_extent.height * 4 / 5),
                     client->store_name, strlen(client->store_name));
#else
		XDrawString( display,
                   client->decorwin[idx],
                   client->screen->titlegc,
                   client->dgroup->title_x,
                   client->dgroup->title_y + titlefont->ascent,
                   client->store_name,
                   strlen(client->store_name) );
#endif
}

/* if a window's title (store_name) changed, we hear about it here */
void decor_titlechange(client_t *client)
{
	int i;

	for (i = 0; i < client->dgroup->decor_count; i++)
   {
		if (client->dgroup->decor[i] == client->dgroup->title)
      {
			XClearWindow(display, client->decorwin[i]);
			decor_expose(client, client->dgroup->decor[i], NULL);
			break;
		}
	}
}

unsigned int decor_measurestring( char *string )
{
#ifdef I18N
   XRectangle ink, logical;
#else
   int direction_return;
   int font_ascent_return, font_descent_return;
   XCharStruct overall_return;
#endif

#ifdef    XFT
  if (_xftfont)
  {
    XGlyphInfo info;

    if (utf)
    {
      // the string to measure is UTF-8 encoded
#ifdef XFT_UTF8
      // measure it as is with XftTextExtentsUtf8()
      XftTextExtentsUtf8(_display, _xftfont, (XftChar8 *) string.c_str(),
                         string.size(), &info);
#else
      // if there is no XftTextExtentsUtf8(), try to convert to locale
      // encoding and use XftExtents8()
      XftTextExtents8(_display, _xftfont, (XftChar8 *) utf8tolocale(string).c_str(),
                      utf8tolocale(string).size(), &info);
#endif
    }
    else {
      // the string to measur isn't in UTF-8 but in locale encoding
#ifdef XFT_UTF8
      // convert to UTF-8 and measure with XftTextExtentsUtf8()
      XftTextExtentsUtf8(_display, _xftfont, (XftChar8 *) localetoutf8(string).c_str(),
                         localetoutf8(string).size(), &info);
#else
      // if there is no XftTextExtentsUtf8(), measure "as is" with XftTextExtents8()
      XftTextExtents8(_display, _xftfont, (XftChar8 *) string.c_str(),
                      string.size(), &info);
#endif
    }
    return info.xOff + (_shadow ? std::abs(_offset) : 0);
  }
#endif // XFT

#ifdef I18N
  XmbTextExtents( titlefont, string, strlen( string ), &ink, &logical);

  return logical.width;
#else
  //  int XTextWidth(font_struct, string, count)

  XTextExtents( titlefont, string, strlen( string ),
                &direction_return,
                &font_ascent_return,
                &font_descent_return,
                &overall_return );

  return overall_return.width;
#endif
}


unsigned int decor_measureheight( void )
{
#ifdef I18N
   XFontSetExtents *extent;
	extent = XExtentsOfFontSet(titlefont);
#endif

#ifdef    XFT
   if (_xftfont)
   {
      return _xftfont->height + (_shadow ? std::abs(_offset) : 0);
   }
#endif // XFT

#ifdef I18N
   return extent->max_logical_extent.height;
#else
   return titlefont->ascent + titlefont->descent;
#endif
}
