package electrosphere.renderer.pipelines.debug;

import java.util.LinkedList;
import java.util.List;

import org.joml.Matrix4d;
import org.joml.Quaterniond;
import org.joml.Quaternionf;
import org.joml.Vector3d;
import org.lwjgl.opengl.GL40;
import org.ode4j.ode.DCapsule;
import org.ode4j.ode.DGeom;
import org.ode4j.ode.DSphere;

import electrosphere.client.entity.camera.CameraEntityUtils;
import electrosphere.collision.PhysicsUtils;
import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.state.hitbox.HitboxCollectionState;
import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxState;
import electrosphere.game.data.collidable.CollidableTemplate;
import electrosphere.game.data.collidable.HitboxData;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.RenderingEngine;
import electrosphere.renderer.model.Model;
import electrosphere.renderer.pipelines.RenderPipeline;
import electrosphere.renderer.texture.Texture;
import electrosphere.server.datacell.Realm;
import electrosphere.server.datacell.utils.EntityLookupUtils;
import electrosphere.server.pathfinding.navmesh.NavCube;
import electrosphere.server.pathfinding.navmesh.NavMesh;
import electrosphere.server.pathfinding.navmesh.NavShape;

/**
 * Pipeline for rendering content to assist debugging
 */
public class DebugContentPipeline implements RenderPipeline {

    //The bone debugging pipeline
    DebugBonesPipeline debugBonesPipeline = new DebugBonesPipeline();

    @Override
    public void render(OpenGLState openGLState, RenderPipelineState renderPipelineState) {
        Globals.profiler.beginCpuSample("DebugContentPipeline.render");

        //bind screen fbo
        RenderingEngine.screenFramebuffer.bind(openGLState);
        openGLState.glDepthTest(true);
        openGLState.glDepthFunc(GL40.GL_LESS);
        GL40.glDepthMask(true);
        openGLState.glViewport(Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        
        ///
        ///     R E N D E R I N G      S T U F F
        ///
        //Sets the background color.


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

        Matrix4d modelTransformMatrix = new Matrix4d();

        if(Globals.userSettings.getGraphicsDebugDrawCollisionSpheresClient()){
            Model hitboxModel;
            for(HitboxCollectionState hitboxState : Globals.clientSceneWrapper.getHitboxManager().getAllHitboxes()){
                for(DGeom geom : hitboxState.getGeometries()){
                    if(geom instanceof DSphere){
                        DSphere sphereView = (DSphere)geom;
                        HitboxState shapeStatus = hitboxState.getShapeStatus(geom);
                        if((hitboxModel = Globals.assetManager.fetchModel(AssetDataStrings.UNITSPHERE)) != null){
                            //set color based on collision status, type, etc
                            Texture texture = Globals.assetManager.fetchTexture(getHitboxColor(shapeStatus,shapeStatus.getHitboxData()));
                            if(texture != null){
                                texture.bind(openGLState);
                            }
                            Vector3d position = PhysicsUtils.odeVecToJomlVec(sphereView.getPosition());
                            //calculate camera-modified vector3d
                            Vector3d cameraModifiedPosition = new Vector3d(position).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(cameraModifiedPosition);
                            modelTransformMatrix.scale(sphereView.getRadius() * 2);
                            hitboxModel.setModelMatrix(modelTransformMatrix);
                            hitboxModel.draw(renderPipelineState,openGLState);
                        }
                    }
                    if(geom instanceof DCapsule){
                        DCapsule capsuleView = (DCapsule)geom;
                        HitboxState shapeStatus = hitboxState.getShapeStatus(geom);
                        if((hitboxModel = Globals.assetManager.fetchModel("Models/basic/geometry/unitcapsule.glb")) != null){
                            //set color based on collision status, type, etc
                            Texture texture = Globals.assetManager.fetchTexture(getHitboxColor(shapeStatus,shapeStatus.getHitboxData()));
                            if(texture != null){
                                texture.bind(openGLState);
                            }
                            Vector3d position = PhysicsUtils.odeVecToJomlVec(capsuleView.getPosition());
                            //calculate camera-modified vector3d
                            Vector3d cameraModifiedPosition = new Vector3d(position).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(cameraModifiedPosition);
                            //since you're directly accessing the quat from the body, need to adjust it to be in the correct orientation
                            modelTransformMatrix.rotate(PhysicsUtils.odeQuatToJomlQuat(capsuleView.getQuaternion()).mul(new Quaterniond(0.707,0,0,0.707)));
                            //the ode4j capsule's end caps are always at least radius length, the length only controls the distance between the two caps.
                            //unfortunately that won't be easy to replicate with rendering tech currently; instead, run logic below
                            double radius = capsuleView.getRadius();
                            double length = capsuleView.getLength();
                            if(length < radius) length = radius;
                            modelTransformMatrix.scale(radius,length,radius);
                            hitboxModel.setModelMatrix(modelTransformMatrix);
                            hitboxModel.draw(renderPipelineState,openGLState);
                        }
                    }
                }
            }
        }
        if(Globals.userSettings.getGraphicsDebugDrawCollisionSpheresServer()){
            Model hitboxModel;
            int serverIdForClientEntity = Globals.clientSceneWrapper.mapClientToServerId(Globals.playerEntity.getId());
            Entity serverPlayerEntity = EntityLookupUtils.getEntityById(serverIdForClientEntity);
            Realm playerRealm = Globals.realmManager.getEntityRealm(serverPlayerEntity);
            List<HitboxCollectionState> hitboxStates = new LinkedList<HitboxCollectionState>(playerRealm.getHitboxManager().getAllHitboxes());
            for(HitboxCollectionState hitboxState : hitboxStates){
                for(DGeom geom : hitboxState.getGeometries()){
                    if(geom instanceof DSphere){
                        DSphere sphereView = (DSphere)geom;
                        HitboxState shapeStatus = hitboxState.getShapeStatus(geom);
                        if((hitboxModel = Globals.assetManager.fetchModel(AssetDataStrings.UNITSPHERE)) != null){
                            //set color based on collision status, type, etc
                            Texture texture = Globals.assetManager.fetchTexture(getHitboxColor(shapeStatus,shapeStatus.getHitboxData()));
                            if(texture != null){
                                texture.bind(openGLState);
                            }
                            Vector3d position = PhysicsUtils.odeVecToJomlVec(sphereView.getPosition());
                            //calculate camera-modified vector3d
                            Vector3d cameraModifiedPosition = new Vector3d(position).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(cameraModifiedPosition);
                            modelTransformMatrix.scale(sphereView.getRadius() * 2);
                            hitboxModel.setModelMatrix(modelTransformMatrix);
                            hitboxModel.draw(renderPipelineState,openGLState);
                        }
                    }
                    if(geom instanceof DCapsule){
                        DCapsule capsuleView = (DCapsule)geom;
                        HitboxState shapeStatus = hitboxState.getShapeStatus(geom);
                        if((hitboxModel = Globals.assetManager.fetchModel("Models/basic/geometry/unitcapsule.glb")) != null){
                            //set color based on collision status, type, etc
                            Texture texture = Globals.assetManager.fetchTexture(getHitboxColor(shapeStatus,shapeStatus.getHitboxData()));
                            if(texture != null){
                                texture.bind(openGLState);
                            }
                            Vector3d position = PhysicsUtils.odeVecToJomlVec(capsuleView.getPosition());
                            //calculate camera-modified vector3d
                            Vector3d cameraModifiedPosition = new Vector3d(position).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(cameraModifiedPosition);
                            //since you're directly accessing the quat from the body, need to adjust it to be in the correct orientation
                            modelTransformMatrix.rotate(PhysicsUtils.odeQuatToJomlQuat(capsuleView.getQuaternion()).mul(new Quaterniond(0.707,0,0,0.707)));
                            //the ode4j capsule's end caps are always at least radius length, the length only controls the distance between the two caps.
                            //unfortunately that won't be easy to replicate with rendering tech currently; instead, run logic below
                            double radius = capsuleView.getRadius();
                            double length = capsuleView.getLength();
                            if(length < radius) length = radius;
                            modelTransformMatrix.scale(radius,length,radius);
                            hitboxModel.setModelMatrix(modelTransformMatrix);
                            hitboxModel.draw(renderPipelineState,openGLState);
                        }
                    }
                }
            }
        }

        if(Globals.userSettings.graphicsDebugDrawPhysicsObjects()){
            Model physicsGraphicsModel;
            for(Collidable collidable : Globals.clientSceneWrapper.getCollisionEngine().getCollidables()){
                Entity physicsEntity = collidable.getParent();
                if((boolean)physicsEntity.getData(EntityDataStrings.DATA_STRING_DRAW) && physicsEntity.getData(EntityDataStrings.PHYSICS_MODEL_TEMPLATE) != null){
                    CollidableTemplate template = (CollidableTemplate)physicsEntity.getData(EntityDataStrings.PHYSICS_MODEL_TEMPLATE);
                    switch(template.getType()){
                        case "CYLINDER": {
                            if((physicsGraphicsModel = Globals.assetManager.fetchModel(AssetDataStrings.UNITCYLINDER)) != null){
                                //set color based on collision status, type, etc
                                Texture texture = Globals.assetManager.fetchTexture("Textures/transparent_blue.png");
                                if(texture != null){
                                    texture.bind(openGLState);
                                }
                                Vector3d position = EntityUtils.getPosition(physicsEntity);
                                //calculate camera-modified vector3d
                                Vector3d cameraModifiedPosition = new Vector3d(position).add(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                                modelTransformMatrix.identity();
                                modelTransformMatrix.translate(cameraModifiedPosition);
                                modelTransformMatrix.rotate(EntityUtils.getRotation(physicsEntity));
                                modelTransformMatrix.scale(template.getDimension1(),template.getDimension2() * 0.5,template.getDimension3());
                                physicsGraphicsModel.setModelMatrix(modelTransformMatrix);
                                physicsGraphicsModel.draw(renderPipelineState,openGLState);
                            }
                        } break;
                        case "CUBE": {
                            if((physicsGraphicsModel = Globals.assetManager.fetchModel(AssetDataStrings.UNITCUBE)) != null){
                                //set color based on collision status, type, etc
                                Texture texture = Globals.assetManager.fetchTexture("Textures/transparent_blue.png");
                                if(texture != null){
                                    texture.bind(openGLState);
                                }
                                Vector3d position = EntityUtils.getPosition(physicsEntity);
                                Quaterniond rotation = EntityUtils.getRotation(physicsEntity);
                                //calculate camera-modified vector3d
                                Vector3d cameraModifiedPosition = new Vector3d(position).add(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                                modelTransformMatrix.identity();
                                modelTransformMatrix.translate(cameraModifiedPosition);
                                modelTransformMatrix.rotate(rotation);
                                modelTransformMatrix.scale(template.getDimension1(),template.getDimension2(),template.getDimension3());
                                physicsGraphicsModel.setModelMatrix(modelTransformMatrix);
                                physicsGraphicsModel.draw(renderPipelineState,openGLState);
                            }
                        } break;
                    }
                }
            }
        }

        //update pipeline state to use mats again
        renderPipelineState.setUseMaterial(true);
        
        if(Globals.userSettings.graphicsDebugDrawNavmesh()){
            Model shapeGraphicsModel;
            for(NavMesh mesh : Globals.navMeshManager.getMeshes()){
                for(NavShape shape : mesh.getNodes()){
                    if(shape instanceof NavCube){
                        if((shapeGraphicsModel = Globals.assetManager.fetchModel(AssetDataStrings.UNITCUBE)) != null){
                            NavCube cube = (NavCube)shape;
                            Vector3d position = new Vector3d(cube.getMinPoint()).add(cube.getMaxPoint()).mul(0.5);
                            Vector3d scale = new Vector3d((float)(cube.getMaxPoint().x-cube.getMinPoint().x)/2,(float)(cube.getMaxPoint().y-cube.getMinPoint().y)/2,(float)(cube.getMaxPoint().z-cube.getMinPoint().z)/2);
                            Quaternionf rotation = new Quaternionf();
                            //calculate camera-modified vector3d
                            Vector3d cameraModifiedPosition = new Vector3d(position).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(cameraModifiedPosition);
                            modelTransformMatrix.rotate(rotation);
    //                            modelTransformMatrix.translate(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()); //center sphere
                            modelTransformMatrix.scale(new Vector3d(scale));
                            shapeGraphicsModel.setModelMatrix(modelTransformMatrix);
                            shapeGraphicsModel.draw(renderPipelineState,openGLState);
                        }
                    }
                }
            }
        }

        debugBonesPipeline.render(openGLState, renderPipelineState);

        Globals.profiler.endCpuSample();
    }

    /**
     * Gets the color texture to use to draw a hitbox
     * @param shapeStatus The hitbox status
     * @param data The hitbox data
     * @return The texture path to use
     */
    private String getHitboxColor(HitboxState shapeStatus, HitboxData data){
        switch(data.getType()){
            case HitboxData.HITBOX_TYPE_BLOCK_CONNECTED: {
                if(shapeStatus.getHadCollision()){
                    return "Textures/color/transparent_yellow.png";
                }
                if(shapeStatus.isActive()){
                    return "Textures/transparent_blue.png";
                }
                return "Textures/transparent_grey.png";
            }
            case HitboxData.HITBOX_TYPE_HIT:
            case HitboxData.HITBOX_TYPE_HIT_CONNECTED: {
                if(shapeStatus.getHadCollision()){
                    return "Textures/color/transparent_yellow.png";
                }
                if(shapeStatus.isActive()){
                    if(shapeStatus.isBlockOverride()){
                        return "Textures/transparent_blue.png";
                    }
                    return "Textures/transparent_red.png";
                }
                return "Textures/transparent_grey.png";
            }
            case HitboxData.HITBOX_TYPE_HURT:
            case HitboxData.HITBOX_TYPE_HURT_CONNECTED: {
                if(shapeStatus.getHadCollision()){
                    return "Textures/color/transparent_yellow.png";
                }
                return "Textures/transparent_grey.png";
            }
        }
        return "Textures/transparent_grey.png";
    }

    /**
     * Gets the bone debugging pipeline
     * @return The bone debugging pipeline
     */
    public DebugBonesPipeline getDebugBonesPipeline(){
        return this.debugBonesPipeline;
    }
    
}
