package electrosphere.renderer.ui.elements;

import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_LESS;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glDepthMask;
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;

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

import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.RenderingEngine;
import electrosphere.renderer.actor.Actor;
import electrosphere.renderer.debug.DebugRendering;
import electrosphere.renderer.framebuffer.Framebuffer;
import electrosphere.renderer.framebuffer.FramebufferUtils;
import electrosphere.renderer.model.Material;
import electrosphere.renderer.model.Model;
import electrosphere.renderer.ui.elementtypes.DraggableElement;
import electrosphere.renderer.ui.elementtypes.DrawableElement;
import electrosphere.renderer.ui.events.DragEvent;
import electrosphere.renderer.ui.events.DragEvent.DragEventType;
import electrosphere.renderer.ui.events.Event;

public class ActorPanel extends StandardElement implements DrawableElement, DraggableElement {

    static Vector3f color = new Vector3f(1.0f);

    Material customMat = new Material();
    Framebuffer elementBuffer;

    Actor actor;
    Matrix4d modelMatrix = new Matrix4d();
    String currentAnim;
    Vector3f actorPosition = new Vector3f(0,0,0);
    Quaterniond actorRotation = new Quaterniond();
    Vector3f actorScale = new Vector3f(1,1,1);
    float FOV = 50.0f;
    float aspectRatio = 1.9f;
    
    
    Vector3f texPosition = new Vector3f(0,0,0);
    Vector3f texScale = new Vector3f(1,1,0);

    DragEventCallback onDragStart;
    DragEventCallback onDrag;
    DragEventCallback onDragRelease;

    static final Vector3f windowDrawDebugColor = new Vector3f(0.0f,0.0f,1.0f);
    
    public ActorPanel(OpenGLState openGLState, int x, int y, int width, int height, Actor actor){
        super();
        elementBuffer = FramebufferUtils.generateTextureFramebuffer(openGLState, width, height);
        customMat.setTexturePointer(elementBuffer.getTexture().getTexturePointer());
        this.actor = actor;
        this.internalPositionX = x;
        this.internalPositionY = y;
        this.setWidth(width);
        this.setHeight(height);
        this.aspectRatio = (float)width / (float)height;
        recalculateModelMatrix();
    }
    
    

    @Override
    public void draw(
        RenderPipelineState renderPipelineState,
        OpenGLState openGLState,
        int parentFramebufferPointer,
        int parentPosX,
        int parentPosY,
        int parentWidth,
        int parentHeight
        ) {

        elementBuffer.bind(openGLState);
//        Globals.renderingEngine.setViewportSize(width, height);

        RenderingEngine.setFOV(FOV);
        RenderingEngine.setAspectRatio(aspectRatio);
        
        openGLState.glDepthTest(true);
        openGLState.glDepthFunc(GL_LESS);
        glDepthMask(true);
        openGLState.glViewport(width, height);

        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        Model actorModel = Globals.assetManager.fetchModel(actor.getModelPath());

        if(currentAnim != null){
            if((!actor.isPlayingAnimation() || !actor.isPlayingAnimation(currentAnim)) &&
            actorModel != null &&
            actorModel.getAnimation(currentAnim) != null
            ){
                actor.playAnimation(currentAnim,3);
                actor.incrementAnimationTime(0.0001);
            }
        }

        //
        // Set rendering engine state
        //
        renderPipelineState.setUseMeshShader(true);
        renderPipelineState.setBufferStandardUniforms(true);
        renderPipelineState.setBufferNonStandardUniforms(true);
        renderPipelineState.setUseMaterial(true);
        renderPipelineState.setUseShadowMap(true);
        renderPipelineState.setUseBones(true);
        renderPipelineState.setUseLight(true);






        
        actor.applySpatialData(modelMatrix,new Vector3d(actorPosition));
        actor.draw(renderPipelineState,openGLState);

        RenderingEngine.setFOV(Globals.verticalFOV);
        RenderingEngine.setAspectRatio(Globals.aspectRatio);

        openGLState.glDepthTest(false);

        //this call binds the screen as the "texture" we're rendering to
        //have to call before actually rendering
        openGLState.glBindFramebuffer(GL_FRAMEBUFFER, parentFramebufferPointer);

        //set viewport
        openGLState.glViewport(parentWidth, parentHeight);
        
        float ndcX = (float)getInternalX()/parentWidth;
        float ndcY = (float)getInternalY()/parentHeight;
        float ndcWidth = (float)getInternalWidth()/parentWidth;
        float ndcHeight = (float)getInternalHeight()/parentHeight;
        
        Vector3f boxPosition = new Vector3f(ndcX,ndcY,0);
        Vector3f boxDimensions = new Vector3f(ndcWidth,ndcHeight,0);





        
        //
        // Set rendering engine state
        //
        renderPipelineState.setUseMeshShader(true);
        renderPipelineState.setBufferStandardUniforms(false);
        renderPipelineState.setBufferNonStandardUniforms(true);
        renderPipelineState.setUseMaterial(true);
        renderPipelineState.setUseShadowMap(false);
        renderPipelineState.setUseBones(false);
        renderPipelineState.setUseLight(false);



        

        Model planeModel = Globals.assetManager.fetchModel(Globals.imagePlaneModelID);
        if(planeModel != null){
            planeModel.pushUniformToMesh("plane", "mPosition", boxPosition);
            planeModel.pushUniformToMesh("plane", "mDimension", boxDimensions);
            planeModel.pushUniformToMesh("plane", "tPosition", texPosition);
            planeModel.pushUniformToMesh("plane", "tDimension", texScale);
            planeModel.pushUniformToMesh(planeModel.getMeshes().get(0).getMeshName(), "color", color);
            planeModel.getMeshes().get(0).setMaterial(customMat);
            planeModel.draw(renderPipelineState,Globals.renderingEngine.getOpenGLState());
        } else {
            LoggerInterface.loggerRenderer.ERROR("Actor Panel unable to find plane model!!", new Exception());
        }

        if(Globals.RENDER_FLAG_RENDER_UI_BOUNDS && DebugRendering.RENDER_DEBUG_OUTLINE_ACTOR_PANEL){
            DebugRendering.drawUIBounds(parentFramebufferPointer, boxPosition, boxDimensions, windowDrawDebugColor);
        }
    }

    
    public boolean visible = false;


    public void setVisible(boolean draw) {
        this.visible = draw;
    }

    public boolean getVisible(){
        return this.visible;
    }
    
    public void setAnimation(String animation){
        currentAnim = animation;
    }

    public void setPosition(Vector3f position){
        this.actorPosition.set(position);
        recalculateModelMatrix();
    }

    public void setRotation(Quaterniond rotation){
        this.actorRotation.set(rotation);
        recalculateModelMatrix();
    }

    public void setScale(Vector3f scale){
        this.actorScale.set(scale);
        recalculateModelMatrix();
    }

    void recalculateModelMatrix(){
        modelMatrix.identity();
        modelMatrix.translate(actorPosition);
        modelMatrix.rotate(actorRotation);
        modelMatrix.scale(new Vector3d(actorScale));
        actor.applySpatialData(modelMatrix,new Vector3d(actorPosition));
    }

    public boolean handleEvent(Event event){
        boolean propagate = true;
        if(event instanceof DragEvent){
            if(onDragStart != null && ((DragEvent)event).getType() == DragEventType.START){
                if(!onDragStart.execute((DragEvent)event)){
                    propagate = false;
                }
            }
            if(onDrag != null && ((DragEvent)event).getType() == DragEventType.DRAG){
                if(!onDrag.execute((DragEvent)event)){
                    propagate = false;
                }
            }
            if(onDragRelease != null && ((DragEvent)event).getType() == DragEventType.RELEASE){
                if(!onDragRelease.execute((DragEvent)event)){
                    propagate = false;
                }
            }
        }
        return propagate;
    }

    @Override
    public void setOnDragStart(DragEventCallback callback) {
        onDragStart = callback;
    }

    @Override
    public void setOnDrag(DragEventCallback callback) {
        onDrag = callback;
    }

    @Override
    public void setOnDragRelease(DragEventCallback callback) {
        onDragRelease = callback;
    }
    


}
