/*
 * Compiz cube reflection plugin
 *
 * cubereflex.c
 *
 * Copyright : (C) 2007 by Dennis Kasprzyk
 * E-mail    : onestone@opencompositing.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.
 *
 */

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

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

#include "cubereflex_options.h"

#define DEG2RAD (M_PI / 180.0f)

static const CompTransform identity =
{
    {
	1.0, 0.0, 0.0, 0.0,
	0.0, 1.0, 0.0, 0.0,
	0.0, 0.0, 1.0, 0.0,
	0.0, 0.0, 0.0, 1.0
    }
};

#define MULTMV(m, v) { \
	float v0 = m[0]*v[0] + m[4]*v[1] + m[8]*v[2] + m[12]*v[3]; \
	float v1 = m[1]*v[0] + m[5]*v[1] + m[9]*v[2] + m[13]*v[3]; \
	float v2 = m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3]; \
	float v3 = m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3]; \
	v[0] = v0; v[1] = v1; v[2] = v2; v[3] = v3; }

static int displayPrivateIndex;

static int cubeDisplayPrivateIndex;

typedef struct _CubereflexDisplay
{
    int screenPrivateIndex;

}
CubereflexDisplay;

typedef struct _CubereflexScreen
{
    DonePaintScreenProc        donePaintScreen;
    PaintOutputProc            paintOutput;
    PaintScreenProc            paintScreen;
    PreparePaintScreenProc     preparePaintScreen;
    PaintTransformedOutputProc paintTransformedOutput;

    CubeClearTargetOutputProc clearTargetOutput;
    CubeGetRotationProc	      getRotation;
    CubeCheckOrientationProc  checkOrientation;

    Bool reflection;
    Bool first;
    
    CompOutput *last;

    float yTrans;
    float zTrans;

    float backVRotate;
    float vRot;

}
CubereflexScreen;

#define GET_CUBEREFLEX_DISPLAY(d) \
    ((CubereflexDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define CUBEREFLEX_DISPLAY(d) \
    CubereflexDisplay *rd = GET_CUBEREFLEX_DISPLAY(d);

#define GET_CUBEREFLEX_SCREEN(s, rd) \
    ((CubereflexScreen *) (s)->privates[(rd)->screenPrivateIndex].ptr)
#define CUBEREFLEX_SCREEN(s) \
    CubereflexScreen *rs = GET_CUBEREFLEX_SCREEN(s, GET_CUBEREFLEX_DISPLAY(s->display))

static void
drawBasicGround (CompScreen *s)
{
    float i;

    glPushMatrix();

    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glLoadIdentity();
    glTranslatef (0.0, 0.0, -DEFAULT_Z_CAMERA);

    i = cubereflexGetIntensity (s) * 2;

    glBegin (GL_QUADS);
    glColor4f (0.0, 0.0, 0.0, MAX (0.0, 1.0 - i) );
    glVertex2f (0.5, 0.0);
    glVertex2f (-0.5, 0.0);
    glColor4f (0.0, 0.0, 0.0, MIN (1.0, 1.0 - (i - 1.0) ) );
    glVertex2f (-0.5, -0.5);
    glVertex2f (0.5, -0.5);
    glEnd();

    if (cubereflexGetGroundSize (s) > 0.0)
    {
	glBegin (GL_QUADS);
	glColor4usv (cubereflexGetGroundColor1 (s) );
	glVertex2f (-0.5, -0.5);
	glVertex2f (0.5, -0.5);
	glColor4usv (cubereflexGetGroundColor2 (s) );
	glVertex2f (0.5, -0.5 + cubereflexGetGroundSize (s) );
	glVertex2f (-0.5, -0.5 + cubereflexGetGroundSize (s) );
	glEnd();
    }

    glColor4usv (defaultColor);

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

static Bool
cubereflexCheckOrientation (CompScreen              *s,
			    const ScreenPaintAttrib *sAttrib,
			    const CompTransform     *transform,
			    CompOutput              *outputPtr,
			    float                   points[3][3])
{
    CUBEREFLEX_SCREEN (s);
    CUBE_SCREEN (s);

    Bool status;

    UNWRAP (rs, cs, checkOrientation);
    status = (*cs->checkOrientation) (s, sAttrib, transform,
				      outputPtr, points);
    WRAP (rs, cs, checkOrientation, cubereflexCheckOrientation);

    if (rs->reflection)
	return !status;

    return status;
}

static void
cubereflexGetRotation (CompScreen *s,
		       float      *x,
		       float      *v)
{
    CUBE_SCREEN (s);
    CUBEREFLEX_SCREEN (s);

    UNWRAP (rs, cs, getRotation);
    (*cs->getRotation) (s, x, v);
    WRAP (rs, cs, getRotation, cubereflexGetRotation);

    if (cubereflexGetMode (s) == ModeAbove && *v > 0.0 && rs->reflection)
    {
	rs->vRot = *v;
	*v = 0.0;
    }
    else
	rs->vRot = 0.0;
}

static void
cubereflexClearTargetOutput (CompScreen *s,
			     float      xRotate,
			     float      vRotate)
{
    CUBEREFLEX_SCREEN (s);
    CUBE_SCREEN (s);

    if (rs->reflection)
	glCullFace (GL_BACK);

    UNWRAP (rs, cs, clearTargetOutput);
    (*cs->clearTargetOutput) (s, xRotate, rs->backVRotate);
    WRAP (rs, cs, clearTargetOutput, cubereflexClearTargetOutput);

    if (rs->reflection)
	glCullFace (GL_FRONT);
}

static void
cubereflexPaintTransformedOutput (CompScreen              *s,
				  const ScreenPaintAttrib *sAttrib,
				  const CompTransform     *transform,
				  Region                  region,
				  CompOutput              *output,
				  unsigned int            mask)
{
    static GLfloat light0Position[] = { -0.5f, 0.5f, -9.0f, 1.0f };

    CUBEREFLEX_SCREEN (s);
    CUBE_SCREEN (s);

    CompTransform sTransform = *transform;

    if (cs->invert == 1 && rs->first)
    {
	rs->first = FALSE;
	rs->reflection = TRUE;

	if (cs->grabIndex)
	{
	    CompTransform rTransform = *transform;
	    matrixTranslate (&rTransform, 0.0, -1.0, 0.0);
	    matrixScale (&rTransform, 1.0, -1.0, 1.0);

	    glCullFace (GL_FRONT);
	    UNWRAP (rs, s, paintTransformedOutput);
	    (*s->paintTransformedOutput) (s, sAttrib, &rTransform, region, output, mask);
	    WRAP (rs, s, paintTransformedOutput, cubereflexPaintTransformedOutput);
	    glCullFace (GL_BACK);
	    drawBasicGround (s);
	}
	else
	{
	    CompTransform rTransform = *transform;
	    CompTransform pTransform = identity;
	    float angle = 360.0 / ( (float) s->hsize * cs->nOutput);
	    float xRot, vRot, xRotate, xRotate2, vRotate;
	    float rYTrans;
	    double point[4] = { -0.5, -0.5, cs->distance, 1.0};
	    double point2[4] = { -0.5, 0.5, cs->distance, 1.0};

	    (*cs->getRotation) (s, &xRot, &vRot);

	    rs->backVRotate = 0.0;

	    xRotate  = xRot;
	    xRotate2 = xRot;
	    vRotate  = vRot;

	    if (vRotate < 0.0)
		xRotate += 180;

	    vRotate = fmod (fabs (vRotate), 180.0);
	    xRotate = fmod (fabs (xRotate), angle);
	    xRotate2 = fmod (fabs (xRotate2), angle);

	    if (vRotate >= 90.0)
		vRotate = 180.0 - vRotate;

	    if (xRotate >= angle / 2.0)
		xRotate = angle - xRotate;

	    if (xRotate2 >= angle / 2.0)
		xRotate2 = angle - xRotate2;

	    matrixRotate (&pTransform, xRotate, 0.0f, 1.0f, 0.0f);
	    matrixRotate (&pTransform, vRotate, cosf (xRotate * DEG2RAD),
			  0.0f, sinf (xRotate * DEG2RAD) );

	    MULTMV (pTransform.m, point);

	    pTransform = identity;

	    matrixRotate (&pTransform, xRotate2, 0.0f, 1.0f, 0.0f);
	    matrixRotate (&pTransform, vRotate, cosf (xRotate2 * DEG2RAD),
			  0.0f, sinf (xRotate2 * DEG2RAD) );

	    MULTMV (pTransform.m, point2);

	    switch (cubereflexGetMode (s) )
	    {

	    case ModeJumpyReflection:
		rs->yTrans     = 0.0;
		rYTrans        = (point[1] * 2.0);
		break;

	    case ModeDistance:
		rs->yTrans     = 0.0;
		rYTrans        = sqrt (0.5 + (cs->distance * cs->distance) )
				 * -2.0;
		break;

	    default:
		rs->yTrans     = -point[1] - 0.5;
		rYTrans        = point[1] - 0.5;
		break;

	    }

	    if (!cubereflexGetAutoZoom (s) ||
		((cs->rotationState != RotationManual) &&
		 cubereflexGetZoomManualOnly (s)))
		rs->zTrans = 0.0;
	    else
		rs->zTrans = -point2[2] + cs->distance;

	    if (cubereflexGetMode (s) == ModeAbove)
		rs->zTrans      = 0.0;

	    if (cubereflexGetMode (s) == ModeAbove && rs->vRot > 0.0)
	    {
		rs->backVRotate = rs->vRot;
		rs->yTrans      = 0.0;
		rYTrans         = 0.0;
		pTransform = identity;
		(*s->applyScreenTransform) (s, sAttrib, output, &pTransform);
		point[0] = point[1] = 0.0;
		point[2] = -cs->distance;
		point[3] = 1.0;
		MULTMV (pTransform.m, point);

		matrixTranslate (&rTransform, 0.0, 0.0, point[2]);
		matrixRotate (&rTransform, rs->vRot, 1.0, 0.0, 0.0);
		matrixScale (&rTransform, 1.0, -1.0, 1.0);
		matrixTranslate (&rTransform, 0.0, 1.0, 0.0);
		matrixTranslate (&rTransform, 0.0, 0.0, -point[2]);
	    }
	    else
	    {
		matrixTranslate (&rTransform, 0.0, rYTrans, rs->zTrans);
		matrixScale (&rTransform, 1.0, -1.0, 1.0);
	    }

	    glPushMatrix();

	    glLoadIdentity();
	    glScalef (1.0, -1.0, 1.0);
	    glLightfv (GL_LIGHT0, GL_POSITION, light0Position);
	    glPopMatrix();

	    glCullFace (GL_FRONT);
	    UNWRAP (rs, s, paintTransformedOutput);
	    (*s->paintTransformedOutput) (s, sAttrib, &rTransform, region, output, mask);
	    WRAP (rs, s, paintTransformedOutput, cubereflexPaintTransformedOutput);
	    glCullFace (GL_BACK);

	    glPushMatrix();
	    glLoadIdentity();
	    glLightfv (GL_LIGHT0, GL_POSITION, light0Position);
	    glPopMatrix();

	    if (cubereflexGetMode (s) == ModeAbove && rs->vRot > 0.0)
	    {
		int j;
		float i, c;
		float v = MIN (1.0, rs->vRot / 30.0);
		float col1[4], col2[4];

		glPushMatrix();

		glEnable (GL_BLEND);
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glLoadIdentity();
		glTranslatef (0.0, 0.0, -DEFAULT_Z_CAMERA);

		i = cubereflexGetIntensity (s) * 2;
		c = cubereflexGetIntensity (s);

		glBegin (GL_QUADS);
		glColor4f (0.0, 0.0, 0.0,
			   ((1 - v) * MAX (0.0, 1.0 - i)) + (v * c));
		glVertex2f (0.5, v / 2.0);
		glVertex2f (-0.5, v / 2.0);
		glColor4f (0.0, 0.0, 0.0,
			   ((1 - v) * MIN (1.0, 1.0 - (i - 1.0))) + (v * c));
		glVertex2f (-0.5, -0.5);
		glVertex2f (0.5, -0.5);
		glEnd();

		for (j = 0; j < 4; j++)
		{
		    col1[j] = (1.0 - v) * cubereflexGetGroundColor1 (s) [j] +
			      (v * (cubereflexGetGroundColor1 (s) [j] +
				    cubereflexGetGroundColor2 (s) [j]) * 0.5);
		    col1[j] /= 0xffff;
		    col2[j] = (1.0 - v) * cubereflexGetGroundColor2 (s) [j] +
			      (v * (cubereflexGetGroundColor1 (s) [j] +
				    cubereflexGetGroundColor2 (s) [j]) * 0.5);
		    col2[j] /= 0xffff;
		}

		if (cubereflexGetGroundSize (s) > 0.0)
		{
		    glBegin (GL_QUADS);
		    glColor4fv (col1);
		    glVertex2f (-0.5, -0.5);
		    glVertex2f (0.5, -0.5);
		    glColor4fv (col2);
		    glVertex2f (0.5, -0.5 +
				((1 - v) * cubereflexGetGroundSize (s)) + v);
		    glVertex2f (-0.5, -0.5 +
				((1 - v) * cubereflexGetGroundSize (s)) + v);
		    glEnd();
		}

		glColor4usv (defaultColor);

		glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
		glDisable (GL_BLEND);
		glPopMatrix();
	    }
	    else
		drawBasicGround (s);
	}
	
	memset (cs->capsPainted, 0, sizeof (Bool) * s->nOutputDev);
	rs->reflection = FALSE;
    }


    matrixTranslate (&sTransform, 0.0, rs->yTrans, rs->zTrans);

    UNWRAP (rs, s, paintTransformedOutput);
    (*s->paintTransformedOutput) (s, sAttrib, &sTransform, region, output, mask);
    WRAP (rs, s, paintTransformedOutput, cubereflexPaintTransformedOutput);
}

static Bool
cubereflexPaintOutput (CompScreen              *s,
		       const ScreenPaintAttrib *sAttrib,
		       const CompTransform     *transform,
		       Region                  region,
		       CompOutput              *output,
		       unsigned int            mask)
{
    Bool status;

    CUBEREFLEX_SCREEN (s);

    if (rs->last != output)
	rs->first = TRUE;

    rs->last = output;

    UNWRAP (rs, s, paintOutput);
    status = (*s->paintOutput) (s, sAttrib, transform, region, output, mask);
    WRAP (rs, s, paintOutput, cubereflexPaintOutput);

    return status;
}

static void
cubereflexDonePaintScreen (CompScreen * s)
{
    CUBEREFLEX_SCREEN (s);

    rs->first      = TRUE;
    rs->yTrans     = 0.0;
    rs->zTrans     = 0.0;

    UNWRAP (rs, s, donePaintScreen);
    (*s->donePaintScreen) (s);
    WRAP (rs, s, donePaintScreen, cubereflexDonePaintScreen);
}


static Bool
cubereflexInitDisplay (CompPlugin  *p,
		       CompDisplay *d)
{
    CubereflexDisplay *rd;
    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, "cubereflex", CompLogLevelError,
			"cube ABI version mismatch");
	return FALSE;
    }

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

    if (cubeDisplayPrivateIndex < 0)
	return FALSE;

    rd = malloc (sizeof (CubereflexDisplay) );

    if (!rd)
	return FALSE;

    rd->screenPrivateIndex = allocateScreenPrivateIndex (d);

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

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

    return TRUE;
}

static void
cubereflexFiniDisplay (CompPlugin  *p,
		       CompDisplay *d)
{
    CUBEREFLEX_DISPLAY (d);

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

static Bool
cubereflexInitScreen (CompPlugin *p,
		      CompScreen *s)
{
    CubereflexScreen *rs;

    CUBEREFLEX_DISPLAY (s->display);
    CUBE_SCREEN (s);

    rs = malloc (sizeof (CubereflexScreen) );

    if (!rs)
	return FALSE;

    s->privates[rd->screenPrivateIndex].ptr = rs;

    rs->reflection = FALSE;
    rs->first      = TRUE;
    rs->last       = NULL;
    rs->yTrans     = 0.0;
    rs->zTrans     = 0.0;

    WRAP (rs, s, paintTransformedOutput, cubereflexPaintTransformedOutput);
    WRAP (rs, s, paintOutput, cubereflexPaintOutput);
    WRAP (rs, s, donePaintScreen, cubereflexDonePaintScreen);
    WRAP (rs, cs, clearTargetOutput, cubereflexClearTargetOutput);
    WRAP (rs, cs, getRotation, cubereflexGetRotation);
    WRAP (rs, cs, checkOrientation, cubereflexCheckOrientation);

    return TRUE;
}

static void
cubereflexFiniScreen (CompPlugin *p,
		      CompScreen *s)
{
    CUBEREFLEX_SCREEN (s);
    CUBE_SCREEN (s);

    UNWRAP (rs, s, paintTransformedOutput);
    UNWRAP (rs, s, paintOutput);
    UNWRAP (rs, s, donePaintScreen);

    UNWRAP (rs, cs, clearTargetOutput);
    UNWRAP (rs, cs, getRotation);
    UNWRAP (rs, cs, checkOrientation);

    free (rs);
}

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

    if (displayPrivateIndex < 0)
	return FALSE;

    return TRUE;
}

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

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

CompPluginVTable cubereflexVTable = {

    "cubereflex",
    cubereflexGetVersion,
    0,
    cubereflexInit,
    cubereflexFini,
    cubereflexInitDisplay,
    cubereflexFiniDisplay,
    cubereflexInitScreen,
    cubereflexFiniScreen,
    0,
    0,
    0,
    0,
    0,
    0
};

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


syntax highlighted by Code2HTML, v. 0.9.1