/**
 * Beryl Trailfocus - take three
 *
 * Copyright (c) 2006 Kristian Lyngstøl <kristian@beryl-project.org>
 * Ported to Compiz and BCOP usage by Danny Baumann <maniac@beryl-project.org>
 *
 * 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.
 *
 * This version is completly rewritten from scratch with opacify as a 
 * basic template. The original trailfocus was written by: 
 * François Ingelrest <Athropos@gmail.com> and rewritten by:
 * Dennis Kasprzyk <onestone@beryl-project.org>
 * 
 *
 * Trailfocus modifies the opacity, brightness and saturation on a window 
 * based on when it last had focus. 
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrender.h>
#include <compiz.h>
#include "trailfocus_options.h"

#define GET_TRAILFOCUS_DISPLAY(d)                            \
	((TrailfocusDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define TRAILFOCUS_DISPLAY(d)                                \
    TrailfocusDisplay *td = GET_TRAILFOCUS_DISPLAY (d)
#define GET_TRAILFOCUS_SCREEN(s, td)                         \
	((TrailfocusScreen *) (s)->privates[(td)->screenPrivateIndex].ptr)
#define TRAILFOCUS_SCREEN(s)                                 \
	TrailfocusScreen *ts = GET_TRAILFOCUS_SCREEN (s, GET_TRAILFOCUS_DISPLAY (s->display))
#define GET_TRAILFOCUS_WINDOW(w, ts)                         \
	((TrailfocusWindow *) (w)->privates[(ts)->windowPrivateIndex].ptr)
#define TRAILFOCUS_WINDOW(w)                                 \
	TrailfocusWindow *tw = GET_TRAILFOCUS_WINDOW (w, GET_TRAILFOCUS_SCREEN(w->screen, GET_TRAILFOCUS_DISPLAY (w->screen->display)))

static int displayPrivateIndex = 0;

typedef struct _TrailfocusDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;
} TrailfocusDisplay;

typedef struct _TfWindowAttributes
{
	GLushort opacity;
	GLushort brightness;
	GLushort saturation;
} TfAttrib;

typedef struct _TrailfocusScreen
{
	int windowPrivateIndex;

	Window *win;
	TfAttrib *inc;
	Bool initialized;

	PaintWindowProc paintWindow;
} TrailfocusScreen;

typedef struct _TrailfocusWindow
{
	Bool isTfWindow;
	TfAttrib attribs;
}  TrailfocusWindow;

/* Core trailfocus functions. These do the real work. ---------------*/

/* Walks through the window-list and sets the opacity-levels for
 * all windows. The inner loop will result in ts->win[i] either
 * representing a recently focused window, or the least
 * focused window.
 */
static void setWindows(CompScreen * s)
{
	CompWindow *w;
	Bool wasTfWindow;

	TRAILFOCUS_SCREEN(s);
	int i = 0;
	int winMax = trailfocusGetWindowsCount(s);

	for (w = s->windows; w; w = w->next)
	{
		TRAILFOCUS_WINDOW(w);
		wasTfWindow = tw->isTfWindow;
		tw->isTfWindow = TRUE;

		if (w->invisible || w->hidden || w->minimized)
			tw->isTfWindow = FALSE;
		else if (!matchEval(trailfocusGetWindowMatch(s), w))
			tw->isTfWindow = FALSE;

		if (wasTfWindow && !tw->isTfWindow)
			addWindowDamage(w);

		if (tw->isTfWindow)
		{
			for (i = 0; i < winMax; i++)
				if (w->id == ts->win[i])
					break;

			if (!wasTfWindow ||
				(memcmp(&tw->attribs, &ts->inc[i], sizeof(TfAttrib)) != 0))
			{
				addWindowDamage(w);
			}

			tw->attribs = ts->inc[i];
		}
	}
}

/* Push a new window-id on the trailfocus window-stack (not to be
 * confused with the real window stack).  Only keep one copy of a
 * window on the stack. If the window allready exist on the stack,
 * move it to the top.
 */
static CompScreen *pushWindow(CompDisplay * d, Window id)
{
	int i, winMax;
	CompWindow *w;

	w = findWindowAtDisplay(d, id);
	if (!w)
		return NULL;

	TRAILFOCUS_SCREEN(w->screen);

	winMax = trailfocusGetWindowsCount(w->screen);
	if (!matchEval(trailfocusGetWindowMatch(w->screen), w))
		return NULL;

	for (i = 0; i < winMax; i++)
		if (ts->win[i] == id)
			break;

	if (i == 0)
		return NULL;

	for (; i > 0; i--)
		ts->win[i] = ts->win[i - 1];

	ts->win[0] = id;
	return w->screen;
}

/* Ppop a window-id from the trailfocus window-stack (not to be
 * confused with the real window stack).  Only keep one copy of a
 * window on the stack. Also fill the empty space with the next
 * window on the real window stack.
 */
static CompScreen *popWindow(CompDisplay * d, Window id)
{
	int i, winMax;
	CompWindow *w;
	CompScreen *s;

	w = findWindowAtDisplay(d, id);
	if (!w)
		return NULL;

	s = w->screen;

	TRAILFOCUS_SCREEN(s);

	winMax = trailfocusGetWindowsCount(s);
	for (i = 0; i < winMax; i++)
		if (ts->win[i] == id)
			break;

	if (i == winMax)
		return NULL;

	for (i++ ; i < winMax; i++)
		ts->win[i - 1] = ts->win[i];

	ts->win[winMax - 1] = None;

	/* find window from the stacking order next to the last window
	   in the stack to fill the empty space */
	for (i = winMax - 1; i >= 0; i--)
	    if (ts->win[i])
		break;

    w = findWindowAtDisplay (d, ts->win[i]);
	if (w)
	{
	    CompWindow *cw;

	    for (cw = w->prev; cw; cw = cw->prev)
	    {
			if (matchEval(trailfocusGetWindowMatch(s), cw) &&
				!w->invisible && !w->hidden && !w->minimized)
			{
				ts->win[winMax - 1] = cw->id;
				break;
			}
		}
	}

	return s;
}

/* Find a window on a screen.... Unlike the findWindowAtScreen which
 * core provides, we don't intend to search for the same window several
 * times in a row so we optimize for* the normal situation of searching for 
 * a window only once in a row.
 */
static CompWindow *findWindow(CompScreen * s, Window id)
{
	CompWindow *w;

	for (w = s->windows; w; w = w->next)
		if (w->id == id)
			return w;

	return NULL;
}

/* Walks through the existing stack and removes windows that should
 * (no longer) be there. Used for option-change.
 */
static void cleanList(CompScreen * s)
{
	TRAILFOCUS_SCREEN(s);
	CompWindow *w;
	int i, j, length;
	int winMax;

	winMax = trailfocusGetWindowsCount(s);

	for (i = 0; i < winMax; i++)
	{
		w = findWindow(s, ts->win[i]);
		if (!w || !matchEval(trailfocusGetWindowMatch(s), w))
			ts->win[i] = 0;
	}

	length = winMax;
	for (i = 0; i < length; i++)
	{
		if (!ts->win[i])
		{
			for (j = i; j < length - 1; j++)
				ts->win[j] = ts->win[j + 1];
			length--;
		}
	}
	for (; length < winMax; length++)
		ts->win[length] = 0;
}

/* Handles the event if it was a FocusIn event.  */
static void trailfocusHandleEvent(CompDisplay * d, XEvent * event)
{
	TRAILFOCUS_DISPLAY(d);
	CompScreen *s;

	switch (event->type)
	{
	case FocusIn:
		s = pushWindow(d, event->xfocus.window);
		if (s)
			setWindows(s);
		break;
	case DestroyNotify:
		s = popWindow(d, event->xdestroywindow.window);
		if (s)
			setWindows(s);
		break;
	default:
		break;
	}

	UNWRAP(td, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(td, d, handleEvent, trailfocusHandleEvent);
}

/* Settings changed. Reallocate rs->inc and re-populate it and the
 * rest of the TrailfocusScreen (-wMask).
 */
static void recalculateAttributes(CompScreen * s)
{
	TRAILFOCUS_SCREEN(s);
	TfAttrib tmp, min, max;
	int i;
	int start;
	int winMax;

	start = trailfocusGetWindowsStart(s) - 1;
	winMax = trailfocusGetWindowsCount(s);

	if (start >= winMax)
	{
		compLogMessage (s->display, "trailfocus", CompLogLevelWarn,
						"Attempting to define start higher than max windows.");
		start = winMax - 1;
	}

	min.opacity = trailfocusGetMinOpacity(s) * OPAQUE / 100;
	min.brightness = trailfocusGetMinBrightness(s) * OPAQUE / 100;
	min.saturation = trailfocusGetMinSaturation(s) * OPAQUE / 100;
	max.opacity = trailfocusGetMaxOpacity(s) * OPAQUE / 100;
	max.brightness = trailfocusGetMaxBrightness(s) * OPAQUE / 100;
	max.saturation = trailfocusGetMaxSaturation(s) * OPAQUE / 100;

	ts->win = realloc(ts->win, sizeof(Window) * (winMax + 1));
	ts->inc = realloc(ts->inc, sizeof(TfAttrib) * (winMax + 1));

	tmp.opacity = (max.opacity - min.opacity) / ((winMax - start));
	tmp.brightness =
			(max.brightness - min.brightness) / ((winMax - start));
	tmp.saturation =
			(max.saturation - min.saturation) / ((winMax - start));

	for (i = 0; i < start; ++i)
		ts->inc[i] = max;

	for (i = 0; i + start <= winMax; i++)
	{
		ts->inc[i + start].opacity = max.opacity - (tmp.opacity * i);
		ts->inc[i + start].brightness = max.brightness - (tmp.brightness * i);
		ts->inc[i + start].saturation = max.saturation - (tmp.saturation * i);
		ts->win[i + start] = 0;
	}
//  ts->inc[i+start] = min;
}

static Bool trailfocusPaintWindow(CompWindow *w, const WindowPaintAttrib *attrib,
								  const CompTransform *transform,
								  Region region, unsigned int mask)
{
	Bool status;
	TRAILFOCUS_WINDOW(w);
	TRAILFOCUS_SCREEN(w->screen);

	if (!ts->initialized)
	{
		setWindows(w->screen);
		ts->initialized = TRUE;
	}

	if (tw->isTfWindow)
	{
		WindowPaintAttrib wAttrib = *attrib;

		wAttrib.opacity = MIN(attrib->opacity, tw->attribs.opacity);
		wAttrib.brightness = MIN(attrib->brightness, tw->attribs.brightness);
		wAttrib.saturation = MIN(attrib->saturation, tw->attribs.brightness);

		UNWRAP(ts, w->screen, paintWindow);
		status = (*w->screen->paintWindow) (w, &wAttrib, transform, region, mask);
		WRAP(ts, w->screen, paintWindow, trailfocusPaintWindow);
	}
	else
	{
		UNWRAP(ts, w->screen, paintWindow);
		status = (*w->screen->paintWindow) (w, attrib, transform, region, mask);
		WRAP(ts, w->screen, paintWindow, trailfocusPaintWindow);
	}

	return status;
}

/* Configuration, initliazation, boring stuff. ----------------------- */

static void trailfocusScreenOptionChanged(CompScreen *s, CompOption *opt, TrailfocusScreenOptions num)
{
	switch (num)
	{
		case TrailfocusScreenOptionMinOpacity:
		case TrailfocusScreenOptionMaxOpacity:
		case TrailfocusScreenOptionMinSaturation:
		case TrailfocusScreenOptionMaxSaturation:
		case TrailfocusScreenOptionMinBrightness:
		case TrailfocusScreenOptionMaxBrightness:
		case TrailfocusScreenOptionWindowsStart:
		case TrailfocusScreenOptionWindowsCount:
			recalculateAttributes(s);
			break;
		default:
			break;
	}

	cleanList(s);
	pushWindow(s->display, s->display->activeWindow);
	setWindows(s);
}

static Bool trailfocusInitWindow(CompPlugin *p, CompWindow *w)
{
	TRAILFOCUS_SCREEN(w->screen);

	TrailfocusWindow *tw = (TrailfocusWindow *) calloc(1, sizeof(TrailfocusWindow));
	w->privates[ts->windowPrivateIndex].ptr = tw;

	tw->isTfWindow = FALSE;

	return TRUE;
}

static void trailfocusFiniWindow(CompPlugin *p, CompWindow *w)
{
	TRAILFOCUS_WINDOW(w);

	free(tw);
}

/* Remember to reset windows to some sane value when we unload */
static void trailfocusFiniScreen(CompPlugin * p, CompScreen * s)
{
	TRAILFOCUS_SCREEN(s);

	if (ts->win)
		free(ts->win);
	if (ts->inc)
		free(ts->inc);

	UNWRAP(ts, s, paintWindow);

	free(ts);
}

/* Remember to populate the TrailFocus screen properly, and push the
 * active window on the stack, then set windows.
 */
static Bool trailfocusInitScreen(CompPlugin * p, CompScreen * s)
{
	TRAILFOCUS_DISPLAY(s->display);

	TrailfocusScreen *ts =
			(TrailfocusScreen *) calloc(1, sizeof(TrailfocusScreen));

	ts->windowPrivateIndex = allocateWindowPrivateIndex(s);
	if (ts->windowPrivateIndex < 0)
	{
		free(ts);
		return FALSE;
	}

	trailfocusSetWindowMatchNotify(s, trailfocusScreenOptionChanged);
	trailfocusSetWindowsCountNotify(s, trailfocusScreenOptionChanged);
	trailfocusSetWindowsStartNotify(s, trailfocusScreenOptionChanged);
	trailfocusSetMinOpacityNotify(s, trailfocusScreenOptionChanged);
	trailfocusSetMaxOpacityNotify(s, trailfocusScreenOptionChanged);
	trailfocusSetMinSaturationNotify(s, trailfocusScreenOptionChanged);
	trailfocusSetMaxSaturationNotify(s, trailfocusScreenOptionChanged);
	trailfocusSetMinBrightnessNotify(s, trailfocusScreenOptionChanged);
	trailfocusSetMaxBrightnessNotify(s, trailfocusScreenOptionChanged);

	s->privates[td->screenPrivateIndex].ptr = ts;

	WRAP(ts, s, paintWindow, trailfocusPaintWindow);

	recalculateAttributes(s);
	pushWindow(s->display, s->display->activeWindow);

	ts->initialized = FALSE;

	return TRUE;
}

static Bool trailfocusInitDisplay(CompPlugin * p, CompDisplay * d)
{
	TrailfocusDisplay *td =
			(TrailfocusDisplay *) malloc(sizeof(TrailfocusDisplay));

	td->screenPrivateIndex = allocateScreenPrivateIndex(d);
	if (td->screenPrivateIndex < 0)
	{
		free(td);
		return FALSE;
	}

	d->privates[displayPrivateIndex].ptr = td;
	WRAP(td, d, handleEvent, trailfocusHandleEvent);
	return TRUE;
}

static void trailfocusFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	TRAILFOCUS_DISPLAY(d);

	UNWRAP(td, d, handleEvent);

	freeScreenPrivateIndex(d, td->screenPrivateIndex);
	free(td);
}

static Bool trailfocusInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();
	if (displayPrivateIndex < 0)
		return FALSE;
	return TRUE;
}

static void trailfocusFini(CompPlugin * p)
{
	if (displayPrivateIndex >= 0)
		freeDisplayPrivateIndex(displayPrivateIndex);
}

static int trailfocusGetVersion(CompPlugin *p, int version)
{
	return ABIVERSION;
}

CompPluginVTable trailfocusVTable = {
	"trailfocus",
	trailfocusGetVersion,
	0,
	trailfocusInit,
	trailfocusFini,
	trailfocusInitDisplay,
	trailfocusFiniDisplay,
	trailfocusInitScreen,
	trailfocusFiniScreen,
	trailfocusInitWindow,
	trailfocusFiniWindow,
	0,							// trailfocusGetDisplayOptions,
	0,							// trailfocusSetDisplayOptions,
	0, 
	0
};

CompPluginVTable *getCompPluginInfo(void)
{
	return &trailfocusVTable;
}


syntax highlighted by Code2HTML, v. 0.9.1