#include "Light.h"
#include "../../Core/ResourceManager.h"

Camera* Light::_shadowCamera = 0;
RenderTarget* Light::_shadowMap = 0;
RenderTarget* Light::_resolvedShadowMap = 0;
DepthStencilBuffer* Light::_depthStencilBuffer = 0;
Shader* Light::_shadowShader = 0;

Light::Light()
: Group(), _shadowed(true), _enableShadows(true), _sceneCamera(0), _lightViewMatrixVariable(0), _lightProjectionMatrixVariable(0),
_diffuseVariable(0), _specularVariable(0), _shadowMapVariable(0), _shadowedVariable(0)
{
    if (_shadowShader == 0)
        _shadowShader = ResourceManager::instance().createShader(L"shadow.fxo");
    
    if (_shadowMap == 0) {
        _shadowMap = ResourceManager::instance().createRenderTarget(512, 512, 4);
    }

    if (_resolvedShadowMap == 0) {
        _resolvedShadowMap = ResourceManager::instance().createRenderTarget(512, 512, 1);
    }

    if (_depthStencilBuffer == 0) {
        _depthStencilBuffer = ResourceManager::instance().createDepthStencilBuffer(512, 512, 4);
    }

    if (_shadowCamera == 0) {
        _shadowCamera = new Camera();
        _shadowCamera->setRenderTargetImplementation(Camera::TEXTURE);
        _shadowCamera->setShader(_shadowShader);
        _shadowCamera->attachOutput(_shadowMap, 0);
        _shadowCamera->attachDepthStencilBuffer(_depthStencilBuffer);
    }

    _diffuse[0]  = 1.0f;    _diffuse[1]  = 1.0f;    _diffuse[2]  =  1.0f;   _diffuse[3]  =  1.0f;
    _specular[0] = 1.0f;    _specular[1] = 1.0f;    _specular[2] =  1.0f;   _specular[3] =  1.0f;
}

Light::~Light()
{
}

bool Light::getShadowed()
{
    return _shadowed;
}

void Light::setShadowed(bool shadowed)
{
    _shadowed = shadowed;
}

void Light::enableShadows(bool state)
{
    _enableShadows = state;
}

Camera* Light::getShadowCamera()
{
    return _shadowCamera;
}

RenderTarget* Light::getShadowMap()
{
    return _shadowMap;
}

RenderTarget* Light::getResolvedShadowMap()
{
    return _resolvedShadowMap;
}

void Light::setSceneCamera(Camera* camera)
{
    if (_sceneCamera != 0)
        _shader->detach(_sceneCamera);

    _sceneCamera = camera;

    if (_sceneCamera != 0)
        _shader->attach(_sceneCamera);
}

float* Light::getDiffuse()
{
    return &_diffuse[0];
}

float* Light::getSpecular()
{
    return &_specular[0];
}

void Light::setDiffuse(float r, float g, float b, float a)
{
    _diffuse[0] = r; _diffuse[1] = g; _diffuse[2] = b; _diffuse[3] = a;
}

void Light::setSpecular(float r, float g, float b, float a)
{
    _specular[0] = r; _specular[1] = g; _specular[2] = b; _specular[3] = a;
}


DirectionalLight::DirectionalLight()
: Light(), _direction(Vector3(0.0f, 0.0f, 0.0f)), _directionVariable(0)
{
    _shader = ResourceManager::instance().createShader(L"directional_light.fxo");

    if (_shader) {
        _shadowedVariable = _shader->createScalarVariable("g_ShadowEnabled");
        _lightViewMatrixVariable = _shader->createMatrixVariable("g_LightViewMatrix");
        _lightProjectionMatrixVariable = _shader->createMatrixVariable("g_LightProjectionMatrix");
        _shadowMapVariable = _shader->createShaderResourceVariable("g_ShadowMap");

        _diffuseVariable = _shader->createVectorVariable("g_DiffuseLight");
        _specularVariable = _shader->createVectorVariable("g_SpecularLight");
        _directionVariable = _shader->createVectorVariable("g_LightDirection");
    }
}

DirectionalLight::~DirectionalLight()
{
}

const Vector3& DirectionalLight::getDirection()
{
    return _direction;
}

void DirectionalLight::setDirection(const Vector3& direction)
{
    _direction = direction;
}

void DirectionalLight::apply()
{
    if (_diffuseVariable) _diffuseVariable->setValue(_diffuse);
    if (_specularVariable) _specularVariable->setValue(_specular);
    
    if (_directionVariable) {
        float dir[4] = { _direction.x, _direction.y, _direction.z };
        _directionVariable->setValue(dir);
    }

    if (_shadowedVariable) _shadowedVariable->setValue(_shadowed && _enableShadows);
    if (_lightViewMatrixVariable) _lightViewMatrixVariable->setValue((float*)_shadowCamera->getView()[0]);
    if (_lightProjectionMatrixVariable) _lightProjectionMatrixVariable->setValue((float*)_shadowCamera->getProjection()[0]);
   
    if (_shadowMapVariable) _shadowMapVariable->setValue(_resolvedShadowMap);
}

void DirectionalLight::setLightView(const Matrix4& worldMatrix)
{
    Vector3 direction = -_direction * worldMatrix;
    direction.normalize();

    Vector3 eye, center, up;

    eye = direction * 10.0f;
    center = eye - direction;
    up = Vector3(0.0f, 1.0f, 0.0f);

    _shadowCamera->setView(eye, center, up);

    _shadowCamera->setOrthographicProjection(512, 512, 1.0f, 1000.0f);
}

PointLight::PointLight()
: Light(), _position(Vector3(0.0f, 0.0f, 0.0f)), _constAtten(0.0f), _linearAtten(0.0f), _quadAtten(0.0f), _positionVariable(0), _constAttenVariable(0), _linearAttenVariable(0), _quadAttenVariable(0)
{
    _shadowed = false;

    _shader = ResourceManager::instance().createShader(L"point_light.fxo");

    if (_shader) {
        _shadowedVariable = _shader->createScalarVariable("g_ShadowEnabled");
        _lightViewMatrixVariable = _shader->createMatrixVariable("g_LightViewMatrix");
        _lightProjectionMatrixVariable = _shader->createMatrixVariable("g_LightProjectionMatrix");
        _shadowMapVariable = _shader->createShaderResourceVariable("g_ShadowMap");

        _diffuseVariable = _shader->createVectorVariable("g_DiffuseLight");
        _specularVariable = _shader->createVectorVariable("g_SpecularLight");
        _positionVariable = _shader->createVectorVariable("g_LightPosition");
        _constAttenVariable = _shader->createScalarVariable("g_ConstAtten");
        _linearAttenVariable = _shader->createScalarVariable("g_LinearAtten");
        _quadAttenVariable = _shader->createScalarVariable("g_QuadAtten");
    }
}

PointLight::~PointLight()
{
}

const Vector3& PointLight::getPosition()
{
    return _position;
}

void PointLight::setPosition(const Vector3& position)
{
    _position = position;
}

float PointLight::getConstAtten()
{
    return _constAtten;
}

float PointLight::getLinearAtten()
{
    return _linearAtten;
}

float PointLight::getQuadAtten()
{
    return _quadAtten;
}

void PointLight::setConstAtten(float constAtten)
{
    _constAtten = constAtten;
}

void PointLight::setLinearAtten(float linearAtten)
{
    _linearAtten = linearAtten;
}

void PointLight::setQuadAtten(float quadAtten)
{
    _quadAtten = quadAtten;
}

void PointLight::apply()
{
    if (_diffuseVariable) _diffuseVariable->setValue(_diffuse);
    if (_specularVariable) _specularVariable->setValue(_specular);
   
    if (_positionVariable) {
         float pos[4] = { _position.x, _position.y, _position.z };
        _positionVariable->setValue(pos);
    }

    if (_constAttenVariable) _constAttenVariable->setValue(_constAtten);
    if (_linearAttenVariable) _linearAttenVariable->setValue(_linearAtten);
    if (_quadAttenVariable) _quadAttenVariable->setValue(_quadAtten);

    if (_shadowedVariable) _shadowedVariable->setValue(_shadowed && _enableShadows);
    if (_lightViewMatrixVariable) _lightViewMatrixVariable->setValue((float*)_shadowCamera->getView()[0]);
    if (_lightProjectionMatrixVariable) _lightProjectionMatrixVariable->setValue((float*)_shadowCamera->getProjection()[0]);
    
    if (_shadowMapVariable) _shadowMapVariable->setValue(_resolvedShadowMap);
}

void PointLight::setLightView(const Matrix4& worldMatrix)
{
    _shadowCamera->setPerspectiveProjection(512, 512, 0.78539816339744830961566084581988f, 1.0f, 1000.0f);
}

SpotLight::SpotLight()
: Light(), _position(Vector3(0.0f, 0.0f, 0.0f)), _direction(Vector3(0.0f, 0.0f, 0.0f)), 
_constAtten(0.0f), _linearAtten(0.0f), _quadAtten(0.0f), _cutoff(0.0f), _exponent(0.0f),
_constAttenVariable(0), _linearAttenVariable(0), _quadAttenVariable(0), _cutoffVariable(0),
_exponentVariable(0), _positionVariable(0), _directionVariable(0)
{
    _shader = ResourceManager::instance().createShader(L"spot_light.fxo");

    if (_shader) {
        _shadowedVariable = _shader->createScalarVariable("g_ShadowEnabled");
        _lightViewMatrixVariable = _shader->createMatrixVariable("g_LightViewMatrix");
        _lightProjectionMatrixVariable = _shader->createMatrixVariable("g_LightProjectionMatrix");
        _shadowMapVariable = _shader->createShaderResourceVariable("g_ShadowMap");

        _diffuseVariable = _shader->createVectorVariable("g_DiffuseLight");
        _specularVariable = _shader->createVectorVariable("g_SpecularLight");
        _positionVariable = _shader->createVectorVariable("g_LightPosition");
        _constAttenVariable = _shader->createScalarVariable("g_ConstAtten");
        _linearAttenVariable = _shader->createScalarVariable("g_LinearAtten");
        _quadAttenVariable = _shader->createScalarVariable("g_QuadAtten");
        _directionVariable = _shader->createVectorVariable("g_SpotDirection");
        _cutoffVariable = _shader->createScalarVariable("g_SpotCutoff");
        _exponentVariable = _shader->createScalarVariable("g_SpotExponent");
    }
}

SpotLight::~SpotLight()
{
}

const Vector3& SpotLight::getPosition()
{
    return _position;
}

void SpotLight::setPosition(const Vector3& position)
{
    _position = position;
}

float SpotLight::getConstAtten()
{
    return _constAtten;
}

float SpotLight::getLinearAtten()
{
    return _linearAtten;
}

float SpotLight::getQuadAtten()
{
    return _quadAtten;
}

void SpotLight::setConstAtten(float constAtten)
{
    _constAtten = constAtten;
}

void SpotLight::setLinearAtten(float linearAtten)
{
    _linearAtten = linearAtten;
}

void SpotLight::setQuadAtten(float quadAtten)
{
    _quadAtten = quadAtten;
}

const Vector3& SpotLight::getDirection()
{
    return _direction;
}

void SpotLight::setDirection(const Vector3& direction)
{
    _direction = direction;
}

float SpotLight::getCutoff()
{
    return _cutoff;
}

float SpotLight::getExponent()
{
    return _exponent;
}

void SpotLight::setCutoff(float cutoff)
{
    _cutoff = cutoff;
}

void SpotLight::setExponent(float exponent)
{
    _exponent = exponent;
}

void SpotLight::apply()
{
    if (_diffuseVariable) _diffuseVariable->setValue(_diffuse);
    if (_specularVariable) _specularVariable->setValue(_specular);
   
    if (_positionVariable) {
         float pos[4] = { _position.x, _position.y, _position.z };
        _positionVariable->setValue(pos);
    }

    if (_constAttenVariable) _constAttenVariable->setValue(_constAtten);
    if (_linearAttenVariable) _linearAttenVariable->setValue(_linearAtten);
    if (_quadAttenVariable) _quadAttenVariable->setValue(_quadAtten);

    if (_directionVariable) {
         float dir[4] = { _direction.x, _direction.y, _direction.z };
        _directionVariable->setValue(dir);
    }

    if (_cutoffVariable) _cutoffVariable->setValue(_cutoff);
    if (_exponentVariable) _exponentVariable->setValue(_exponent);


    if (_shadowedVariable) _shadowedVariable->setValue(_shadowed && _enableShadows);
    if (_lightViewMatrixVariable) _lightViewMatrixVariable->setValue((float*)_shadowCamera->getView()[0]);
    if (_lightProjectionMatrixVariable) _lightProjectionMatrixVariable->setValue((float*)_shadowCamera->getProjection()[0]);
    
    if (_shadowMapVariable) _shadowMapVariable->setValue(_resolvedShadowMap);
}

void SpotLight::setLightView(const Matrix4& worldMatrix)
{
    Vector3 eye = Vector3(
        _position.x * worldMatrix[0][0] + _position.y * worldMatrix[1][0] + _position.z * worldMatrix[2][0] + worldMatrix[3][0],
        _position.x * worldMatrix[0][1] + _position.y * worldMatrix[1][1] + _position.z * worldMatrix[2][1] + worldMatrix[3][1],
        _position.x * worldMatrix[0][2] + _position.y * worldMatrix[1][2] + _position.z * worldMatrix[2][2] + worldMatrix[3][2]
    );

    Vector3 direction = _direction * worldMatrix;
    direction.normalize();

    Vector3 center = eye + direction;
    Vector3 up = Vector3(0.0f, 1.0f, 0.0f);

    _shadowCamera->setView(eye, center, up);

    _shadowCamera->setPerspectiveProjection(512, 512, 0.78539816339744830961566084581988f, 1.0f, 1000.0f);
}