/*	Direct3D OpenGL driver
 *
 * Copyright 2001-2002 TransGaming Technologies Inc.
 */
#include "config.h"

#include <assert.h>
#include <stdlib.h>
#include <math.h> /* for M_PI */

#include "wine/debug.h"
#include "winbase.h"
#include "ddrawi.h"
#include "d3d9.h"
#include "d3dhal.h"
#include "d3dgl_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

#define GL_LIGHT_DATA_CHANGE 0x10
#define GL_LIGHT_VIEW_CHANGE 0x20
#define GL_LIGHT_ENABLED     0x40

static void GL_LoadLightPosition(GL_D3D_light *light)
{
    GLenum gl = GL_LIGHT0 + light->num;

    TRACE("positioning GL light %lu\n", light->num);
    glLightfv(gl, GL_POSITION, (const float *)&light->pos);
    if ((light->l.dltType == D3DLIGHT_SPOT) || (light->l.dltType == D3DLIGHT_GLSPOT))
	glLightfv(gl, GL_SPOT_DIRECTION, (const float *)&(light->l.dvDirection));

    light->dwFlags &= ~GL_LIGHT_VIEW_CHANGE;
}

void GL_LoadLightPositions(GL_D3D_priv* priv, BOOL new_view)
{
    DWORD fl;
    unsigned u;

    fl = D3DLIGHT_ACTIVE;
    if (!new_view) fl |= GL_LIGHT_VIEW_CHANGE;

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_VIEW]);
    for (u=0; u<priv->num_lights; u++)
	if ((priv->light[u].dwFlags & fl) == fl)
	    GL_LoadLightPosition(&priv->light[u]);
    glPopMatrix();
}

static void GL_LoadLightData(GL_D3D_priv* priv, GL_D3D_light *light)
{
    GLenum gl = GL_LIGHT0 + light->num;

    TRACE("loading GL light %lu\n", light->num);
    glLightfv(gl, GL_DIFFUSE, (float *)&(light->l.dcvDiffuse));
    glLightfv(gl, GL_SPECULAR, (float *)&(light->l.dcvSpecular));
    glLightfv(gl, GL_AMBIENT, (float *)&(light->l.dcvAmbient));

    /* I can't find a GL parameter for dvRange, and no exact match for dvFalloff */
    glLightf(gl, GL_CONSTANT_ATTENUATION, light->l.dvAttenuation0);
    glLightf(gl, GL_LINEAR_ATTENUATION, light->l.dvAttenuation1);
    glLightf(gl, GL_QUADRATIC_ATTENUATION, light->l.dvAttenuation2);
    if (light->l.dltType == D3DLIGHT_SPOT) {
	float theta, ctheta, phi, cphi, pos, cpos, lit;
	theta = light->l.dvTheta / 2;
	phi = light->l.dvPhi / 2;
	if (theta < phi) {
	    /* Direct3D spotlights don't work exactly like OpenGL spotlights,
	     * but to approximate it as closely as possible, we'll map the
	     * dvTheta and dvFalloff to the OpenGL spot exponent by trying to
	     * make some point between dvTheta and dvPhi have an equal
	     * brightness in the two models */
	    /* choosing a point near phi makes the cutoff smoother */
	    pos = (phi - theta)*7/8 + theta;

	    ctheta = cos(theta);
	    cphi = cos(phi);
	    cpos = cos(pos);
	    /* Direct3D: light = ((cos(p)-cos(phi))/(cos(theta)-cos(phi)))^falloff */
	    lit = (cpos-cphi)/(ctheta-cphi);
	    /* ignore falloff, since we're just approximating anyway */
	    /* OpenGL: light = cos(p)^exponent */
	    /*   i.e.: exponent = ln light / ln cos(p) */
	    glLightf(gl, GL_SPOT_EXPONENT, log(lit) / log(cpos));
	} else glLightf(gl, GL_SPOT_EXPONENT, 0.0);
	glLightf(gl, GL_SPOT_CUTOFF, phi * 180 / M_PI);
    }
    else if (light->l.dltType == D3DLIGHT_GLSPOT) {
	/* not sure about this */
	glLightf(gl, GL_SPOT_EXPONENT, light->l.dvFalloff);
	glLightf(gl, GL_SPOT_CUTOFF, light->l.dvPhi * 90 / M_PI);
    } else {
	glLightf(gl, GL_SPOT_EXPONENT, 0.0);
	glLightf(gl, GL_SPOT_CUTOFF, 180);
    }
    light->dwFlags |= GL_LIGHT_VIEW_CHANGE;
    light->dwFlags &= ~GL_LIGHT_DATA_CHANGE;
}

void GL_LoadLights(GL_D3D_priv* priv, BOOL new_view)
{
    unsigned u;

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf((const float *)&priv->state.xfrm[D3DTRANSFORMSTATE_VIEW]);
    /* first a disable-lights pass, so the next pass can freely reallocate as needed */
    for (u=0; u<priv->num_lights; u++) {
	GL_D3D_light *light = &priv->light[u];
	if ((light->dwFlags & (D3DLIGHT_ACTIVE|GL_LIGHT_ENABLED)) == GL_LIGHT_ENABLED) {
	    /* do we still own this light? */
	    if (priv->light_user[light->num] != (u+1)) continue;
	    /* disable light */
	    glDisable(GL_LIGHT0 + light->num);
	    light->dwFlags &= ~GL_LIGHT_ENABLED;
	    priv->lights &= ~(1 << light->num);
	}
    }
    /* load/enable-lights pass */
    for (u=0; u<priv->num_lights; u++) {
	GL_D3D_light *light = &priv->light[u];
	if (!(light->dwFlags & D3DLIGHT_ACTIVE)) continue;
	if (!(light->dwFlags & GL_LIGHT_ENABLED)) {
	    /* need to allocate GL light - is the previous allocation still free? */
	    if (priv->light_user[light->num] != (u+1)) {
		/* it's taken, allocate another
		 * (hmm, we could use a better algorithm than this)
		 */
		unsigned u;
		for (u=0; u<32 && (priv->lights & (1 << u)); u++);
		if (u >= priv->lpCaps->dwMaxActiveLights) {
		    ERR("too many allocated lights, can't activate!\n");
		    light->dwFlags &= ~D3DLIGHT_ACTIVE;
		    continue;
		}
		light->num = u;
		/* light now needs to be loaded */
		light->dwFlags |= GL_LIGHT_DATA_CHANGE;
	    }
	    else {
		/* the position may have changed */
		light->dwFlags |= GL_LIGHT_VIEW_CHANGE;
	    }
	    /* enable light */
	    priv->light_user[light->num] = (u+1);
	    priv->lights |= (1 << light->num);
	    glEnable(GL_LIGHT0 + light->num);
	    light->dwFlags |= GL_LIGHT_ENABLED;
	}
	if (light->dwFlags & GL_LIGHT_DATA_CHANGE)
	    GL_LoadLightData(priv, light);
	if (new_view || (light->dwFlags & GL_LIGHT_VIEW_CHANGE))
	    GL_LoadLightPosition(light);
    }
    glPopMatrix();
}

HRESULT D3D_GL_create_light(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2CREATELIGHT dat = (LPD3DHAL_DP2CREATELIGHT)data1;
    DWORD dwSize = (dat->dwIndex+1) * sizeof(GL_D3D_light);
    GL_D3D_light *light;
    light = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, priv->light, dwSize);
    if (!light) {
	ERR("out of memory\n");
	return E_OUTOFMEMORY;
    }
    priv->light = light;
    priv->num_lights = dat->dwIndex+1;
    return D3D_OK;
}

HRESULT D3D_GL_light_enable(GL_D3D_priv* priv, DWORD dwIndex)
{
    GL_D3D_light *light = &priv->light[dwIndex];
    light->dwFlags |= D3DLIGHT_ACTIVE;
    priv->glFlags |= GLF_NEW_LIGHT_DATA;
    return D3D_OK;
}

HRESULT D3D_GL_light_disable(GL_D3D_priv* priv, DWORD dwIndex)
{
    GL_D3D_light *light = &priv->light[dwIndex];
    light->dwFlags &= ~D3DLIGHT_ACTIVE;
    priv->glFlags |= GLF_NEW_LIGHT_DATA;
    return D3D_OK;
}

HRESULT D3D_GL_set_light_data(GL_D3D_priv* priv, DWORD dwIndex, LPD3DLIGHT7 ldata)
{
    GL_D3D_light *light = &priv->light[dwIndex];
    light->l = *ldata;
    if (light->l.dltType == D3DLIGHT_DIRECTIONAL) {
	light->pos[0] = -ldata->dvDirection.x;
	light->pos[1] = -ldata->dvDirection.y;
	light->pos[2] = -ldata->dvDirection.z;
	light->pos[3] = 0.0;
    }
    else {
	light->pos[0] = ldata->dvPosition.x;
	light->pos[1] = ldata->dvPosition.y;
	light->pos[2] = ldata->dvPosition.z;
	light->pos[3] = 1.0;
    }
    light->dwFlags |= GL_LIGHT_DATA_CHANGE;
    priv->glFlags |= GLF_NEW_LIGHT_DATA;
    return D3D_OK;
}

HRESULT D3D_GL_set_light(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2SETLIGHT dat = (LPD3DHAL_DP2SETLIGHT)data1;
    LPD3DLIGHT7 ldata = (LPD3DLIGHT7)data2;

    if (GL_IN_BLOCK(priv)) {
	GL_D3D_light_state *light = NULL;
	if (priv->block->num_lights <= dat->dwIndex) {
	    DWORD dwSize = (dat->dwIndex+1) * sizeof(GL_D3D_light_state);
	    light = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, priv->block->light, dwSize);
	    if (!light) {
		ERR("out of memory\n");
		return E_OUTOFMEMORY;
	    }
	    priv->block->light = light;
	    priv->block->num_lights = dat->dwIndex+1;
	}
	light = &priv->block->light[dat->dwIndex];
	switch (dat->dwDataType) {
	case D3DHAL_SETLIGHT_ENABLE:
	    light->dwEnable = GL_D3D_LIGHT_ENABLE;
	    break;
	case D3DHAL_SETLIGHT_DISABLE:
	    light->dwEnable = GL_D3D_LIGHT_DISABLE;
	    break;
	case D3DHAL_SETLIGHT_DATA:
	    if (!light->data) {
		light->data = HeapAlloc(GetProcessHeap(), 0, sizeof(D3DLIGHT7));
		if (!light->data) {
		    ERR("out of memory\n");
		    return E_OUTOFMEMORY;
		}
	    }
	    *light->data = *ldata;
	    break;
	}
    }
    else {
	switch (dat->dwDataType) {
	case D3DHAL_SETLIGHT_ENABLE:
	    return D3D_GL_light_enable(priv, dat->dwIndex);
	case D3DHAL_SETLIGHT_DISABLE:
	    return D3D_GL_light_disable(priv, dat->dwIndex);
	case D3DHAL_SETLIGHT_DATA:
	    return D3D_GL_set_light_data(priv, dat->dwIndex, ldata);
	}
    }
    return D3D_OK;
}
