/*
 *  PolyCamera.cpp
 *  Poly
 *
 *  Created by Ivan Safrin on 3/26/08.
 *  Copyright 2008 Ivan Safrin. All rights reserved.
 *
 */

#include "PolyCamera.h"

using namespace Polycode;
			
Camera::Camera(Scene *parentScene) : SceneEntity() {
	setParentScene(parentScene);
	orthoMode = false;
	fov = 45.0f;
	exposureLevel = 1.0f;
	_hasFilterShader = false;	
	fovSet = false;
}

Camera::~Camera() {

}

void Camera::setExposureLevel(float level) {
	exposureLevel = level;
}

float Camera::getExposureLevel() {
	return exposureLevel;
}


void Camera::setFOV(float fov) {
	this->fov = fov;
	fovSet = true;
}

float Camera::getFOV() {
	return fov;
}


bool Camera::isSphereInFrustrum(Vector3 pos, float fRadius) {
    for( int i = 0; i < 6; ++i )
    {
        if( frustumPlanes[i][0] * pos.x +
            frustumPlanes[i][1] * pos.y +
            frustumPlanes[i][2] * pos.z +
            frustumPlanes[i][3] <= -fRadius )
            return false;
    }

    return true;
}

void Camera::setOrthoMode(bool mode) {
	orthoMode = mode;
}			

bool Camera::getOrthoMode() {
	return orthoMode;
}

void Camera::buildFrustrumPlanes() {

	Matrix4 p; 
	Matrix4 mv;
	Matrix4 mvp;
	float t;

	p = CoreServices::getInstance()->getRenderer()->getProjectionMatrix();
    mv = CoreServices::getInstance()->getRenderer()->getModelviewMatrix();

    //
    // Concatenate the projection matrix and the model-view matrix to produce 
    // a combined model-view-projection matrix.
    //

    mvp.ml[ 0] = mv.ml[ 0] * p.ml[ 0] + mv.ml[ 1] * p.ml[ 4] + mv.ml[ 2] * p.ml[ 8] + mv.ml[ 3] * p.ml[12];

    mvp.ml[ 1] = mv.ml[ 0] * p.ml[ 1] + mv.ml[ 1] * p.ml[ 5] + mv.ml[ 2] * p.ml[ 9] + mv.ml[ 3] * p.ml[13];
    mvp.ml[ 2] = mv.ml[ 0] * p.ml[ 2] + mv.ml[ 1] * p.ml[ 6] + mv.ml[ 2] * p.ml[10] + mv.ml[ 3] * p.ml[14];
    mvp.ml[ 3] = mv.ml[ 0] * p.ml[ 3] + mv.ml[ 1] * p.ml[ 7] + mv.ml[ 2] * p.ml[11] + mv.ml[ 3] * p.ml[15];

    mvp.ml[ 4] = mv.ml[ 4] * p.ml[ 0] + mv.ml[ 5] * p.ml[ 4] + mv.ml[ 6] * p.ml[ 8] + mv.ml[ 7] * p.ml[12];
    mvp.ml[ 5] = mv.ml[ 4] * p.ml[ 1] + mv.ml[ 5] * p.ml[ 5] + mv.ml[ 6] * p.ml[ 9] + mv.ml[ 7] * p.ml[13];
    mvp.ml[ 6] = mv.ml[ 4] * p.ml[ 2] + mv.ml[ 5] * p.ml[ 6] + mv.ml[ 6] * p.ml[10] + mv.ml[ 7] * p.ml[14];
    mvp.ml[ 7] = mv.ml[ 4] * p.ml[ 3] + mv.ml[ 5] * p.ml[ 7] + mv.ml[ 6] * p.ml[11] + mv.ml[ 7] * p.ml[15];

    mvp.ml[ 8] = mv.ml[ 8] * p.ml[ 0] + mv.ml[ 9] * p.ml[ 4] + mv.ml[10] * p.ml[ 8] + mv.ml[11] * p.ml[12];
    mvp.ml[ 9] = mv.ml[ 8] * p.ml[ 1] + mv.ml[ 9] * p.ml[ 5] + mv.ml[10] * p.ml[ 9] + mv.ml[11] * p.ml[13];
    mvp.ml[10] = mv.ml[ 8] * p.ml[ 2] + mv.ml[ 9] * p.ml[ 6] + mv.ml[10] * p.ml[10] + mv.ml[11] * p.ml[14];
    mvp.ml[11] = mv.ml[ 8] * p.ml[ 3] + mv.ml[ 9] * p.ml[ 7] + mv.ml[10] * p.ml[11] + mv.ml[11] * p.ml[15];

    mvp.ml[12] = mv.ml[12] * p.ml[ 0] + mv.ml[13] * p.ml[ 4] + mv.ml[14] * p.ml[ 8] + mv.ml[15] * p.ml[12];
    mvp.ml[13] = mv.ml[12] * p.ml[ 1] + mv.ml[13] * p.ml[ 5] + mv.ml[14] * p.ml[ 9] + mv.ml[15] * p.ml[13];
    mvp.ml[14] = mv.ml[12] * p.ml[ 2] + mv.ml[13] * p.ml[ 6] + mv.ml[14] * p.ml[10] + mv.ml[15] * p.ml[14];
    mvp.ml[15] = mv.ml[12] * p.ml[ 3] + mv.ml[13] * p.ml[ 7] + mv.ml[14] * p.ml[11] + mv.ml[15] * p.ml[15];

    //
    // Extract the frustum's right clipping plane and normalize it.
    //

    frustumPlanes[0][0] = mvp.ml[ 3] - mvp.ml[ 0];
    frustumPlanes[0][1] = mvp.ml[ 7] - mvp.ml[ 4];
    frustumPlanes[0][2] = mvp.ml[11] - mvp.ml[ 8];
    frustumPlanes[0][3] = mvp.ml[15] - mvp.ml[12];

    t = (float) sqrt( frustumPlanes[0][0] * frustumPlanes[0][0] + 
                      frustumPlanes[0][1] * frustumPlanes[0][1] + 
                      frustumPlanes[0][2] * frustumPlanes[0][2] );

    frustumPlanes[0][0] /= t;
    frustumPlanes[0][1] /= t;
    frustumPlanes[0][2] /= t;
    frustumPlanes[0][3] /= t;

    //
    // Extract the frustum's left clipping plane and normalize it.
    //

    frustumPlanes[1][0] = mvp.ml[ 3] + mvp.ml[ 0];
    frustumPlanes[1][1] = mvp.ml[ 7] + mvp.ml[ 4];
    frustumPlanes[1][2] = mvp.ml[11] + mvp.ml[ 8];
    frustumPlanes[1][3] = mvp.ml[15] + mvp.ml[12];

    t = (float) sqrt( frustumPlanes[1][0] * frustumPlanes[1][0] + 
                      frustumPlanes[1][1] * frustumPlanes[1][1] + 
                      frustumPlanes[1][2] * frustumPlanes[1][2] );

    frustumPlanes[1][0] /= t;
    frustumPlanes[1][1] /= t;
    frustumPlanes[1][2] /= t;
    frustumPlanes[1][3] /= t;

    //
    // Extract the frustum's bottom clipping plane and normalize it.
    //

    frustumPlanes[2][0] = mvp.ml[ 3] + mvp.ml[ 1];
    frustumPlanes[2][1] = mvp.ml[ 7] + mvp.ml[ 5];
    frustumPlanes[2][2] = mvp.ml[11] + mvp.ml[ 9];
    frustumPlanes[2][3] = mvp.ml[15] + mvp.ml[13];

    t = (float) sqrt( frustumPlanes[2][0] * frustumPlanes[2][0] + 
                      frustumPlanes[2][1] * frustumPlanes[2][1] + 
                      frustumPlanes[2][2] * frustumPlanes[2][2] );

    frustumPlanes[2][0] /= t;
    frustumPlanes[2][1] /= t;
    frustumPlanes[2][2] /= t;
    frustumPlanes[2][3] /= t;

    //
    // Extract the frustum's top clipping plane and normalize it.
    //

    frustumPlanes[3][0] = mvp.ml[ 3] - mvp.ml[ 1];
    frustumPlanes[3][1] = mvp.ml[ 7] - mvp.ml[ 5];
    frustumPlanes[3][2] = mvp.ml[11] - mvp.ml[ 9];
    frustumPlanes[3][3] = mvp.ml[15] - mvp.ml[13];

    t = (float) sqrt( frustumPlanes[3][0] * frustumPlanes[3][0] + 
                      frustumPlanes[3][1] * frustumPlanes[3][1] + 
                      frustumPlanes[3][2] * frustumPlanes[3][2] );

    frustumPlanes[3][0] /= t;
    frustumPlanes[3][1] /= t;
    frustumPlanes[3][2] /= t;
    frustumPlanes[3][3] /= t;

    //
    // Extract the frustum's far clipping plane and normalize it.
    //

    frustumPlanes[4][0] = mvp.ml[ 3] - mvp.ml[ 2];
    frustumPlanes[4][1] = mvp.ml[ 7] - mvp.ml[ 6];
    frustumPlanes[4][2] = mvp.ml[11] - mvp.ml[10];
    frustumPlanes[4][3] = mvp.ml[15] - mvp.ml[14];

    t = (float) sqrt( frustumPlanes[4][0] * frustumPlanes[4][0] +  
                      frustumPlanes[4][1] * frustumPlanes[4][1] + 
                      frustumPlanes[4][2] * frustumPlanes[4][2] );

    frustumPlanes[4][0] /= t;
    frustumPlanes[4][1] /= t;
    frustumPlanes[4][2] /= t;
    frustumPlanes[4][3] /= t;

    //
    // Extract the frustum's near clipping plane and normalize it.
    //

    frustumPlanes[5][0] = mvp.ml[ 3] + mvp.ml[ 2];
    frustumPlanes[5][1] = mvp.ml[ 7] + mvp.ml[ 6];
    frustumPlanes[5][2] = mvp.ml[11] + mvp.ml[10];
    frustumPlanes[5][3] = mvp.ml[15] + mvp.ml[14];

    t = (float) sqrt( frustumPlanes[5][0] * frustumPlanes[5][0] + 
                      frustumPlanes[5][1] * frustumPlanes[5][1] + 
                      frustumPlanes[5][2] * frustumPlanes[5][2] );

    frustumPlanes[5][0] /= t;
    frustumPlanes[5][1] /= t;
    frustumPlanes[5][2] /= t;
    frustumPlanes[5][3] /= t;

}

bool Camera::canSee(SceneEntity *entity) {
	return isSphereInFrustrum(*entity->getPosition(), entity->getBBoxRadius());
}

void Camera::setParentScene(Scene *parentScene) {
	this->parentScene = parentScene;
}

void Camera::setPostFilter(string shaderName) {
	Material *shaderMaterial = (Material*) CoreServices::getInstance()->getResourceManager()->getResource(Resource::RESOURCE_MATERIAL, shaderName);
	if(shaderMaterial)
		createPostFilter(shaderMaterial);
}

void Camera::createPostFilter(Material *shaderMaterial) {
	if(!shaderMaterial)
		return;
	if(shaderMaterial->getNumShaders() == 0)
		return;
		
	this->filterShaderMaterial = shaderMaterial;
	
	// TODO: make it save the textures to resource manager and check if they there
//	originalSceneTexture = CoreServices::getInstance()->getMaterialManager()->createNewTexture(CoreServices::getInstance()->getCore()->getXRes(), CoreServices::getInstance()->getCore()->getYRes());
//	zBufferSceneTexture = CoreServices::getInstance()->getMaterialManager()->createFramebufferTexture(CoreServices::getInstance()->getCore()->getXRes(), CoreServices::getInstance()->getCore()->getYRes(), 0);

	CoreServices::getInstance()->getRenderer()->createRenderTextures(&originalSceneTexture, &zBufferSceneTexture, CoreServices::getInstance()->getCore()->getXRes(), CoreServices::getInstance()->getCore()->getYRes());
	
	for(int i=0; i < shaderMaterial->getNumShaders(); i++) {
		ShaderBinding* binding = shaderMaterial->getShader(i)->createBinding();		
		binding->addTexture("PolySceneRender", originalSceneTexture);
		binding->addTexture("PolySceneZBuffer", zBufferSceneTexture);
		localShaderOptions.push_back(binding);
	}
	

	_hasFilterShader = true;
}

bool Camera::hasFilterShader() {
	return _hasFilterShader;
}

void Camera::setLightDepthTexture(Texture *texture) {
	for(int i=0; i < localShaderOptions.size(); i++) {
		localShaderOptions[i]->clearTexture("PolyLight0ZBuffer");
		localShaderOptions[i]->addTexture("PolyLight0ZBuffer", texture);
	}

}

void Camera::drawFilter() {

	if(!filterShaderMaterial)
		return;

	CoreServices::getInstance()->getRenderer()->bindFrameBufferTexture(zBufferSceneTexture);
	parentScene->Render();
	CoreServices::getInstance()->getRenderer()->unbindFramebuffers();

	ShaderBinding* materialBinding;		
	for(int i=0; i < filterShaderMaterial->getNumShaders(); i++) {
		materialBinding = filterShaderMaterial->getShaderBinding(i);
		CoreServices::getInstance()->getRenderer()->applyMaterial(filterShaderMaterial, localShaderOptions[i], i);		
		if(i==filterShaderMaterial->getNumShaders()-1) {
				CoreServices::getInstance()->getRenderer()->clearScreen();
				CoreServices::getInstance()->getRenderer()->loadIdentity();
				CoreServices::getInstance()->getRenderer()->drawScreenQuad(CoreServices::getInstance()->getRenderer()->getXRes(), CoreServices::getInstance()->getRenderer()->getYRes());		
		} else {
			for(int j=0; j < materialBinding->getNumOutTargetBindings(); j++) {
				CoreServices::getInstance()->getRenderer()->bindFrameBufferTexture(materialBinding->getOutTargetBinding(j)->texture);
				CoreServices::getInstance()->getRenderer()->drawScreenQuad(materialBinding->getOutTargetBinding(j)->width, materialBinding->getOutTargetBinding(j)->height);
				CoreServices::getInstance()->getRenderer()->unbindFramebuffers();
			}		
		}
		CoreServices::getInstance()->getRenderer()->clearShader();
		CoreServices::getInstance()->getRenderer()->loadIdentity();
	}

}

void Camera::doCameraTransform() {

	if(fovSet)
			CoreServices::getInstance()->getRenderer()->setFOV(fov);
	CoreServices::getInstance()->getRenderer()->setExposureLevel(exposureLevel);

	if(matrixDirty) {
		rebuildTransformMatrix();
	}

	Matrix4 camMatrix = getConcatenatedMatrix();
	CoreServices::getInstance()->getRenderer()->setCameraMatrix(camMatrix);	
	camMatrix = camMatrix.inverse();
	CoreServices::getInstance()->getRenderer()->multModelviewMatrix(camMatrix);		
}