/*
 * Compiz Cube Caps plugin
 *
 * cubecaps.c
 *
 * Copyright : (C) 2007 Guillaume Seguin
 * E-mail    : guillaume@segu.in
 *
 * 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.
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

#include <compiz.h>
#include <cube.h>
#include "cubecaps_options.h"

static int displayPrivateIndex;

static int cubeDisplayPrivateIndex;

typedef struct _CubeCapsDisplay
{
    int screenPrivateIndex;
} CubeCapsDisplay;

typedef struct _CubeCap
{
	int		    current;
	CompListValue	    *files;

	CompTexture	    texture;
	GLfloat		    tc[12];

	Bool		    scale;
	int		    pw;
	int		    ph;
} CubeCap;

typedef struct _CubeCapsScreen
{
    CubePaintTopProc	    paintTop;
    CubePaintBottomProc	    paintBottom;

    CubeCap		    topCap;
    CubeCap		    bottomCap;
} CubeCapsScreen;

#define GET_CUBECAPS_DISPLAY(d)						\
    ((CubeCapsDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define CUBECAPS_DISPLAY(d)						\
    CubeCapsDisplay *ccd = GET_CUBECAPS_DISPLAY (d);

#define GET_CUBECAPS_SCREEN(s, ccd)					\
    ((CubeCapsScreen *) (s)->privates[(ccd)->screenPrivateIndex].ptr)
#define CUBECAPS_SCREEN(s)						\
    CubeCapsScreen *ccs = GET_CUBECAPS_SCREEN (s,			\
			  GET_CUBECAPS_DISPLAY (s->display))

/* Actual caps handling ----------------------------------------------------- */

/*
 * Initiate a CubeCap
 */
static void
cubecapsInitCap (CompScreen *s, CubeCap *cap)
{
    memset (cap->tc, 0, sizeof (cap->tc));

    initTexture (s, &cap->texture);

    cap->current    = 0;
    cap->files	    = NULL;

    cap->scale	    = FALSE;
    cap->pw	    = 0;
    cap->ph	    = 0;
}

/*
 * Prepare cap texture coordinates
 */
static void
cubecapsInitTextureCoords (CompScreen * s, CubeCap * cap,
			   unsigned int width, unsigned int height)
{
    float x1, x2, y1, y2;
    CompMatrix *matrix;

    if (!cap)
	return;

    matrix = &cap->texture.matrix;

    if (cap->scale)
    {
	x1 = 0.0f;
	y1 = 0.0f;
	x2 = width;
	y2 = height;
    }
    else
    {
	int bigscr, i;
	int bigWidth, bigHeight;

	CUBE_SCREEN(s);
	bigWidth = s->width;
	bigHeight = s->height;

	/* Scale the texture in a sane way for multi head too */
	if (s->nOutputDev > 1 && cs->moMode != CUBE_MOMODE_ONE)
	{
	    for (i = bigscr = 0; i < s->nOutputDev; i++)
		if (s->outputDev[i].width > s->outputDev[bigscr].width)
		    bigscr = i;
	    bigWidth = s->outputDev[bigscr].width;
	    bigHeight = s->outputDev[bigscr].height;
	}

	x1 = width / 2.0f - bigWidth / 2.0f;
	y1 = height / 2.0f - bigHeight / 2.0f;
	x2 = width / 2.0f + bigWidth / 2.0f;
	y2 = height / 2.0f + bigHeight / 2.0f;
    }

    cap->tc[0] = COMP_TEX_COORD_X (matrix, width / 2.0f);
    cap->tc[1] = COMP_TEX_COORD_Y (matrix, height / 2.0f);

    cap->tc[2] = COMP_TEX_COORD_X (matrix, x2);
    cap->tc[3] = COMP_TEX_COORD_Y (matrix, y1);

    cap->tc[4] = COMP_TEX_COORD_X (matrix, x1);
    cap->tc[5] = COMP_TEX_COORD_Y (matrix, y1);

    cap->tc[6] = COMP_TEX_COORD_X (matrix, x1);
    cap->tc[7] = COMP_TEX_COORD_Y (matrix, y2);

    cap->tc[8] = COMP_TEX_COORD_X (matrix, x2);
    cap->tc[9] = COMP_TEX_COORD_Y (matrix, y2);

    cap->tc[10] = COMP_TEX_COORD_X (matrix, x2);
    cap->tc[11] = COMP_TEX_COORD_Y (matrix, y1);
}

/*
 * Attempt to load current cap image (if any)
 */
static void
cubecapsLoadCap (CompScreen *s,
		 CubeCap    *cap)
{
    unsigned int    width, height;
    int		    pw, ph;

    CUBE_SCREEN (s);

    if (!cs->fullscreenOutput)
    {
	pw = s->width;
	ph = s->height;
    }
    else
    {
	pw = s->outputDev[0].width;
	ph = s->outputDev[0].height;
    }

    if (!cap->files || !cap->files->nValue || cap->pw != pw || cap->ph != ph)
    {
	finiTexture (s, &cap->texture);
	initTexture (s, &cap->texture);

	if (!cap->files || !cap->files->nValue)
	    return;
    }

    cap->current = cap->current % cap->files->nValue;

    if (!readImageToTexture (s, &cap->texture,
			     cap->files->value[cap->current].s,
			     &width, &height))
    {
	compLogMessage (s->display, "cubecaps", CompLogLevelWarn,
			"Failed to load image: %s",
			cap->files->value[cap->current].s);

	finiTexture (s, &cap->texture);
	initTexture (s, &cap->texture);

	return;
    }

    cubecapsInitTextureCoords (s, cap, width, height);
}

/*
 * Paint a cap
 */
static void
cubecapsPaintCap (CompScreen	    *s,
		  int		    offset,
		  CubeCap	    *capOutside,
		  CubeCap	    *capInside,
		  unsigned short    *colorOutside,
		  unsigned short    *colorInside,
		  Bool		    clamp_to_border_outside,
		  Bool		    clamp_to_border_inside)
{
    CubeCap	    *cap;
    unsigned short  opacity;
    Bool	    clamp_to_border;

    CUBE_SCREEN(s);

    opacity = cs->desktopOpacity;

    if (cs->invert == 1)
    {
	cap = capOutside;
	clamp_to_border = clamp_to_border_outside;
	if (opacity == OPAQUE)
	    opacity = colorOutside[3];
	glColor4us (colorOutside[0],
		    colorOutside[1],
		    colorOutside[2],
		    opacity);
    }
    else if (cs->invert != 1)
    {
	cap = capInside;
	clamp_to_border = clamp_to_border_inside;
	if (opacity == OPAQUE)
	    opacity = colorInside[4];
	glColor4us (colorInside[0],
		    colorInside[1],
		    colorInside[2],
		    opacity);
    }

    glTranslatef (cs->outputXOffset, -cs->outputYOffset, 0.0f);
    glScalef (cs->outputXScale, cs->outputYScale, 1.0f);

    glVertexPointer (3, GL_FLOAT, 0, cs->vertices);

    glEnable (GL_BLEND);
    /* Draw cap once and reset color so that image will get correctly
     * blended, and for non-4-horizontal-viewports setups */
    if (opacity != OPAQUE)
    {
	screenTexEnvMode (s, GL_MODULATE);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glDrawArrays (GL_TRIANGLE_FAN, offset, cs->nVertices >> 1);
    }
    else
	glDrawArrays (GL_TRIANGLE_FAN, offset, cs->nVertices >> 1);
    
    glColor4usv (defaultColor);

    /* It is not really a good idea to draw the cap texture when there are 
     * only three viewports */
    if (cap && cap->texture.name && s->hsize >= 4)
    {
	/* Apply blend strategy to blend correctly color and image */
	if (opacity != OPAQUE)
	    glBlendFunc (GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	else
	    glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

	enableTexture (s, &cap->texture, COMP_TEXTURE_FILTER_GOOD);

	/* Use CLAMP_TO_BORDER if available to avoid weird looking clamping 
	 * of non-scaled images (it also improves scaled images a bit but 
	 * that's much less obvious) */
	if (clamp_to_border && s->textureBorderClamp)
	{
	    glTexParameteri (cap->texture.target, GL_TEXTURE_WRAP_S,
			     GL_CLAMP_TO_BORDER);
	    glTexParameteri (cap->texture.target, GL_TEXTURE_WRAP_T,
			     GL_CLAMP_TO_BORDER);
	}
	else
	{
	    glTexParameteri (cap->texture.target, GL_TEXTURE_WRAP_S,
			     GL_CLAMP_TO_EDGE);
	    glTexParameteri (cap->texture.target, GL_TEXTURE_WRAP_T,
			     GL_CLAMP_TO_EDGE);
	}

	if (s->hsize == 4)
	{
	    /* 4 viewports is pretty much straight forward ... */
	    glTexCoordPointer (2, GL_FLOAT, 0, cap->tc - (offset << 1));
	    glDrawArrays (GL_TRIANGLE_FAN, offset, cs->nVertices >> 1);
	}
	else if (s->hsize > 4)
	{
	    /* Paint image using custom vertexes */
	    int centerx = *cs->vertices;
	    int centery = *(cs->vertices + 1);
	    int centerz = *(cs->vertices + 2);
	    GLfloat x1, y1, x2, y2;
	    x1 = cap->tc[4];
	    x2 = cap->tc[2];
	    y1 = cap->tc[3];
	    y2 = cap->tc[9];

	    glBegin (GL_QUADS);

	    if (offset)
		centery -= 1;

	    if (offset)
	    {
		glTexCoord2f (x1, y1);
		glVertex3f (centerx - 0.5, centery + 0.5, centerz + 0.5);
		glTexCoord2f (x1, y2);
		glVertex3f (centerx - 0.5, centery + 0.5, centerz - 0.5);
		glTexCoord2f (x2, y2);
		glVertex3f (centerx + 0.5, centery + 0.5, centerz - 0.5);
		glTexCoord2f (x2, y1);
		glVertex3f (centerx + 0.5, centery + 0.5, centerz + 0.5);
	    }
	    else
	    {
		glTexCoord2f (x2,y2);
		glVertex3f (centerx + 0.5, centery + 0.5, centerz + 0.5);
		glTexCoord2f (x2, y1);
		glVertex3f (centerx + 0.5, centery + 0.5, centerz - 0.5);
		glTexCoord2f (x1, y1);
		glVertex3f (centerx - 0.5, centery + 0.5, centerz - 0.5);
		glTexCoord2f (x1, y2);
		glVertex3f (centerx - 0.5, centery + 0.5, centerz + 0.5);
	    }

	    glEnd ();
	}

	disableTexture (s, &cap->texture);
    }

    if (opacity != OPAQUE)
	screenTexEnvMode (s, GL_REPLACE);

    glDisable (GL_BLEND);
    glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}

/* Cube hooks --------------------------------------------------------------- */

/*
 * Paint top cube face
 */
static void
cubecapsPaintTop (CompScreen		  *s,
		  const ScreenPaintAttrib *sAttrib,
		  const CompTransform     *transform,
		  CompOutput		  *output,
		  int			  size)
{
    ScreenPaintAttrib sa = *sAttrib;
    CompTransform     sTransform = *transform;

    CUBE_SCREEN (s);
    CUBECAPS_SCREEN (s);

    /* Only paint if required */
    if (!cubecapsGetDrawTop (s))
	return;

    screenLighting (s, TRUE);

    glPushMatrix ();

    /* Readjust cap orientation ... */
    if (cs->invert == 1)
    {
	sa.yRotate += (360.0f / size) * (cs->xRotations + 1);
	if (!cubecapsGetAdjustTop (s)) /* ... Or not */
	    sa.yRotate -= (360.0f / size) * s->x;
    }
    else
    {
	sa.yRotate -= (360.0f / size) * (cs->xRotations - 1);
	if (!cubecapsGetAdjustTop (s)) /* ... Or not */
	    sa.yRotate += (360.0f / size) * s->x;
    }

    (*s->applyScreenTransform) (s, &sa, output, &sTransform);

    glLoadMatrixf (sTransform.m);

    /* Actually paint the cap */
    cubecapsPaintCap (s, 0, &ccs->topCap, &ccs->bottomCap,
		      cubecapsGetTopColor (s), cubecapsGetBottomColor (s),
		      cubecapsGetClampTopToBorder (s),
		      cubecapsGetClampBottomToBorder (s));

    glPopMatrix ();

    glColor4usv (defaultColor);
}

/*
 * Paint bottom cube face
 */
static void
cubecapsPaintBottom (CompScreen		     *s,
		     const ScreenPaintAttrib *sAttrib,
		     const CompTransform     *transform,
		     CompOutput		     *output,
		     int		     size)
{
    ScreenPaintAttrib sa = *sAttrib;
    CompTransform     sTransform = *transform;

    CUBE_SCREEN (s);
    CUBECAPS_SCREEN (s);

    /* Only paint if required */
    if (!cubecapsGetDrawBottom (s))
	return;

    screenLighting (s, TRUE);

    glPushMatrix ();

    /* Readjust cap orientation ... */
    if (cs->invert == 1)
    {
	sa.yRotate += (360.0f / size) * cs->xRotations;
	if (!cubecapsGetAdjustBottom (s)) /* ... Or not */
	    sa.yRotate -= (360.0f / size) * s->x;
    }
    else
    {
	sa.yRotate -= (360.0f / size) * cs->xRotations;
	if (!cubecapsGetAdjustBottom (s)) /* ... Or not */
	    sa.yRotate += (360.0f / size) * s->x;
    }

    (*s->applyScreenTransform) (s, &sa, output, &sTransform);

    glLoadMatrixf (sTransform.m);

    /* Actually paint the cap */
    cubecapsPaintCap (s, cs->nVertices >> 1, &ccs->bottomCap, &ccs->topCap,
		      cubecapsGetBottomColor (s), cubecapsGetTopColor (s),
		      cubecapsGetClampBottomToBorder (s),
		      cubecapsGetClampTopToBorder (s));

    glPopMatrix ();

    glColor4usv (defaultColor);
}

/* Settings handling -------------------------------------------------------- */

/*
 * Switch cap, load it and damage screen if possible
 */
static void
cubecapsChangeCap (CompScreen *s,
		   CubeCap    *cap,
		   int	      change)
{
    if (cap->files && cap->files->nValue)
    {
	int count = cap->files->nValue;
	cap->current = (cap->current + change + count) % count;
	cubecapsLoadCap (s, cap);
	damageScreen (s);
    }
}

/*
 * Top images list changed, reload top cap if any
 */
static void
cubecapsTopImagesChanged (CompScreen		*s,
			  CompOption		*opt,
			  CubecapsScreenOptions num)
{
    CUBECAPS_SCREEN (s);

    ccs->topCap.files = cubecapsGetTopImages (s);
    cubecapsChangeCap (s, &ccs->topCap, 0);
}

/*
 * Bottom images list changed, reload bottom cap if any
 */
static void
cubecapsBottomImagesChanged (CompScreen		   *s,
			     CompOption		   *opt,
			     CubecapsScreenOptions num)
{
    CUBECAPS_SCREEN (s);

    ccs->bottomCap.files = cubecapsGetBottomImages (s);
    cubecapsChangeCap (s, &ccs->bottomCap, 0);
}

/*
 * scale_top_image setting changed, reload top cap if any to update texture
 * coordinates
 */
static void
cubecapsScaleTopImageChanged (CompScreen	    *s,
			      CompOption	    *opt,
			      CubecapsScreenOptions num)
{
    CUBECAPS_SCREEN (s);

    ccs->topCap.scale = cubecapsGetScaleTopImage (s);
    cubecapsChangeCap (s, &ccs->topCap, 0);
}

/*
 * scale_bottom_image setting changed, reload bottom cap if any to update
 * texture coordinates
 */
static void
cubecapsScaleBottomImageChanged (CompScreen		*s,
				 CompOption		*opt,
				 CubecapsScreenOptions	num)
{
    CUBECAPS_SCREEN (s);

    ccs->bottomCap.scale = cubecapsGetScaleBottomImage (s);
    cubecapsChangeCap (s, &ccs->bottomCap, 0);
}

/* Actions handling --------------------------------------------------------- */

/*
 * Switch to next top image
 */
static Bool
cubecapsTopNext (CompDisplay     *d,
		 CompAction      *action,
		 CompActionState state,
		 CompOption      *option,
		 int		 nOption)
{
    CompScreen *s;
    Window     xid;

    xid = getIntOptionNamed (option, nOption, "root", 0);

    s = findScreenAtDisplay (d, xid);

    if (s)
    {
	CUBECAPS_SCREEN (s);
	cubecapsChangeCap (s, &ccs->topCap, 1);
    }

    return FALSE;
}

/*
 * Switch to previous top image
 */
static Bool
cubecapsTopPrev (CompDisplay     *d,
		 CompAction      *action,
		 CompActionState state,
		 CompOption      *option,
		 int		 nOption)
{
    CompScreen *s;
    Window     xid;

    xid = getIntOptionNamed (option, nOption, "root", 0);

    s = findScreenAtDisplay (d, xid);
    if (s)
    {
	CUBECAPS_SCREEN (s);
	cubecapsChangeCap (s, &ccs->topCap, -1);
    }

    return FALSE;
}

/*
 * Switch to next bottom image
 */
static Bool
cubecapsBottomNext (CompDisplay     *d,
		    CompAction      *action,
		    CompActionState state,
		    CompOption      *option,
		    int		    nOption)
{
    CompScreen *s;
    Window     xid;

    xid = getIntOptionNamed (option, nOption, "root", 0);

    s = findScreenAtDisplay (d, xid);

    if (s)
    {
	CUBECAPS_SCREEN (s);
	cubecapsChangeCap (s, &ccs->bottomCap, 1);
    }

    return FALSE;
}

/*
 * Switch to previous bottom image
 */
static Bool
cubecapsBottomPrev (CompDisplay     *d,
		    CompAction      *action,
		    CompActionState state,
		    CompOption      *option,
		    int		    nOption)
{
    CompScreen *s;
    Window     xid;

    xid = getIntOptionNamed (option, nOption, "root", 0);

    s = findScreenAtDisplay (d, xid);
    if (s)
    {
	CUBECAPS_SCREEN (s);
	cubecapsChangeCap (s, &ccs->bottomCap, -1);
    }

    return FALSE;
}

/* Internal stuff ----------------------------------------------------------- */

static Bool
cubecapsInitDisplay (CompPlugin  *p,
		     CompDisplay *d)
{
    CubeCapsDisplay *ccd;
    CompPlugin	 *cube = findActivePlugin ("cube");

    CompOption	*option;
    int		nOption;

    if (!cube || !cube->vTable->getDisplayOptions)
	return FALSE;

    option = (*cube->vTable->getDisplayOptions) (cube, d, &nOption);

    if (getIntOptionNamed (option, nOption, "abi", 0) != CUBE_ABIVERSION)
    {
	compLogMessage (d, "cubecaps", CompLogLevelError,
			"cube ABI version mismatch");
	return FALSE;
    }

    cubeDisplayPrivateIndex = getIntOptionNamed (option, nOption, "index", -1);

    if (cubeDisplayPrivateIndex < 0)
	return FALSE;

    ccd = malloc (sizeof (CubeCapsDisplay));

    if (!ccd)
	return FALSE;

    ccd->screenPrivateIndex = allocateScreenPrivateIndex (d);

    if (ccd->screenPrivateIndex < 0)
    {
	free (ccd);
	return FALSE;
    }

    cubecapsSetTopNextInitiate (d, cubecapsTopNext);
    cubecapsSetTopPrevInitiate (d, cubecapsTopPrev);
    cubecapsSetBottomNextInitiate (d, cubecapsBottomNext);
    cubecapsSetBottomPrevInitiate (d, cubecapsBottomPrev);

    d->privates[displayPrivateIndex].ptr = ccd;

    return TRUE;
}

static void
cubecapsFiniDisplay (CompPlugin  *p,
		     CompDisplay *d)
{
    CUBECAPS_DISPLAY (d);

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

static Bool
cubecapsInitScreen (CompPlugin *p,
		    CompScreen *s)
{
    CubeCapsScreen *ccs;
    CUBECAPS_DISPLAY (s->display);
    CUBE_SCREEN (s);

    ccs = malloc (sizeof (CubeCapsScreen));

    if (!ccs)
	return FALSE;

    cubecapsInitCap (s, &ccs->topCap);
    cubecapsInitCap (s, &ccs->bottomCap);

    ccs->topCap.files = cubecapsGetTopImages (s);
    ccs->bottomCap.files = cubecapsGetBottomImages (s);

    cubecapsSetTopImagesNotify (s, cubecapsTopImagesChanged);
    cubecapsSetBottomImagesNotify (s, cubecapsBottomImagesChanged);

    cubecapsSetScaleTopImageNotify (s, cubecapsScaleTopImageChanged);
    cubecapsSetScaleBottomImageNotify (s, cubecapsScaleBottomImageChanged);

    WRAP (ccs, cs, paintTop, cubecapsPaintTop);
    WRAP (ccs, cs, paintBottom, cubecapsPaintBottom);

    s->privates[ccd->screenPrivateIndex].ptr = ccs;

    cubecapsChangeCap (s, &ccs->topCap, 0);
    cubecapsChangeCap (s, &ccs->bottomCap, 0);

    return TRUE;
}

static void
cubecapsFiniScreen (CompPlugin *p,
		    CompScreen *s)
{
    CUBECAPS_SCREEN (s);
    CUBE_SCREEN (s);

    UNWRAP (ccs, cs, paintTop);
    UNWRAP (ccs, cs, paintBottom);

    free (ccs);
}

static Bool
cubecapsInit (CompPlugin * p)
{
    displayPrivateIndex = allocateDisplayPrivateIndex();

    if (displayPrivateIndex < 0)
	return FALSE;

    return TRUE;
}

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

static int
cubecapsGetVersion (CompPlugin *plugin,
		    int        version)
{
    return ABIVERSION;
}

CompPluginVTable cubecapsVTable =
{
    "cubecaps",
    cubecapsGetVersion,
    0,
    cubecapsInit,
    cubecapsFini,
    cubecapsInitDisplay,
    cubecapsFiniDisplay,
    cubecapsInitScreen,
    cubecapsFiniScreen,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};

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


syntax highlighted by Code2HTML, v. 0.9.1