/*
 * Compiz cube gears plugin
 *
 * gears.c
 *
 * This is an example plugin to show how to render something inside
 * of the transparent cube
 *
 * 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.
 *
 * Based on glxgears.c:
 *    http://cvsweb.xfree86.org/cvsweb/xc/programs/glxgears/glxgears.c
 */

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

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

static int displayPrivateIndex;

static int cubeDisplayPrivateIndex;

typedef struct _GearsDisplay
{
    int screenPrivateIndex;

}
GearsDisplay;

typedef struct _GearsScreen
{
    DonePaintScreenProc    donePaintScreen;
    PreparePaintScreenProc preparePaintScreen;

    CubeClearTargetOutputProc clearTargetOutput;
    CubePaintInsideProc       paintInside;

    Bool damage;

    float contentRotation;
    GLuint gear1, gear2, gear3;
    float angle;
    float a1, a2, a3;
}
GearsScreen;

#define GET_GEARS_DISPLAY(d) \
    ((GearsDisplay *) (d)->base.privates[displayPrivateIndex].ptr)
#define GEARS_DISPLAY(d) \
    GearsDisplay *gd = GET_GEARS_DISPLAY(d);

#define GET_GEARS_SCREEN(s, gd) \
    ((GearsScreen *) (s)->base.privates[(gd)->screenPrivateIndex].ptr)
#define GEARS_SCREEN(s) \
    GearsScreen *gs = GET_GEARS_SCREEN(s, GET_GEARS_DISPLAY(s->display))


static void
gear (GLfloat inner_radius,
      GLfloat outer_radius,
      GLfloat width,
      GLint   teeth,
      GLfloat tooth_depth)
{
    GLint i;
    GLfloat r0, r1, r2, maxr2, minr2;
    GLfloat angle, da;
    GLfloat u, v, len;

    r0 = inner_radius;
    r1 = outer_radius - tooth_depth / 2.0;
    maxr2 = r2 = outer_radius + tooth_depth / 2.0;
    minr2 = r2;

    da = 2.0 * M_PI / teeth / 4.0;

    glShadeModel (GL_FLAT);

    glNormal3f (0.0, 0.0, 1.0);

    /* draw front face */
    glBegin (GL_QUAD_STRIP);

    for (i = 0; i <= teeth; i++)
    {
	angle = i * 2.0 * M_PI / teeth;
	glVertex3f (r0 * cos (angle), r0 * sin (angle), width * 0.5);
	glVertex3f (r1 * cos (angle), r1 * sin (angle), width * 0.5);

	if (i < teeth)
	{
	    glVertex3f (r0 * cos (angle), r0 * sin (angle), width * 0.5);
	    glVertex3f (r1 * cos (angle + 3 * da), r1 * sin (angle + 3 * da),
			width * 0.5);
	}
    }

    glEnd();

    /* draw front sides of teeth */
    glBegin (GL_QUADS);

    for (i = 0; i < teeth; i++)
    {
	angle = i * 2.0 * M_PI / teeth;

	glVertex3f (r1 * cos (angle), r1 * sin (angle), width * 0.5);
	glVertex3f (r2 * cos (angle + da), r2 * sin (angle + da), width * 0.5);
	glVertex3f (r2 * cos (angle + 2 * da), r2 * sin (angle + 2 * da),
		    width * 0.5);
	glVertex3f (r1 * cos (angle + 3 * da), r1 * sin (angle + 3 * da),
		    width * 0.5);
	r2 = minr2;
    }

    r2 = maxr2;

    glEnd();

    glNormal3f (0.0, 0.0, -1.0);

    /* draw back face */
    glBegin (GL_QUAD_STRIP);

    for (i = 0; i <= teeth; i++)
    {
	angle = i * 2.0 * M_PI / teeth;
	glVertex3f (r1 * cos (angle), r1 * sin (angle), -width * 0.5);
	glVertex3f (r0 * cos (angle), r0 * sin (angle), -width * 0.5);

	if (i < teeth)
	{
	    glVertex3f (r1 * cos (angle + 3 * da), r1 * sin (angle + 3 * da),
			-width * 0.5);
	    glVertex3f (r0 * cos (angle), r0 * sin (angle), -width * 0.5);
	}
    }

    glEnd();

    /* draw back sides of teeth */
    glBegin (GL_QUADS);
    da = 2.0 * M_PI / teeth / 4.0;

    for (i = 0; i < teeth; i++)
    {
	angle = i * 2.0 * M_PI / teeth;

	glVertex3f (r1 * cos (angle + 3 * da), r1 * sin (angle + 3 * da),
		    -width * 0.5);
	glVertex3f (r2 * cos (angle + 2 * da), r2 * sin (angle + 2 * da),
		    -width * 0.5);
	glVertex3f (r2 * cos (angle + da), r2 * sin (angle + da), -width * 0.5);
	glVertex3f (r1 * cos (angle), r1 * sin (angle), -width * 0.5);
	r2 = minr2;
    }

    r2 = maxr2;

    glEnd();

    /* draw outward faces of teeth */
    glBegin (GL_QUAD_STRIP);

    for (i = 0; i < teeth; i++)
    {
	angle = i * 2.0 * M_PI / teeth;

	glVertex3f (r1 * cos (angle), r1 * sin (angle), width * 0.5);
	glVertex3f (r1 * cos (angle), r1 * sin (angle), -width * 0.5);
	u = r2 * cos (angle + da) - r1 * cos (angle);
	v = r2 * sin (angle + da) - r1 * sin (angle);
	len = sqrt (u * u + v * v);
	u /= len;
	v /= len;
	glNormal3f (v, -u, 0.0);
	glVertex3f (r2 * cos (angle + da), r2 * sin (angle + da), width * 0.5);
	glVertex3f (r2 * cos (angle + da), r2 * sin (angle + da), -width * 0.5);
	glNormal3f (cos (angle + 1.5 * da), sin (angle + 1.5 * da), 0.0);
	glVertex3f (r2 * cos (angle + 2 * da), r2 * sin (angle + 2 * da),
		    width * 0.5);
	glVertex3f (r2 * cos (angle + 2 * da), r2 * sin (angle + 2 * da),
		    -width * 0.5);
	u = r1 * cos (angle + 3 * da) - r2 * cos (angle + 2 * da);
	v = r1 * sin (angle + 3 * da) - r2 * sin (angle + 2 * da);
	glNormal3f (v, -u, 0.0);
	glVertex3f (r1 * cos (angle + 3 * da), r1 * sin (angle + 3 * da),
		    width * 0.5);
	glVertex3f (r1 * cos (angle + 3 * da), r1 * sin (angle + 3 * da),
		    -width * 0.5);
	glNormal3f (cos (angle + 3.5 * da), sin (angle + 3.5 * da), 0.0);
	r2 = minr2;
    }

    r2 = maxr2;

    glVertex3f (r1 * cos (0), r1 * sin (0), width * 0.5);
    glVertex3f (r1 * cos (0), r1 * sin (0), -width * 0.5);

    glEnd();

    glShadeModel (GL_SMOOTH);

    /* draw inside radius cylinder */
    glBegin (GL_QUAD_STRIP);

    for (i = 0; i <= teeth; i++)
    {
	angle = i * 2.0 * M_PI / teeth;
	glNormal3f (-cos (angle), -sin (angle), 0.0);
	glVertex3f (r0 * cos (angle), r0 * sin (angle), -width * 0.5);
	glVertex3f (r0 * cos (angle), r0 * sin (angle), width * 0.5);
    }

    glEnd();
}

static void
gearsClearTargetOutput (CompScreen *s,
			float      xRotate,
			float      vRotate)
{
    GEARS_SCREEN (s);
    CUBE_SCREEN (s);

    UNWRAP (gs, cs, clearTargetOutput);
    (*cs->clearTargetOutput) (s, xRotate, vRotate);
    WRAP (gs, cs, clearTargetOutput, gearsClearTargetOutput);

    glClear (GL_DEPTH_BUFFER_BIT);
}

static void
gearsPaintInside (CompScreen              *s,
		  const ScreenPaintAttrib *sAttrib,
		  const CompTransform     *transform,
		  CompOutput              *output,
		  int                     size)
{
    GEARS_SCREEN (s);
    CUBE_SCREEN (s);

    static GLfloat white[4] = { 1.0, 1.0, 1.0, 1.0 };

    ScreenPaintAttrib sA = *sAttrib;

    sA.yRotate += cs->invert * (360.0f / size) *
		  (cs->xRotations - (s->x * cs->nOutput));

    CompTransform mT = *transform;

    (*s->applyScreenTransform) (s, &sA, output, &mT);

    glPushMatrix();
    glLoadMatrixf (mT.m);
    glTranslatef (cs->outputXOffset, -cs->outputYOffset, 0.0f);
    glScalef (cs->outputXScale, cs->outputYScale, 1.0f);

    Bool enabledCull = FALSE;

    glPushAttrib (GL_COLOR_BUFFER_BIT | GL_TEXTURE_BIT);

    glDisable (GL_BLEND);

    if (!glIsEnabled (GL_CULL_FACE) )
    {
	enabledCull = TRUE;
	glEnable (GL_CULL_FACE);
    }

    glPushMatrix();

    glRotatef (gs->contentRotation, 0.0, 1.0, 0.0);

    glScalef (0.05, 0.05, 0.05);
    glColor4usv (defaultColor);

    glEnable (GL_NORMALIZE);
    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT1);
    glDisable (GL_COLOR_MATERIAL);

    glEnable (GL_DEPTH_TEST);
    glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glPushMatrix();
    glTranslatef (-3.0, -2.0, 0.0);
    glRotatef (gs->angle, 0.0, 0.0, 1.0);
    glCallList (gs->gear1);
    glPopMatrix();

    glPushMatrix();
    glTranslatef (3.1, -2.0, 0.0);
    glRotatef (-2.0 * gs->angle - 9.0, 0.0, 0.0, 1.0);
    glCallList (gs->gear2);
    glPopMatrix();

    glPushMatrix();
    glTranslatef (-3.1, 4.2, 0.0);
    glRotatef (-2.0 * gs->angle - 25.0, 0.0, 0.0, 1.0);
    glCallList (gs->gear3);
    glPopMatrix();

    glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, white);

    glPopMatrix();

    glDisable (GL_LIGHT1);
    glDisable (GL_NORMALIZE);
    glEnable (GL_COLOR_MATERIAL);

    if (!s->lighting)
	glDisable (GL_LIGHTING);

    glDisable (GL_DEPTH_TEST);

    if (enabledCull)
	glDisable (GL_CULL_FACE);

    glPopMatrix();
    glPopAttrib();

    gs->damage = TRUE;

    UNWRAP (gs, cs, paintInside);
    (*cs->paintInside) (s, sAttrib, transform, output, size);
    WRAP (gs, cs, paintInside, gearsPaintInside);
}

static void
gearsPreparePaintScreen (CompScreen *s,
			 int        ms)
{
    GEARS_SCREEN (s);

    gs->contentRotation += ms * 360.0 / 20000.0;
    gs->contentRotation = fmod (gs->contentRotation, 360.0);
    gs->angle += ms * 360.0 / 8000.0;
    gs->angle = fmod (gs->angle, 360.0);
    gs->a1 += ms * 360.0 / 3000.0;
    gs->a1 = fmod (gs->a1, 360.0);
    gs->a2 += ms * 360.0 / 2000.0;
    gs->a2 = fmod (gs->a2, 360.0);
    gs->a3 += ms * 360.0 / 1000.0;
    gs->a3 = fmod (gs->a3, 360.0);

    UNWRAP (gs, s, preparePaintScreen);
    (*s->preparePaintScreen) (s, ms);
    WRAP (gs, s, preparePaintScreen, gearsPreparePaintScreen);
}

static void
gearsDonePaintScreen (CompScreen * s)
{
    GEARS_SCREEN (s);

    if (gs->damage)
    {
	damageScreen (s);
	gs->damage = FALSE;
    }

    UNWRAP (gs, s, donePaintScreen);
    (*s->donePaintScreen) (s);
    WRAP (gs, s, donePaintScreen, gearsDonePaintScreen);
}


static Bool
gearsInitDisplay (CompPlugin  *p,
		  CompDisplay *d)
{
    GearsDisplay *gd;

    if (!checkPluginABI ("core", CORE_ABIVERSION) ||
	!checkPluginABI ("cube", CUBE_ABIVERSION))
	return FALSE;

    if (!getPluginDisplayIndex (d, "cube", &cubeDisplayPrivateIndex))
	return FALSE;

    gd = malloc (sizeof (GearsDisplay));

    if (!gd)
	return FALSE;

    gd->screenPrivateIndex = allocateScreenPrivateIndex (d);

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

    d->base.privates[displayPrivateIndex].ptr = gd;

    return TRUE;
}

static void
gearsFiniDisplay (CompPlugin  *p,
		  CompDisplay *d)
{
    GEARS_DISPLAY (d);

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

static Bool
gearsInitScreen (CompPlugin *p,
		 CompScreen *s)
{
    GearsScreen *gs;

    static GLfloat pos[4]         = { 5.0, 5.0, 10.0, 0.0 };
    static GLfloat red[4]         = { 0.8, 0.1, 0.0, 1.0 };
    static GLfloat green[4]       = { 0.0, 0.8, 0.2, 1.0 };
    static GLfloat blue[4]        = { 0.2, 0.2, 1.0, 1.0 };
    static GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, 0.3f };
    static GLfloat diffuseLight[] = { 0.5f, 0.5f, 0.5f, 0.5f };

    GEARS_DISPLAY (s->display);

    CUBE_SCREEN (s);

    gs = malloc (sizeof (GearsScreen) );

    if (!gs)
	return FALSE;

    s->base.privates[gd->screenPrivateIndex].ptr = gs;

    glLightfv (GL_LIGHT1, GL_AMBIENT, ambientLight);
    glLightfv (GL_LIGHT1, GL_DIFFUSE, diffuseLight);
    glLightfv (GL_LIGHT1, GL_POSITION, pos);

    gs->damage = FALSE;

    gs->contentRotation = 0.0;

    gs->gear1 = glGenLists (1);
    glNewList (gs->gear1, GL_COMPILE);
    glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
    gear (1.0, 4.0, 1.0, 20, 0.7);
    glEndList();

    gs->gear2 = glGenLists (1);
    glNewList (gs->gear2, GL_COMPILE);
    glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
    gear (0.5, 2.0, 2.0, 10, 0.7);
    glEndList();

    gs->gear3 = glGenLists (1);
    glNewList (gs->gear3, GL_COMPILE);
    glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
    gear (1.3, 2.0, 0.5, 10, 0.7);
    glEndList();

    gs->angle = 0.0;
    gs->a1    = 0.0;
    gs->a2    = 0.0;
    gs->a3    = 0.0;

    WRAP (gs, s, donePaintScreen, gearsDonePaintScreen);
    WRAP (gs, s, preparePaintScreen, gearsPreparePaintScreen);
    WRAP (gs, cs, clearTargetOutput, gearsClearTargetOutput);
    WRAP (gs, cs, paintInside, gearsPaintInside);

    return TRUE;
}

static void
gearsFiniScreen (CompPlugin *p,
		 CompScreen *s)
{
    GEARS_SCREEN (s);
    CUBE_SCREEN (s);

    glDeleteLists (gs->gear1, 1);
    glDeleteLists (gs->gear2, 1);
    glDeleteLists (gs->gear3, 1);

    UNWRAP (gs, s, donePaintScreen);
    UNWRAP (gs, s, preparePaintScreen);

    UNWRAP (gs, cs, clearTargetOutput);
    UNWRAP (gs, cs, paintInside);

    free (gs);
}

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

    if (displayPrivateIndex < 0)
	return FALSE;

    return TRUE;
}

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

static CompBool
gearsInitObject (CompPlugin *p,
		 CompObject *o)
{
    static InitPluginObjectProc dispTab[] = {
	(InitPluginObjectProc) 0, /* InitCore */
	(InitPluginObjectProc) gearsInitDisplay,
	(InitPluginObjectProc) gearsInitScreen
    };

    RETURN_DISPATCH (o, dispTab, ARRAY_SIZE (dispTab), TRUE, (p, o));
}

static void
gearsFiniObject (CompPlugin *p,
		 CompObject *o)
{
    static FiniPluginObjectProc dispTab[] = {
	(FiniPluginObjectProc) 0, /* FiniCore */
	(FiniPluginObjectProc) gearsFiniDisplay,
	(FiniPluginObjectProc) gearsFiniScreen
    };

    DISPATCH (o, dispTab, ARRAY_SIZE (dispTab), (p, o));
}

CompPluginVTable gearsVTable = {
    "gears",
    0,
    gearsInit,
    gearsFini,
    gearsInitObject,
    gearsFiniObject,
    0,
    0
};

CompPluginVTable *
getCompPluginInfo20070830 (void)
{
    return &gearsVTable;
}
