package electrosphere.renderer.actor.instance;

import org.joml.Matrix4d;
import org.joml.Vector3d;

import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityDataStrings;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.model.Material;
import electrosphere.renderer.model.Model;
import electrosphere.renderer.shader.ShaderProgram;
import electrosphere.renderer.texture.Texture;

/**
 * An actor that will trigger an instance call when you draw the model; however it uses a texture to store data about its instances
 */
public class TextureInstancedActor {

    //path of the model that this instanced actor uses
    String modelPath;

    //the material that will contain the data about the model
    Material material;

    //the draw count of the texture instanced actor
    int drawCount;

    //shader paths
    String vertexShaderPath;
    String fragmentShaderPath;

    /**
     * Creates an instanced actor
     * @param modelPath The path of the model this actor uses
     */
    protected TextureInstancedActor(String modelPath, String vertexShaderPath, String fragmentShaderPath, Texture dataTexture, int drawCount){
        this.modelPath = modelPath;
        this.material = new Material();
        this.material.setTexturePointer(dataTexture.getTexturePointer());
        this.drawCount = drawCount;
        this.vertexShaderPath = vertexShaderPath;
        this.fragmentShaderPath = fragmentShaderPath;
    }

    /**
     * Attaches a TextureInstancedActor to an entity
     * @param parent The entity
     * @param modelPath The path to the model for this instanced actor
     * @param dataTexture The data texture containing data for this actor
     */
    public static void attachTextureInstancedActor(Entity parent, String modelPath, String vertexShaderPath, String fragmentShaderPath, Texture dataTexture, int drawCount){
        TextureInstancedActor newActor = new TextureInstancedActor(modelPath, vertexShaderPath, fragmentShaderPath, dataTexture, drawCount);
        parent.putData(EntityDataStrings.TEXTURE_INSTANCED_ACTOR, newActor);
    }
    
    /**
     * Draws the instanced actor. Should be called normally in a loop as if this was a regular actor.
     * @param renderPipelineState The pipeline state of the instanced actor
     * @param position The position used for frustum checking
     */
    public void draw(RenderPipelineState renderPipelineState, OpenGLState openGLState){
        Model model = Globals.assetManager.fetchModel(modelPath);
        ShaderProgram shader = Globals.assetManager.fetchShader(vertexShaderPath, null, fragmentShaderPath);
        if(model != null && shader != null){
            //setup render pipeline
            boolean instancedState = renderPipelineState.getInstanced();
            boolean materialState = renderPipelineState.getUseMaterial();
            boolean useShader = renderPipelineState.getUseMeshShader();
            boolean bufferStandardUniforms = renderPipelineState.getBufferStandardUniforms();
            boolean bufferNonStandardUniforms = renderPipelineState.getBufferNonStandardUniforms();
            boolean useLight = renderPipelineState.getUseLight();
            renderPipelineState.setInstanced(true);
            renderPipelineState.setUseMaterial(false);
            renderPipelineState.setUseMeshShader(false);
            renderPipelineState.setInstanceCount(drawCount);
            renderPipelineState.setBufferStandardUniforms(true);
            renderPipelineState.setBufferNonStandardUniforms(true);
            renderPipelineState.setUseLight(true);
            renderPipelineState.setInstanceData(null); //need to set the instance data to null otherwise it will overwrite what we currently have set (ie overwrite draw calls count, etc)


            openGLState.setActiveShader(renderPipelineState, shader);
            this.material.apply_material(openGLState);
            model.draw(renderPipelineState, openGLState);

            //reset render pipeline state
            renderPipelineState.setInstanced(instancedState);
            renderPipelineState.setUseMaterial(materialState);
            renderPipelineState.setUseMeshShader(useShader);
            renderPipelineState.setBufferStandardUniforms(bufferStandardUniforms);
            renderPipelineState.setBufferNonStandardUniforms(bufferNonStandardUniforms);
            renderPipelineState.setUseLight(useLight);
        }
    }

    /**
     * Gets the path of the model packing this instanced actore
     * @return The path of the model
     */
    protected String getModelPath(){
        return this.modelPath;
    }

    /**
     * Gets the texture instanced actor attached to this entity
     * @param parent The parent entity
     * @return The texture instanced actor if it exists
     */
    public static TextureInstancedActor getTextureInstancedActor(Entity parent){
        return (TextureInstancedActor)parent.getData(EntityDataStrings.TEXTURE_INSTANCED_ACTOR);
    }

    /**
     * Applies spatial data on the actor, this includes the model matrix as well as the real world position
     * @param modelMatrix The model matrix
     * @param worldPos the real world coordinates of the model
     */
    public void applySpatialData(Matrix4d modelMatrix, Vector3d worldPos){
        Model model = Globals.assetManager.fetchModel(modelPath);
        if(model != null){
            model.setModelMatrix(modelMatrix);
            model.setWorldPos(worldPos);
        }
    }
}
