package de.frajul.endlessroll.rendering.renderer;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;

import java.util.ArrayList;
import java.util.List;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import de.frajul.endlessroll.entities.Entity;
import de.frajul.endlessroll.entities.Obstacle;
import de.frajul.endlessroll.entities.textures.TexturePack;
import de.frajul.endlessroll.entities.tileLists.Tile;
import de.frajul.endlessroll.entities.tileLists.TileList;
import de.frajul.endlessroll.main.GameActivity;
import de.frajul.endlessroll.main.GameLog;
import de.frajul.endlessroll.main.game.Camera;
import de.frajul.endlessroll.main.game.Scene;
import de.frajul.endlessroll.main.game.Timer;
import de.frajul.endlessroll.rendering.MatrixCreator;
import de.frajul.endlessroll.rendering.Quad;
import de.frajul.endlessroll.rendering.Rendering;
import de.frajul.endlessroll.rendering.shader.EntityShader;
import de.frajul.endlessroll.rendering.shader.ObstacleShader;
import de.frajul.endlessroll.rendering.shader.SimpleShader;
import de.frajul.endlessroll.rendering.shader.TerrainShader;

/**
 * Created by Julian on 22.11.2015.
 */
public class GameRenderer implements GLSurfaceView.Renderer {

    private List<Rendering> renderTargets = new ArrayList<>();
    private Rendering currentRendering;
    private MatrixCreator matrixCreator;

    private GameActivity activity;
    private Quad quad;

    private ParticleRenderer particleRenderer;

    private EntityShader entityShader;
    private ObstacleShader obstacleShader;
    private TerrainShader terrainShader;
    private SimpleShader simpleShader;

    private TexturePack texturePack;
    private Timer timer;

    public GameRenderer(GameActivity activity) {
        this.activity = activity;
        matrixCreator = new MatrixCreator();
        quad = new Quad();
        particleRenderer = new ParticleRenderer(activity, quad, matrixCreator);
    }

    public void addRendering(Rendering rendering) {
        renderTargets.add(rendering);
    }

    public synchronized void setCurrentRendering(Rendering currentRendering) {
        this.currentRendering = currentRendering;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GameLog.d("onSurfaceCreated");
        GLES20.glClearColor(1, 1, 1, 1.0f);
        gl.glEnable(GL10.GL_BLEND);
        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

        try {
            particleRenderer.createContext();
            entityShader = new EntityShader(activity);
            obstacleShader = new ObstacleShader(activity);
            terrainShader = new TerrainShader(activity);
            simpleShader = new SimpleShader(activity);
            texturePack = new TexturePack(activity);
            timer = new Timer();
        } catch (Exception e) {
            for (Rendering rendering : renderTargets)
                rendering.onException(e);
        }
        for (Rendering rendering : renderTargets) {
            rendering.initiate(texturePack, timer);
        }
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GameLog.d("onSurfaceChanged: width=" + width + ", height=" + height);
        particleRenderer.createFbo(width, height);
        GLES20.glViewport(0, 0, width, height);
        matrixCreator.setMVPMSize(width, height);
        for (Rendering rendering : renderTargets)
            rendering.setScreenSize(width, height);
        activity.onSurfaceChanged();
    }

    @Override
    public synchronized void onDrawFrame(GL10 gl) {
        timer.update();
        if (currentRendering != null) {
            currentRendering.update();
            Scene scene = currentRendering.getScene();

            particleRenderer.renderParticlesToFbo(gl, scene);

            GLES20.glClearColor(1, 1, 1, 1.0f);
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

            renderBackground(gl, scene);
            renderTileList(gl, scene.getTerrain(), scene);
            renderTileList(gl, scene.getCeiling(), scene);
            renderEntityList(gl, scene.getCollectables(), scene.getCamera());
            renderObstacles(gl, scene);
            renderEntityList(gl, scene.getTools(), scene.getCamera());

            renderEntityList(gl, scene.getUncategorizedEntities(), scene.getCamera());

            renderFbo(gl);
        }
    }

    private void renderBackground(GL10 gl, Scene scene) {
        entityShader.start();
        entityShader.loadMVPMatrix(matrixCreator, scene.getCamera());
        synchronized (scene.getBackground()) {
            for (Entity backgroundPart : scene.getBackground())
                renderEntity(gl, backgroundPart);
        }
        entityShader.stop();
    }

    private void renderObstacles(GL10 gl, Scene scene) {
        List<Obstacle> obstacles = scene.getObstacles();
        obstacleShader.start();
        obstacleShader.loadMVPMatrix(matrixCreator, scene.getCamera());
        synchronized (obstacles) {
            for (Obstacle obstacle : obstacles)
                renderObstacle(gl, obstacle);
        }
        obstacleShader.stop();
    }


    private void renderObstacle(GL10 gl, Obstacle obstacle) {
        gl.glActiveTexture(GL10.GL_TEXTURE0);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, obstacle.getTexture().getId());
        obstacleShader.loadTransformationMatrix(matrixCreator, obstacle);
        obstacleShader.loadAlpha(obstacle.getAlpha());
        obstacleShader.loadTextureAtlasInfos(obstacle.getTexture());
        obstacleShader.loadGridSize(obstacle.getGridSize());
        obstacleShader.loadDeadly(obstacle.isDeadly());
        obstacleShader.loadFloating(obstacle.isFloating());
        quad.draw();
    }

    private void renderEntityList(GL10 gl, List<? extends Entity> list, Camera camera) {
        entityShader.start();
        entityShader.loadMVPMatrix(matrixCreator, camera);
        synchronized (list) {
            for (Entity entity : list) {
                renderEntity(gl, entity);
            }
        }
        entityShader.stop();
    }

    private void renderEntity(GL10 gl, Entity entity) {
        if (!entity.isVisible())
            return;

        gl.glActiveTexture(GL10.GL_TEXTURE0);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, entity.getTexture().getId());
        entityShader.loadTransformationMatrix(matrixCreator, entity);
        entityShader.loadAlpha(entity.getAlpha());
        entityShader.loadTextureAtlasInfos(entity.getTexture(), entity.getTextureAtlasIndex());
        float texScaleX = entity.getMaxTexSize().x == 0 ? 1 : entity.getWidth() / entity
                .getMaxTexSize().x;
        float texScaleY = entity.getMaxTexSize().y == 0 ? 1 : entity.getHeight() / entity
                .getMaxTexSize().y;
        entityShader.loadTexCoordScaling(texScaleX, texScaleY);
        quad.draw();
    }

    private void renderTileList(GL10 gl, TileList tileList, Scene scene) {
        terrainShader.start();
        terrainShader.loadMVPMatrix(matrixCreator, scene.getCamera());
        synchronized (tileList) {
            for (Tile tile : tileList) {
                gl.glActiveTexture(GL10.GL_TEXTURE0);
                gl.glBindTexture(GL10.GL_TEXTURE_2D, tile.getTexture().getId());
                terrainShader.loadTransformationMatrix(matrixCreator, tile);
                quad.draw();
            }
        }
        terrainShader.stop();
    }

    private void renderFbo(GL10 gl) {
        simpleShader.start();
        gl.glActiveTexture(GL10.GL_TEXTURE0);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, particleRenderer.getFboTexture());
        quad.draw();
        simpleShader.stop();
    }


}
