/*
 * Copyright 2016 MovingBlocks
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.terasology.rendering.logic;

import com.google.common.collect.Maps;
import org.terasology.assets.management.AssetManager;
import org.terasology.entitySystem.entity.EntityManager;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.entity.lifecycleEvents.BeforeDeactivateComponent;
import org.terasology.entitySystem.entity.lifecycleEvents.OnChangedComponent;
import org.terasology.entitySystem.event.ReceiveEvent;
import org.terasology.entitySystem.systems.BaseComponentSystem;
import org.terasology.entitySystem.systems.RegisterMode;
import org.terasology.entitySystem.systems.RegisterSystem;
import org.terasology.entitySystem.systems.RenderSystem;
import org.terasology.logic.location.LocationComponent;
import org.terasology.math.geom.Vector3f;
import org.terasology.registry.In;
import org.terasology.rendering.assets.font.Font;
import org.terasology.rendering.assets.font.FontMeshBuilder;
import org.terasology.rendering.assets.material.Material;
import org.terasology.rendering.assets.mesh.Mesh;
import org.terasology.rendering.cameras.Camera;
import org.terasology.rendering.nui.Color;
import org.terasology.rendering.nui.HorizontalAlign;
import org.terasology.rendering.opengl.OpenGLUtils;
import org.terasology.world.WorldProvider;

import java.util.Arrays;
import java.util.Map;

import static org.lwjgl.opengl.GL11.*;


@RegisterSystem(RegisterMode.CLIENT)
public class FloatingTextRenderer extends BaseComponentSystem implements RenderSystem {

    private static final int PIXEL_PER_METER = 250;

    private static final float METER_PER_PIXEL = 1.0f / PIXEL_PER_METER;

    @In
    private EntityManager entityManager;

    @In
    private AssetManager assetManager;

    @In
    private WorldProvider worldProvider;

    @In
    private Camera camera;

    private Font font;
    private Material underlineMaterial;
    private Map<EntityRef, Map<Material, Mesh>> entityMeshCache = Maps.newHashMap();

    @Override
    public void initialise() {
        this.font = assetManager.getAsset("engine:NotoSans-Regular-Large", Font.class).get();
        this.underlineMaterial = assetManager.getAsset("engine:UIUnderline", Material.class).get();
    }

    private void render(Iterable<EntityRef> floatingTextEntities) {
        Vector3f cameraPosition = camera.getPosition();

        for (EntityRef entity : floatingTextEntities) {
            LocationComponent location = entity.getComponent(LocationComponent.class);
            if (location == null) {
                continue;
            }

            Vector3f worldPos = location.getWorldPosition();
            if (!worldProvider.isBlockRelevant(worldPos)) {
                continue;
            }

            FloatingTextComponent floatingText = entity.getComponent(FloatingTextComponent.class);

            // If the FloatingTextComponent is meant to be occluded, enable depth tests
            if (floatingText.isOccluded) {
                glEnable(GL_DEPTH_TEST);
                glDepthFunc(GL_LESS);
            } else {
                glDisable(GL_DEPTH_TEST);
            }

            String[] linesOfText = floatingText.text.split("\n");
            Color baseColor = floatingText.textColor;
            Color shadowColor = floatingText.textShadowColor;
            boolean underline = false;

            int textWidth = 0;
            for (String singleLine : linesOfText) {
                if (font.getWidth(singleLine) > textWidth) {
                    textWidth = font.getWidth(singleLine);
                }
            }

            FontMeshBuilder meshBuilder = new FontMeshBuilder(underlineMaterial);

            Map<Material, Mesh> meshMap = entityMeshCache.get(entity);
            if (meshMap == null) {
                meshMap = meshBuilder
                        .createTextMesh(font, Arrays.asList(linesOfText), textWidth, HorizontalAlign.CENTER, baseColor,
                                shadowColor, underline);
                entityMeshCache.put(entity, meshMap);
            }
            glPushMatrix();

            float scale = METER_PER_PIXEL * floatingText.scale;

            glTranslated(worldPos.x - cameraPosition.x, worldPos.y - cameraPosition.y, worldPos.z - cameraPosition.z);
            OpenGLUtils.applyBillboardOrientation();
            glScaled(scale, -scale, scale);
            glTranslated(-textWidth / 2.0, 0.0, 0.0);
            for (Map.Entry<Material, Mesh> meshMapEntry : meshMap.entrySet()) {
                Mesh mesh = meshMapEntry.getValue();
                Material material = meshMapEntry.getKey();
                material.enable();
                material.bindTextures();
                material.setFloat4("croppingBoundaries", Float.MIN_VALUE, Float.MAX_VALUE,
                        Float.MIN_VALUE, Float.MAX_VALUE);
                material.setFloat2("offset", 0.0f, 0.0f);
                material.setFloat("alpha", 1.0f);
                mesh.render();
            }

            glPopMatrix();
        }
        glEnable(GL_DEPTH_TEST);
    }

    private void diposeMeshMap(Map<Material, Mesh> meshMap) {
        for (Map.Entry<Material, Mesh> meshMapEntry : meshMap.entrySet()) {
            Mesh mesh = meshMapEntry.getValue();
            mesh.dispose();
            // Note: Material belongs to font and must not be disposed
        }
    }

    @Override
    public void renderOpaque() {
    }

    @Override
    public void renderAlphaBlend() {
        render(entityManager.getEntitiesWith(FloatingTextComponent.class, LocationComponent.class));
    }

    @Override
    public void renderFirstPerson() {
    }

    @Override
    public void renderOverlay() {
    }

    @Override
    public void renderShadows() {
    }

    @ReceiveEvent(components = {FloatingTextComponent.class})
    public void onDisplayNameChange(OnChangedComponent event, EntityRef entity) {
        disposeCachedMeshOfEntity(entity);
    }


    @ReceiveEvent(components = {FloatingTextComponent.class})
    public void onNameTagOwnerRemoved(BeforeDeactivateComponent event, EntityRef entity) {
        disposeCachedMeshOfEntity(entity);
    }

    private void disposeCachedMeshOfEntity(EntityRef entity) {
        Map<Material, Mesh> meshMap = entityMeshCache.remove(entity);
        if (meshMap != null) {
            diposeMeshMap(meshMap);
        }
    }

    @Override
    public void shutdown() {
        entityMeshCache.values().forEach(this::diposeMeshMap);
        entityMeshCache.clear();
    }
}

@@ -3586,46 +3586,8 @@
 T);%0A
-                glDepthFunc(GL_LESS);%0A
     

Remove glDepthFunc modification