#include "LightSystem.h"
#include <cassert>
#include <iostream>
#include <cmath>

using namespace ltbl;

void LightSystem::getPenumbrasPoint(std::vector<Penumbra> &penumbras, std::vector<int> &innerBoundaryIndices, std::vector<sf::Vector2f> &innerBoundaryVectors, std::vector<int> &outerBoundaryIndices, std::vector<sf::Vector2f> &outerBoundaryVectors, const sf::ConvexShape &shape, const sf::Vector2f &sourceCenter, float sourceRadius) {
    const int numPoints = shape.getPointCount();

    std::vector<bool> bothEdgesBoundaryWindings;
    bothEdgesBoundaryWindings.reserve(2);

    std::vector<bool> oneEdgeBoundaryWindings;
    oneEdgeBoundaryWindings.reserve(2);

    // Calculate front and back facing sides
    std::vector<bool> facingFrontBothEdges;
    facingFrontBothEdges.reserve(numPoints);

    std::vector<bool> facingFrontOneEdge;
    facingFrontOneEdge.reserve(numPoints);

    for (int i = 0; i < numPoints; i++) {
        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(i));

        sf::Vector2f nextPoint;

        if (i < numPoints - 1)
            nextPoint = shape.getTransform().transformPoint(shape.getPoint(i + 1));
        else
            nextPoint = shape.getTransform().transformPoint(shape.getPoint(0));

        sf::Vector2f firstEdgeRay;
        sf::Vector2f secondEdgeRay;
        sf::Vector2f firstNextEdgeRay;
        sf::Vector2f secondNextEdgeRay;

        {
            sf::Vector2f sourceToPoint = point - sourceCenter;

            sf::Vector2f perpendicularOffset(-sourceToPoint.y, sourceToPoint.x);

            perpendicularOffset = vectorNormalize(perpendicularOffset);
            perpendicularOffset *= sourceRadius;

            firstEdgeRay = point - (sourceCenter - perpendicularOffset);
            secondEdgeRay = point - (sourceCenter + perpendicularOffset);
        }

        {
            sf::Vector2f sourceToPoint = nextPoint - sourceCenter;

            sf::Vector2f perpendicularOffset(-sourceToPoint.y, sourceToPoint.x);

            perpendicularOffset = vectorNormalize(perpendicularOffset);
            perpendicularOffset *= sourceRadius;

            firstNextEdgeRay = nextPoint - (sourceCenter - perpendicularOffset);
            secondNextEdgeRay = nextPoint - (sourceCenter + perpendicularOffset);
        }

        sf::Vector2f pointToNextPoint = nextPoint - point;

        sf::Vector2f normal = vectorNormalize(sf::Vector2f(-pointToNextPoint.y, pointToNextPoint.x));

        // Front facing, mark it
        facingFrontBothEdges.push_back((vectorDot(firstEdgeRay, normal) > 0.0f && vectorDot(secondEdgeRay, normal) > 0.0f) || (vectorDot(firstNextEdgeRay, normal) > 0.0f && vectorDot(secondNextEdgeRay, normal) > 0.0f));
        facingFrontOneEdge.push_back((vectorDot(firstEdgeRay, normal) > 0.0f || vectorDot(secondEdgeRay, normal) > 0.0f) || vectorDot(firstNextEdgeRay, normal) > 0.0f || vectorDot(secondNextEdgeRay, normal) > 0.0f);
    }

    // Go through front/back facing list. Where the facing direction switches, there is a boundary
    for (int i = 1; i < numPoints; i++)
        if (facingFrontBothEdges[i] != facingFrontBothEdges[i - 1]) {
            innerBoundaryIndices.push_back(i);
            bothEdgesBoundaryWindings.push_back(facingFrontBothEdges[i]);
        }

    // Check looping indices separately
    if (facingFrontBothEdges[0] != facingFrontBothEdges[numPoints - 1]) {
        innerBoundaryIndices.push_back(0);
        bothEdgesBoundaryWindings.push_back(facingFrontBothEdges[0]);
    }

    // Go through front/back facing list. Where the facing direction switches, there is a boundary
    for (int i = 1; i < numPoints; i++)
        if (facingFrontOneEdge[i] != facingFrontOneEdge[i - 1]) {
            outerBoundaryIndices.push_back(i);
            oneEdgeBoundaryWindings.push_back(facingFrontOneEdge[i]);
        }

    // Check looping indices separately
    if (facingFrontOneEdge[0] != facingFrontOneEdge[numPoints - 1]) {
        outerBoundaryIndices.push_back(0);
        oneEdgeBoundaryWindings.push_back(facingFrontOneEdge[0]);
    }

    // Compute outer boundary vectors
    for (unsigned bi = 0; bi < outerBoundaryIndices.size(); bi++) {
        int penumbraIndex = outerBoundaryIndices[bi];
        bool winding = oneEdgeBoundaryWindings[bi];

        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));

        sf::Vector2f sourceToPoint = point - sourceCenter;

        sf::Vector2f perpendicularOffset(-sourceToPoint.y, sourceToPoint.x);

        perpendicularOffset = vectorNormalize(perpendicularOffset);
        perpendicularOffset *= sourceRadius;

        sf::Vector2f firstEdgeRay = point - (sourceCenter + perpendicularOffset);
        sf::Vector2f secondEdgeRay = point - (sourceCenter - perpendicularOffset);

        // Add boundary vector
        outerBoundaryVectors.push_back(winding ? firstEdgeRay : secondEdgeRay);
    }

    for (unsigned bi = 0; bi < innerBoundaryIndices.size(); bi++) {
        int penumbraIndex = innerBoundaryIndices[bi];
        bool winding = bothEdgesBoundaryWindings[bi];

        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));

        sf::Vector2f sourceToPoint = point - sourceCenter;

        sf::Vector2f perpendicularOffset(-sourceToPoint.y, sourceToPoint.x);

        perpendicularOffset = vectorNormalize(perpendicularOffset);
        perpendicularOffset *= sourceRadius;

        sf::Vector2f firstEdgeRay = point - (sourceCenter + perpendicularOffset);
        sf::Vector2f secondEdgeRay = point - (sourceCenter - perpendicularOffset);

        // Add boundary vector
        innerBoundaryVectors.push_back(winding ? secondEdgeRay : firstEdgeRay);
        sf::Vector2f outerBoundaryVector = winding ? firstEdgeRay : secondEdgeRay;

        if (innerBoundaryIndices.size() == 1)
            innerBoundaryVectors.push_back(outerBoundaryVector);

        // Add penumbras
        bool hasPrevPenumbra = false;

        sf::Vector2f prevPenumbraLightEdgeVector;

        float prevBrightness = 1.0f;

        int counter = 0;

        while (penumbraIndex != -1) {
            sf::Vector2f nextPoint;
            int nextPointIndex;

            if (penumbraIndex < numPoints - 1) {
                nextPointIndex = penumbraIndex + 1;
                nextPoint = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex + 1));
            }
            else {
                nextPointIndex = 0;
                nextPoint = shape.getTransform().transformPoint(shape.getPoint(0));
            }

            sf::Vector2f pointToNextPoint = nextPoint - point;

            sf::Vector2f prevPoint;
            int prevPointIndex;

            if (penumbraIndex > 0) {
                prevPointIndex = penumbraIndex - 1;
                prevPoint = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex - 1));
            }
            else {
                prevPointIndex = numPoints - 1;
                prevPoint = shape.getTransform().transformPoint(shape.getPoint(numPoints - 1));
            }

            sf::Vector2f pointToPrevPoint = prevPoint - point;

            LightSystem::Penumbra penumbra;

            penumbra._source = point;

            if (!winding) {
                if (hasPrevPenumbra)
                    penumbra._lightEdge = prevPenumbraLightEdgeVector;
                else
                    penumbra._lightEdge = innerBoundaryVectors.back();

                penumbra._darkEdge = outerBoundaryVector;

                penumbra._lightBrightness = prevBrightness;

                // Next point, check for intersection
                float intersectionAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(pointToNextPoint)));
                float penumbraAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(penumbra._darkEdge)));

                if (intersectionAngle < penumbraAngle) {
                    prevBrightness = penumbra._darkBrightness = intersectionAngle / penumbraAngle;

                    assert(prevBrightness >= 0.0f && prevBrightness <= 1.0f);

                    penumbra._darkEdge = pointToNextPoint;

                    penumbraIndex = nextPointIndex;

                    if (hasPrevPenumbra) {
                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
                    }

                    hasPrevPenumbra = true;

                    prevPenumbraLightEdgeVector = penumbra._darkEdge;

                    point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));

                    sourceToPoint = point - sourceCenter;

                    perpendicularOffset = sf::Vector2f(-sourceToPoint.y, sourceToPoint.x);

                    perpendicularOffset = vectorNormalize(perpendicularOffset);
                    perpendicularOffset *= sourceRadius;

                    firstEdgeRay = point - (sourceCenter + perpendicularOffset);
                    secondEdgeRay = point - (sourceCenter - perpendicularOffset);

                    outerBoundaryVector = secondEdgeRay;

                    if (!outerBoundaryVectors.empty()) {
                        outerBoundaryVectors[0] = penumbra._darkEdge;
                        outerBoundaryIndices[0] = penumbraIndex;
                    }
                }
                else {
                    penumbra._darkBrightness = 0.0f;

                    if (hasPrevPenumbra) {
                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
                    }

                    hasPrevPenumbra = false;

                    if (!outerBoundaryVectors.empty()) {
                        outerBoundaryVectors[0] = penumbra._darkEdge;
                        outerBoundaryIndices[0] = penumbraIndex;
                    }

                    penumbraIndex = -1;
                }
            }
            else {
                if (hasPrevPenumbra)
                    penumbra._lightEdge = prevPenumbraLightEdgeVector;
                else
                    penumbra._lightEdge = innerBoundaryVectors.back();

                penumbra._darkEdge = outerBoundaryVector;

                penumbra._lightBrightness = prevBrightness;

                // Next point, check for intersection
                float intersectionAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(pointToPrevPoint)));
                float penumbraAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(penumbra._darkEdge)));

                if (intersectionAngle < penumbraAngle) {
                    prevBrightness = penumbra._darkBrightness = intersectionAngle / penumbraAngle;

                    assert(prevBrightness >= 0.0f && prevBrightness <= 1.0f);

                    penumbra._darkEdge = pointToPrevPoint;

                    penumbraIndex = prevPointIndex;

                    if (hasPrevPenumbra) {
                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
                    }

                    hasPrevPenumbra = true;

                    prevPenumbraLightEdgeVector = penumbra._darkEdge;

                    point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));

                    sourceToPoint = point - sourceCenter;

                    perpendicularOffset = sf::Vector2f(-sourceToPoint.y, sourceToPoint.x);

                    perpendicularOffset = vectorNormalize(perpendicularOffset);
                    perpendicularOffset *= sourceRadius;

                    firstEdgeRay = point - (sourceCenter + perpendicularOffset);
                    secondEdgeRay = point - (sourceCenter - perpendicularOffset);

                    outerBoundaryVector = firstEdgeRay;

                    if (!outerBoundaryVectors.empty()) {
                        outerBoundaryVectors[1] = penumbra._darkEdge;
                        outerBoundaryIndices[1] = penumbraIndex;
                    }
                }
                else {
                    penumbra._darkBrightness = 0.0f;

                    if (hasPrevPenumbra) {
                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
                    }

                    hasPrevPenumbra = false;

                    if (!outerBoundaryVectors.empty()) {
                        outerBoundaryVectors[1] = penumbra._darkEdge;
                        outerBoundaryIndices[1] = penumbraIndex;
                    }

                    penumbraIndex = -1;
                }
            }

            penumbras.push_back(penumbra);

            counter++;
        }
    }
}

void LightSystem::getPenumbrasDirection(std::vector<Penumbra> &penumbras, std::vector<int> &innerBoundaryIndices, std::vector<sf::Vector2f> &innerBoundaryVectors, std::vector<int> &outerBoundaryIndices, std::vector<sf::Vector2f> &outerBoundaryVectors, const sf::ConvexShape &shape, const sf::Vector2f &sourceDirection, float sourceRadius, float sourceDistance) {
    const int numPoints = shape.getPointCount();

    innerBoundaryIndices.reserve(2);
    innerBoundaryVectors.reserve(2);
    penumbras.reserve(2);

    std::vector<bool> bothEdgesBoundaryWindings;
    bothEdgesBoundaryWindings.reserve(2);

    // Calculate front and back facing sides
    std::vector<bool> facingFrontBothEdges;
    facingFrontBothEdges.reserve(numPoints);

    std::vector<bool> facingFrontOneEdge;
    facingFrontOneEdge.reserve(numPoints);

    for (int i = 0; i < numPoints; i++) {
        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(i));

        sf::Vector2f nextPoint;

        if (i < numPoints - 1)
            nextPoint = shape.getTransform().transformPoint(shape.getPoint(i + 1));
        else
            nextPoint = shape.getTransform().transformPoint(shape.getPoint(0));

        sf::Vector2f firstEdgeRay;
        sf::Vector2f secondEdgeRay;
        sf::Vector2f firstNextEdgeRay;
        sf::Vector2f secondNextEdgeRay;

        sf::Vector2f perpendicularOffset(-sourceDirection.y, sourceDirection.x);

        perpendicularOffset = vectorNormalize(perpendicularOffset);
        perpendicularOffset *= sourceRadius;

        firstEdgeRay = point - (point - sourceDirection * sourceDistance - perpendicularOffset);
        secondEdgeRay = point - (point - sourceDirection * sourceDistance + perpendicularOffset);

        firstNextEdgeRay = nextPoint - (point - sourceDirection * sourceDistance - perpendicularOffset);
        secondNextEdgeRay = nextPoint - (point - sourceDirection * sourceDistance + perpendicularOffset);

        sf::Vector2f pointToNextPoint = nextPoint - point;

        sf::Vector2f normal = vectorNormalize(sf::Vector2f(-pointToNextPoint.y, pointToNextPoint.x));

        // Front facing, mark it
        facingFrontBothEdges.push_back((vectorDot(firstEdgeRay, normal) > 0.0f && vectorDot(secondEdgeRay, normal) > 0.0f) || (vectorDot(firstNextEdgeRay, normal) > 0.0f && vectorDot(secondNextEdgeRay, normal) > 0.0f));
        facingFrontOneEdge.push_back((vectorDot(firstEdgeRay, normal) > 0.0f || vectorDot(secondEdgeRay, normal) > 0.0f) || vectorDot(firstNextEdgeRay, normal) > 0.0f || vectorDot(secondNextEdgeRay, normal) > 0.0f);
    }

    // Go through front/back facing list. Where the facing direction switches, there is a boundary
    for (int i = 1; i < numPoints; i++)
        if (facingFrontBothEdges[i] != facingFrontBothEdges[i - 1]) {
            innerBoundaryIndices.push_back(i);
            bothEdgesBoundaryWindings.push_back(facingFrontBothEdges[i]);
        }

    // Check looping indices separately
    if (facingFrontBothEdges[0] != facingFrontBothEdges[numPoints - 1]) {
        innerBoundaryIndices.push_back(0);
        bothEdgesBoundaryWindings.push_back(facingFrontBothEdges[0]);
    }

    // Go through front/back facing list. Where the facing direction switches, there is a boundary
    for (int i = 1; i < numPoints; i++)
        if (facingFrontOneEdge[i] != facingFrontOneEdge[i - 1])
            outerBoundaryIndices.push_back(i);

    // Check looping indices separately
    if (facingFrontOneEdge[0] != facingFrontOneEdge[numPoints - 1])
        outerBoundaryIndices.push_back(0);

    for (unsigned bi = 0; bi < innerBoundaryIndices.size(); bi++) {
        int penumbraIndex = innerBoundaryIndices[bi];
        bool winding = bothEdgesBoundaryWindings[bi];

        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));

        sf::Vector2f perpendicularOffset(-sourceDirection.y, sourceDirection.x);

        perpendicularOffset = vectorNormalize(perpendicularOffset);
        perpendicularOffset *= sourceRadius;

        sf::Vector2f firstEdgeRay = point - (point - sourceDirection * sourceDistance + perpendicularOffset);
        sf::Vector2f secondEdgeRay = point - (point - sourceDirection * sourceDistance - perpendicularOffset);

        // Add boundary vector
        innerBoundaryVectors.push_back(winding ? secondEdgeRay : firstEdgeRay);
        sf::Vector2f outerBoundaryVector = winding ? firstEdgeRay : secondEdgeRay;

        outerBoundaryVectors.push_back(outerBoundaryVector);

        // Add penumbras
        bool hasPrevPenumbra = false;

        sf::Vector2f prevPenumbraLightEdgeVector;

        float prevBrightness = 1.0f;

        int counter = 0;

        while (penumbraIndex != -1) {
            sf::Vector2f nextPoint;
            int nextPointIndex;

            if (penumbraIndex < numPoints - 1) {
                nextPointIndex = penumbraIndex + 1;
                nextPoint = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex + 1));
            }
            else {
                nextPointIndex = 0;
                nextPoint = shape.getTransform().transformPoint(shape.getPoint(0));
            }

            sf::Vector2f pointToNextPoint = nextPoint - point;

            sf::Vector2f prevPoint;
            int prevPointIndex;

            if (penumbraIndex > 0) {
                prevPointIndex = penumbraIndex - 1;
                prevPoint = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex - 1));
            }
            else {
                prevPointIndex = numPoints - 1;
                prevPoint = shape.getTransform().transformPoint(shape.getPoint(numPoints - 1));
            }

            sf::Vector2f pointToPrevPoint = prevPoint - point;

            LightSystem::Penumbra penumbra;

            penumbra._source = point;

            if (!winding) {
                if (hasPrevPenumbra)
                    penumbra._lightEdge = prevPenumbraLightEdgeVector;
                else
                    penumbra._lightEdge = innerBoundaryVectors.back();

                penumbra._darkEdge = outerBoundaryVector;

                penumbra._lightBrightness = prevBrightness;

                // Next point, check for intersection
                float intersectionAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(pointToNextPoint)));
                float penumbraAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(penumbra._darkEdge)));

                if (intersectionAngle < penumbraAngle) {
                    prevBrightness = penumbra._darkBrightness = intersectionAngle / penumbraAngle;

                    assert(prevBrightness >= 0.0f && prevBrightness <= 1.0f);

                    penumbra._darkEdge = pointToNextPoint;

                    penumbraIndex = nextPointIndex;

                    if (hasPrevPenumbra) {
                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
                    }

                    hasPrevPenumbra = true;

                    prevPenumbraLightEdgeVector = penumbra._darkEdge;

                    point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));

                    perpendicularOffset = sf::Vector2f(-sourceDirection.y, sourceDirection.x);

                    perpendicularOffset = vectorNormalize(perpendicularOffset);
                    perpendicularOffset *= sourceRadius;

                    firstEdgeRay = point - (point - sourceDirection * sourceDistance + perpendicularOffset);
                    secondEdgeRay = point - (point - sourceDirection * sourceDistance - perpendicularOffset);

                    outerBoundaryVector = secondEdgeRay;
                }
                else {
                    penumbra._darkBrightness = 0.0f;

                    if (hasPrevPenumbra) {
                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
                    }

                    hasPrevPenumbra = false;

                    penumbraIndex = -1;
                }
            }
            else {
                if (hasPrevPenumbra)
                    penumbra._lightEdge = prevPenumbraLightEdgeVector;
                else
                    penumbra._lightEdge = innerBoundaryVectors.back();

                penumbra._darkEdge = outerBoundaryVector;

                penumbra._lightBrightness = prevBrightness;

                // Next point, check for intersection
                float intersectionAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(pointToPrevPoint)));
                float penumbraAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(penumbra._darkEdge)));

                if (intersectionAngle < penumbraAngle) {
                    prevBrightness = penumbra._darkBrightness = intersectionAngle / penumbraAngle;

                    assert(prevBrightness >= 0.0f && prevBrightness <= 1.0f);

                    penumbra._darkEdge = pointToPrevPoint;

                    penumbraIndex = prevPointIndex;

                    if (hasPrevPenumbra) {
                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
                    }

                    hasPrevPenumbra = true;

                    prevPenumbraLightEdgeVector = penumbra._darkEdge;

                    point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));

                    perpendicularOffset = sf::Vector2f(-sourceDirection.y, sourceDirection.x);

                    perpendicularOffset = vectorNormalize(perpendicularOffset);
                    perpendicularOffset *= sourceRadius;

                    firstEdgeRay = point - (point - sourceDirection * sourceDistance + perpendicularOffset);
                    secondEdgeRay = point - (point - sourceDirection * sourceDistance - perpendicularOffset);

                    outerBoundaryVector = firstEdgeRay;
                }
                else {
                    penumbra._darkBrightness = 0.0f;

                    if (hasPrevPenumbra) {
                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
                    }

                    hasPrevPenumbra = false;

                    penumbraIndex = -1;
                }
            }

            penumbras.push_back(penumbra);

            counter++;
        }
    }
}

void LightSystem::create(const sf::FloatRect& rootRegion, const sf::Vector2u& imageSize,
                         const sf::Texture& penumbraTexture,
                         sf::Shader& unshadowShader, sf::Shader& lightOverShapeShader, sf::Shader& normalsShader)
{
    _shapeQuadtree.create(rootRegion);
    _lightPointEmissionQuadtree.create(rootRegion);

    _lightTempTexture.create(imageSize.x, imageSize.y);
    _emissionTempTexture.create(imageSize.x, imageSize.y);
    _antumbraTempTexture.create(imageSize.x, imageSize.y);
    _compositionTexture.create(imageSize.x, imageSize.y);
    _normalsTexture.create(imageSize.x, imageSize.y);

    normalsTargetClear();

    sf::Vector2f targetSizeInv = sf::Vector2f(1.0f / imageSize.x, 1.0f / imageSize.y);

    unshadowShader.setParameter("penumbraTexture", penumbraTexture);

    lightOverShapeShader.setParameter("emissionTexture", _emissionTempTexture.getTexture());
    lightOverShapeShader.setParameter("targetSizeInv", targetSizeInv);

    normalsShader.setParameter("normalsTexture", _normalsTexture.getTexture());
    normalsShader.setParameter("targetSize", imageSize.x, imageSize.y);
    normalsShader.setParameter("lightTexture", sf::Shader::CurrentTexture);
}

void LightSystem::render(const sf::View &view, sf::Shader &unshadowShader, sf::Shader &lightOverShapeShader, sf::Shader& normalsShader)
{
    _compositionTexture.clear(_ambientColor);
    _compositionTexture.setView(_compositionTexture.getDefaultView());

    // Get bounding rectangle of view
    sf::FloatRect viewBounds = sf::FloatRect(view.getCenter().x, view.getCenter().y, 0.0f, 0.0f);
    sf::FloatRect centeredViewBounds = rectRecenter(viewBounds, sf::Vector2f(0.0f, 0.0f));

    _lightTempTexture.setView(view);

    viewBounds = rectExpand(viewBounds, _lightTempTexture.mapPixelToCoords(sf::Vector2i(0, 0)));
    viewBounds = rectExpand(viewBounds, _lightTempTexture.mapPixelToCoords(sf::Vector2i(_lightTempTexture.getSize().x, 0)));
    viewBounds = rectExpand(viewBounds, _lightTempTexture.mapPixelToCoords(sf::Vector2i(_lightTempTexture.getSize().x, _lightTempTexture.getSize().y)));
    viewBounds = rectExpand(viewBounds, _lightTempTexture.mapPixelToCoords(sf::Vector2i(0, _lightTempTexture.getSize().y)));

    std::vector<QuadtreeOccupant*> viewPointEmissionLights;

    _lightPointEmissionQuadtree.queryRegion(viewPointEmissionLights, viewBounds);

    sf::RenderStates compoRenderStates;
    compoRenderStates.blendMode = sf::BlendAdd;

    //----- Point lights

    std::vector<QuadtreeOccupant*> lightShapes;
    sf::Sprite lightTempSprite(_lightTempTexture.getTexture());

    for (auto occupant : viewPointEmissionLights) {
        auto pPointEmissionLight = static_cast<LightPointEmission*>(occupant);

        // Query shapes this light is affected by
        lightShapes.clear();
        _shapeQuadtree.queryRegion(lightShapes, pPointEmissionLight->getAABB());

        pPointEmissionLight->render(view, _lightTempTexture, _emissionTempTexture, _antumbraTempTexture, lightShapes, unshadowShader, lightOverShapeShader, _normalsEnabled, normalsShader);
        _compositionTexture.draw(lightTempSprite, compoRenderStates);
    }

    //----- Direction lights

    for (const auto& directionEmissionLight : _directionEmissionLights) {
        LightDirectionEmission* pDirectionEmissionLight = directionEmissionLight.get();

        float maxDim = std::max(centeredViewBounds.width, centeredViewBounds.height);
        sf::FloatRect extendedViewBounds = rectFromBounds(sf::Vector2f(-maxDim, -maxDim) * _directionEmissionRadiusMultiplier,
                                                          sf::Vector2f(maxDim, maxDim) * _directionEmissionRadiusMultiplier + sf::Vector2f(_directionEmissionRange, 0.0f));
        float shadowExtension = vectorMagnitude(rectLowerBound(centeredViewBounds)) * _directionEmissionRadiusMultiplier * 2.0f;

        sf::ConvexShape directionShape = shapeFromRect(extendedViewBounds);
        directionShape.setPosition(view.getCenter());

        sf::Vector2f normalizedCastDirection = vectorNormalize(pDirectionEmissionLight->_castDirection);
        directionShape.setRotation(_radToDeg * std::atan2(normalizedCastDirection.y, normalizedCastDirection.x));

        std::vector<QuadtreeOccupant*> viewLightShapes;
        _shapeQuadtree.queryShape(viewLightShapes, directionShape);

        pDirectionEmissionLight->render(view, _lightTempTexture, _antumbraTempTexture, viewLightShapes, unshadowShader, shadowExtension);

        sf::Sprite sprite;
        sprite.setTexture(_lightTempTexture.getTexture());
        _compositionTexture.draw(sprite, compoRenderStates);

        // TODO Normals
    }

    _compositionTexture.display();
}

LightShape* LightSystem::allocateShape()
{
    return _lightShapesPool.newElement();
}

void LightSystem::deallocateShape(LightShape* pLightShape)
{
    _lightShapesPool.deleteElement(pLightShape);
}

void LightSystem::addShape(LightShape* pLightShape)
{
    _shapeQuadtree.add(pLightShape);
}

void LightSystem::removeShape(LightShape* pLightShape)
{
    pLightShape->quadtreeRemove();
}

void LightSystem::addLight(const std::shared_ptr<LightPointEmission> &pointEmissionLight) {
    _lightPointEmissionQuadtree.add(pointEmissionLight.get());
    _pointEmissionLights.insert(pointEmissionLight);
}

void LightSystem::addLight(const std::shared_ptr<LightDirectionEmission> &directionEmissionLight) {
    _directionEmissionLights.insert(directionEmissionLight);
}

void LightSystem::removeLight(const std::shared_ptr<LightPointEmission> &pointEmissionLight) {
    std::unordered_set<std::shared_ptr<LightPointEmission>>::iterator it = _pointEmissionLights.find(pointEmissionLight);

    if (it != _pointEmissionLights.end()) {
        (*it)->quadtreeRemove();

        _pointEmissionLights.erase(it);
    }
}

void LightSystem::removeLight(const std::shared_ptr<LightDirectionEmission> &directionEmissionLight) {
    std::unordered_set<std::shared_ptr<LightDirectionEmission>>::iterator it = _directionEmissionLights.find(directionEmissionLight);
    if (it != _directionEmissionLights.end())
        _directionEmissionLights.erase(it);
}

//----- Normals -----//

void LightSystem::normalsEnabled(bool enabled)
{
    _normalsEnabled = enabled;
}

void LightSystem::normalsTargetSetView(sf::View view)
{
    _normalsTexture.setView(view);
}

void LightSystem::normalsTargetClear()
{
    _normalsTexture.clear(sf::Color{127u, 127u, 255u});
}

void LightSystem::normalsTargetDisplay()
{
    _normalsTexture.display();
}

void LightSystem::normalsTargetDraw(const sf::Drawable& drawable, sf::RenderStates states)
{
    _normalsTexture.draw(drawable, states);
}
