#include "Camera.h"

Camera::Camera()
: Group(), _projectionType(IDENTITY), _width(0), _height(0), _fov(0.0f), _zNear(0.0f), _zFar(0.0f), _renderTargetImplementation(FRAMEBUFFER), _depthStencilBuffer(0)
{
    _view.identity();
    _projection.identity();

    _eye = Vector3(0.0f, 0.0f, 0.0f);
    _center = Vector3(0.0f, 0.0f, 0.0f);
    _up = Vector3(0.0f, 0.0f, 0.0f);

    memset(_input,  0, sizeof(RenderTarget*) * 16);
    memset(_output, 0, sizeof(RenderTarget*) * 16);
}

Camera::~Camera()
{
}

const Matrix4& Camera::getView()
{
    return _view;
}

const Matrix4& Camera::getProjection()
{
    return _projection;
}

void Camera::setViewIdentity()
{
    _eye = Vector3(0.0f, 0.0f, 0.0f);
    _center = Vector3(0.0f, 0.0f, 0.0f);
    _up = Vector3(0.0f, 0.0f, 0.0f);

    _view.identity();

    notify();
}

void Camera::setView(const Vector3& eye, const Vector3& center, const Vector3& up)
{
    _eye = eye;
    _center = center;
    _up = up;

    Vector3 zaxis = (center - eye);
    zaxis.normalize();

    Vector3 xaxis = Vector3::cross(up, zaxis);
    xaxis.normalize();

    Vector3 yaxis = Vector3::cross(zaxis, xaxis);


    _view[0][0] = xaxis.x;                              _view[0][1] = yaxis.x;                              _view[0][2] = zaxis.x;                              _view[0][3] = 0.0f;
    _view[1][0] = xaxis.y;                              _view[1][1] = yaxis.y;                              _view[1][2] = zaxis.y;                              _view[1][3] = 0.0f;
    _view[2][0] = xaxis.z;                              _view[2][1] = yaxis.z;                              _view[2][2] = zaxis.z;                              _view[2][3] = 0.0f;
    _view[3][0] = -1.0f * Vector3::dot(xaxis, eye);     _view[3][1] = -1.0f * Vector3::dot(yaxis, eye);     _view[3][2] = -1.0f * Vector3::dot(zaxis, eye);     _view[3][3] = 1.0f;

    notify();
}

void Camera::setProjectionIdentity()
{
    _projectionType = IDENTITY;

    _projection.identity();

    notify();
}

void Camera::setOrtho2DProjection(int width, int height)
{
    _projectionType = ORTHO2D;

    _width = width;
    _height = height;

    _fov = 0.0f;
    _zNear = 0.0f;
    _zFar = 0.0f;

    _projection[0][0] = 2.0f/width;     _projection[0][1] = 0.0f;           _projection[0][2] = 0.0f;   _projection[0][3] = 0.0f;
    _projection[1][0] = 0.0f;           _projection[1][1] = -2.0f/height;   _projection[1][2] = 0.0f;   _projection[1][3] = 0.0f;
    _projection[2][0] = 0.0f;           _projection[2][1] = 0.0f;           _projection[2][2] = 1.0f;   _projection[2][3] = 0.0f;
    _projection[3][0] = -1.0f;          _projection[3][1] = 1.0f;           _projection[3][2] = 0.0f;   _projection[3][3] = 1.0f;

    notify();
}

void Camera::setOrthographicProjection(int width, int height, float zNear, float zFar)
{
    _projectionType = ORTHOGRAPHIC;

    _width = width;
    _height = height;

    _zNear = zNear;
    _zFar = zFar;

    _fov = 0.0f;

    _projection[0][0] = 2.0f/_width;    _projection[0][1] = 0.0f;           _projection[0][2] = 0.0f;                   _projection[0][3] = 0.0f;
    _projection[1][0] = 0.0f;           _projection[1][1] = 2.0f/_height;   _projection[1][2] = 0.0f;                   _projection[1][3] = 0.0f;
    _projection[2][0] = 0.0f;           _projection[2][1] = 0.0f;           _projection[2][2] = 1.0f/(_zFar-_zNear);    _projection[2][3] = 0.0f;
    _projection[3][0] = 0.0f;           _projection[3][1] = 0.0f;           _projection[3][2] = _zNear/(_zNear-_zFar);  _projection[3][3] = 0.025f;

    
    notify();
}

void Camera::setPerspectiveProjection(int width, int height, float fov, float zNear, float zFar)
{
    _projectionType = PERSPECTIVE;

    _width = width;
    _height = height;

    _fov = fov;
    _zNear = zNear;
    _zFar = zFar;

    float aspectRatio = (float)_width / (float)_height;
    float yScale = 1.0f / tan(_fov/2.0f);
    float xScale = yScale / aspectRatio;

    _projection[0][0] = xScale;     _projection[0][1] = 0.0f;           _projection[0][2] = 0.0f;                           _projection[0][3] = 0.0f;
    _projection[1][0] = 0.0f;       _projection[1][1] = yScale;         _projection[1][2] = 0.0f;                           _projection[1][3] = 0.0f;
    _projection[2][0] = 0.0f;       _projection[2][1] = 0.0f;           _projection[2][2] = _zFar/(_zFar-_zNear);           _projection[2][3] = 1.0f;
    _projection[3][0] = 0.0f;       _projection[3][1] = 0.0f;           _projection[3][2] = _zNear*_zFar/(_zNear-_zFar);    _projection[3][3] = 0.0f;

    notify();
}

void Camera::updateProjection(int width, int height)
{
    if ((_renderTargetImplementation == TEXTURE) && (_output[0] != 0)) {
        width = _output[0]->getWidth();
        height = _output[0]->getHeight();
    }

    if (_projectionType == ORTHO2D) {
        setOrtho2DProjection(width, height);
    }

    else if (_projectionType == ORTHOGRAPHIC) {
        setOrthographicProjection(width, height, _zNear, _zFar);
    }

    else if (_projectionType == PERSPECTIVE) {
        setPerspectiveProjection(width, height, _fov, _zNear, _zFar);
    }
}

const Vector3& Camera::getEye()
{
    return _eye;
}

const Vector3& Camera::getCenter()
{
    return _center;
}

const Vector3& Camera::getUp()
{
    return _up;
}

void Camera::setRenderTargetImplementation(Camera::RenderTargetImplementation renderTargetImplementation)
{
    _renderTargetImplementation = renderTargetImplementation;
}

Camera::RenderTargetImplementation Camera::getRenderTargetImplementation()
{
    return _renderTargetImplementation;
}

void Camera::attachInput(RenderTarget* renderTarget, size_t index)
{
    _input[index] = renderTarget;
}

void Camera::attachOutput(RenderTarget* renderTarget, size_t index)
{
    _output[index] = renderTarget;
}

void Camera::attachDepthStencilBuffer(DepthStencilBuffer* depthStencilBuffer)
{
    _depthStencilBuffer = depthStencilBuffer;
}

RenderTarget** Camera::getInputBuffers()
{
    return &_input[0];
}

RenderTarget** Camera::getOutputBuffers()
{
    return &_output[0];
}

DepthStencilBuffer* Camera::getDepthStencilBuffer()
{
    return _depthStencilBuffer;
}