package electrosphere.client.foliagemanager;

import java.util.HashSet;
import java.util.Set;

import org.joml.Matrix4d;
import org.joml.Quaterniond;
import org.joml.Sphered;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector3i;

import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.camera.CameraEntityUtils;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.actor.instance.TextureInstancedActor;
import electrosphere.renderer.buffer.ShaderAttribute;

/**
 * Contains a set of foliage entities and groups them together.
 */
public class FoliageCell {
    //position of the foliage cell in world coordinates
    protected Vector3i worldPosition;
    //position of the foliage cell in local coordinates
    protected Vector3i voxelPosition;
    //the real position of this cell, stored so we don't constantly have to recalculate
    protected Vector3d realPosition;
    //constituent entities
    protected Set<Entity> containedEntities;

    //template bounding shere used for checking frustum for this cell
    static Sphered boundingSphere = new Sphered(0.5,0.5,0.5,2);

    /**
     * Constructor
     * @param worldPos The position of the foliage cell in world coordinates
     * @param voxelPos The position of the foliage cell in voxel coordinates
     */
    protected FoliageCell(Vector3i worldPos, Vector3i voxelPos, Vector3d realPos){
        this.worldPosition = worldPos;
        this.voxelPosition = voxelPos;
        this.realPosition = realPos;
        this.containedEntities = new HashSet<Entity>();
    }

    /**
     * Adds an entity to this foliage cell
     * @param entity The entity to add
     */
    protected void addEntity(Entity entity){
        containedEntities.add(entity);
    }

    /**
     * Clears all entities in this foliage cell
     */
    protected void clearEntities(){
        containedEntities.clear();
    }

    /**
     * Destroys all entities in this foliage cell
     */
    protected void destroy(){
        for(Entity entity : containedEntities){
            EntityUtils.cleanUpEntity(entity);
        }
        clearEntities();
    }

    /**
     * Draws all entities in the foliage cell
     * @param modelMatrixAttribute The model matrix attribute to draw with
     */
    protected void draw(ShaderAttribute modelMatrixAttribute){
        Matrix4d modelMatrix = new Matrix4d();
        Vector3f cameraCenter = CameraEntityUtils.getCameraCenter(Globals.playerCamera);

        RenderPipelineState renderPipelineState = Globals.renderingEngine.getRenderPipelineState();
        OpenGLState openGLState = Globals.renderingEngine.getOpenGLState();

        Vector3f cameraModifiedPosition = new Vector3f((float)realPosition.x,(float)realPosition.y,(float)realPosition.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
        //frustum check entire cell
        boolean shouldRender = renderPipelineState.getFrustumIntersection().testSphere((float)(cameraModifiedPosition.x + boundingSphere.x), (float)(cameraModifiedPosition.y + boundingSphere.y), (float)(cameraModifiedPosition.z + boundingSphere.z), (float)(boundingSphere.r));
        if(shouldRender){
            //disable frustum check and instead perform at cell level
            boolean currentFrustumCheckState = renderPipelineState.shouldFrustumCheck();
            renderPipelineState.setFrustumCheck(false);
            for(Entity entity : containedEntities){
                Vector3d grassPosition = EntityUtils.getPosition(entity);
                Quaterniond grassRotation = EntityUtils.getRotation(entity);
                TextureInstancedActor actor = TextureInstancedActor.getTextureInstancedActor(entity);

                
                modelMatrix = modelMatrix.identity();
                cameraModifiedPosition = new Vector3f((float)grassPosition.x,(float)grassPosition.y,(float)grassPosition.z).sub(cameraCenter);
                modelMatrix.translate(cameraModifiedPosition);
                modelMatrix.rotate(new Quaterniond(grassRotation));
                modelMatrix.scale(new Vector3d(EntityUtils.getScale(entity)));
                actor.applySpatialData(modelMatrix,grassPosition);


                //draw
                actor.draw(renderPipelineState, openGLState);
            }
            renderPipelineState.setFrustumCheck(currentFrustumCheckState);
        }
    }
}
