package xaero.rotatenjump.game;

import android.opengl.GLES20;
import android.util.Log;

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

import xaero.rotatenjump.GameView;
import xaero.rotatenjump.game.graphics.Graphics;
import xaero.rotatenjump.game.graphics.model.Square;
import xaero.rotatenjump.game.object.Coin;
import xaero.rotatenjump.game.object.Entity;
import xaero.rotatenjump.game.object.Platform;
import xaero.rotatenjump.game.object.Player;
import xaero.rotatenjump.game.touch.TouchHandler;
import xaero.rotatenjump.misc.Transformations;

/**
 * Created by xaero on 06/07/2017.
 * Handles the game process
 */

public class Game {

    private TouchHandler touchHandler;
    private Level level;

    /*
     * Game state
     */
    public boolean gameStarted;
    private int victoryState = 0;
    private int victoryStateRender = 0;//victory animation should be delayed by ~1 tick like everything else
    private float victoryX;
    private float victoryY;
    private float victoryGravityAngle;
    public boolean forceEnd;

    /*
     * Scoring
     */

    private static final int VICTORY_FLIGHT_LENGTH = 60;
    private static final int VICTORY_COUNT_LENGTH = 20;
    float scoreboardX;
    float scoreboardY;
    private float scoreboardCountingAnim;
    private float scoreboardCountingAnimRender;
    private float lastScoreboardCountingAnimRender;
    int coinTimerStartValue;
    int coinTimer;
    public int coinTier;
    public int coinTierRender;
    public int[] coinCounters = new int[3];//gold, silver, copper
    private float coinCountersAnim = 0;

    /*
     * Gravity
     */

    private static final float GRAVITY = 0.25f;
    private float gravityAngle = 0;//0 is directly down, positive direction is counter-clockwise
    private float gravityAngleRender;
    private float lastGravityAngleRender;
    private float directedGravityX;
    private float directedGravityY;

    /*
     * Debug
     */

    private int fps;
    private long lastFPSDebug;
    private int debugFPS;
    public float updateTimeToRender;
    private Square debugCenter = new Square();

    /*
     * Entities
     */

    private List<Entity> platforms;
    private List<Entity> coins;
    private List<Entity> others;
    private Player player;

    /*
     * Camera
     */

    private float cameraX;
    private float cameraY;
    private float lastCameraXRender;
    private float lastCameraYRender;
    private float cameraXRender;
    private float cameraYRender;
    private float cameraScale;
    private float cameraAngleX = 5;
    private float cameraAngleXRender = cameraAngleX;
    private float lastCameraAngleXRender = cameraAngleX;

    protected float[] updateVector2 = new float[2];//reusable object for all sorts of things, only for usage in update thread
    protected float[] renderVector2 = new float[2];//reusable object for all sorts of things, only for usage in render thread
    private float[] getReusableVector2(boolean isUpdaterThread){
        if(isUpdaterThread)
            return updateVector2;
        else
            return renderVector2;
    }
    private StringBuilder stringBuilder;

    public Game(TouchHandler touchHandler, Level level){
        this.touchHandler = touchHandler;
        this.level = level;
        player = new Player();
        gameStarted = false;
        platforms = new ArrayList<>();
        coins = new ArrayList<>();
        others = new ArrayList<>();
        counter = 0;
        setGravityAngle(0);//updates the directed gravity
        level.constructLevel(this);
    }

    public List<Entity> getPlatforms() {
        return platforms;
    }

    public List<Entity> getCoins() {
        return coins;
    }

    public List<Entity> getOthers() {
        return others;
    }

    public Player getPlayer() {
        return player;
    }

    private int counter = 0;

    public void preUpdate(){
        for(int i = 0; i < others.size(); i++)
            others.get(i).preUpdate();
        for(int i = 0; i < platforms.size(); i++)
            platforms.get(i).preUpdate();
        for(int i = 0; i < coins.size(); i++)
            coins.get(i).preUpdate();
        player.preUpdate();
    }

    public void gameUpdate(){
        if(gameStarted){
            if(level.hasWon(this)) {
                Log.d("game", "VICTORY");
                victoryState = 1;
                victoryX = player.getPosX(0);
                victoryY = player.getPosY(0);
                victoryGravityAngle = gravityAngle;
                gameStarted = false;
            } else {

                if(player.getPosX(1) * directedGravityX / GRAVITY + player.getPosY(1) * directedGravityY / GRAVITY > level.deadlyDepth) {//projection on the gravity axis is longer than deadlyDepth
                    Log.d("game", "LOSS");
                    GameView.changeScreen(GameView.LEVEL_SELECT);
                    gameStarted = false;
                }
            }
        }
        if(victoryState > 0){
            if(victoryState <= VICTORY_FLIGHT_LENGTH) {
                float anim = Transformations.getSinAnimation(victoryState, VICTORY_FLIGHT_LENGTH);
                cameraX = victoryX + (scoreboardX - victoryX) * anim;
                cameraY = victoryY + (scoreboardY - victoryY) * anim;
                cameraAngleX = 5 * (1 - anim);
                gravityAngle = victoryGravityAngle - (victoryGravityAngle % 360) * anim;
                //Log.d("test", "" + gravityAngle);
            }
            if(victoryState <= VICTORY_FLIGHT_LENGTH + VICTORY_COUNT_LENGTH){
                if(victoryState > VICTORY_FLIGHT_LENGTH)
                    scoreboardCountingAnim = Transformations.getSinAnimation(victoryState - VICTORY_FLIGHT_LENGTH, VICTORY_COUNT_LENGTH);
                victoryState++;
            }
        }

        if (counter == 0)
            player.attach(this, (Platform) platforms.get(0), 0);
        for (int i = 0; i < others.size(); i++)
            others.get(i).update(this);
        for (int i = 0; i < platforms.size(); i++)
            platforms.get(i).update(this);
        for (int i = 0; i < coins.size(); i++)
            coins.get(i).update(this);

        player.update(this);
        if(coinTimer <= 0) {
            if(coinTier > 0) {
                coinTier--;
                if(coinTier > 0)//last tier (tier 0) stays forever
                    coinTimer = coinTimerStartValue = coinTimerStartValue * 4 / 3;
            }
        } else
            coinTimer--;

        if(forceEnd){
            GameView.changeScreen(GameView.LEVEL_SELECT);
            gameStarted = false;
            forceEnd = false;
        }
            //Log.d("game", "update");
        counter++;
    }

    public void refresh(){//on render before rendering the new tick, needed for running the game at 40 tps with 60 fps (partial ticks)
        for(int i = 0; i < others.size(); i++)
            others.get(i).refresh();
        for(int i = 0; i < platforms.size(); i++)
            platforms.get(i).refresh();
        for(int i = 0; i < coins.size(); i++)
            coins.get(i).refresh();
        player.refresh();
        lastGravityAngleRender = gravityAngleRender;
        gravityAngleRender = gravityAngle;
        victoryStateRender = victoryState;
        lastCameraXRender = cameraXRender;
        cameraXRender = cameraX;
        lastCameraYRender = cameraYRender;
        cameraYRender = cameraY;
        lastCameraAngleXRender = cameraAngleXRender;
        cameraAngleXRender = cameraAngleX;
        coinTierRender = coinTier;
        lastScoreboardCountingAnimRender = scoreboardCountingAnimRender;
        scoreboardCountingAnimRender = scoreboardCountingAnim;

        //cameraPartialTickMultiplier = cameraNextPartialTickMultiplier;
        //cameraNextPartialTickMultiplier = 1f;
    }

    //private float test = 0;
    public void render(float partialTicks){
        cameraScale = GameView.DEBUG ? 3f : 7f;

        float sbcAnimRender = lastScoreboardCountingAnimRender + (scoreboardCountingAnimRender - lastScoreboardCountingAnimRender) * partialTicks;
        if(victoryStateRender > VICTORY_FLIGHT_LENGTH)
            cameraScale *= 1 + 0.5f * sbcAnimRender;

        touchHandler.handleIngameEvents();

        player.calculateRenderValues(partialTicks);
        float cameraPartialTicks = partialTicks;
        if(!GameView.DEBUG && victoryStateRender == 0) {
            lastCameraXRender = player.getLastPosXRender();
            lastCameraYRender = player.getLastPosYRender();
            cameraXRender = player.getToPosX();
            cameraYRender = player.getToPosY();
            cameraPartialTicks = player.getUsedPartialTicks();
        }
        float renderedCameraX = (lastCameraXRender + (cameraXRender - lastCameraXRender) * cameraPartialTicks);
        float renderedCameraY = (lastCameraYRender + (cameraYRender - lastCameraYRender) * cameraPartialTicks);
        float renderedCameraAngle = lastGravityAngleRender + (gravityAngleRender - lastGravityAngleRender) * partialTicks;
        Graphics.updateView(renderedCameraX, renderedCameraY, cameraScale, cameraScale, cameraScale, lastCameraAngleXRender + (cameraAngleXRender - lastCameraAngleXRender) * partialTicks, -renderedCameraAngle);
        Graphics.sendLightVector();

        //Graphics.fontRenderer.drawGUIString("MIN", minX, minY, 0.5f);
        //Graphics.fontRenderer.drawGUIString("MAX", maxX, maxY, 0.5f);

        /*
         * Render only the visible entities
         */
        renderEntityList(others, renderedCameraX, renderedCameraY, renderedCameraAngle, partialTicks);
        renderEntityList(coins, renderedCameraX, renderedCameraY, renderedCameraAngle, partialTicks);
        player.render(this, partialTicks);
        renderEntityList(platforms, renderedCameraX, renderedCameraY, renderedCameraAngle, partialTicks);
        /*Graphics.color = Graphics.WHITE;
        Graphics.modelsLoadedFromFiles[4].draw(0, 0, 0, 180, 0, 10, 10, 10);*/
        //Graphics.color = Graphics.WHITE;
        //Graphics.testModel.draw(renderedCameraX, renderedCameraY, 0, test, 20, 20, 20);
        //test += 1;

        if(GameView.DEBUG) {
            Graphics.color = Graphics.BLACK;
            debugCenter.draw(renderedCameraX, renderedCameraY, 0, 0, 0, 3, 3, 1);
        }
        if(victoryStateRender > 0 && GameView.instance.getCurrentScreen() == null)
            renderScoreboard(sbcAnimRender);
        Graphics.setupGuiRendering();
        if(GameView.instance.getCurrentScreen() == null) {
            /*
             * Ingame GUI
             */
            renderCoinCounters(sbcAnimRender);
        }
        fps++;
        if(System.currentTimeMillis() - lastFPSDebug >= 1000) {
            debugFPS = fps;
            fps = 0;
            lastFPSDebug = System.currentTimeMillis();
        }
        Graphics.color = Graphics.BLACK;
        //Graphics.fontRenderer.drawGUIString("FPS: " + debugFPS, 0, 40, 1);
        //Graphics.fontRenderer.drawGUIString("UT: " + updateTimeToRender, 0, 100, 1);
        if(GameView.DEBUG) {
            //paint.setTextAlign(Paint.Align.LEFT);
            Graphics.fontRenderer.drawGUIString("X: " + cameraX, 0, 100, 1);
            Graphics.fontRenderer.drawGUIString("Y: " + cameraY, 0, 160, 1);
            //SimpleGraphics.scaledText(canvas, paint, "X: " + cameraX, 0, 50);
            //SimpleGraphics.scaledText(canvas, paint, "Y: " + cameraY, 0, 100);
        }
        //Graphics.fontRenderer.drawGUIString("" + (player.getPosX(1) * directedGravityX / GRAVITY + player.getPosY(1) * directedGravityY / GRAVITY), 0, 160, 1);
        //Graphics.fontRenderer.drawGUIString("X: " + directedGravityX, 0, 220, 1);
        //Graphics.fontRenderer.drawGUIString("Y: " + directedGravityY, 0, 280, 1);
        //Graphics.fontRenderer.drawGUIString("A: " + gravityAngle, 0, 340, 1);
        /*if(System.currentTimeMillis() - GameView.performanceTestStart >= GameView.performanceTestLength) {//TODO TEST
            Graphics.fontRenderer.drawGUIString("Frames: " + GameView.performanceTestCounter, 0, 160, 1);
            Graphics.fontRenderer.drawGUIString("Frametime: " + (GameView.performanceTestAccumulator / (float)GameView.performanceTestCounter) + " ms", 0, 240, 1);
            Graphics.fontRenderer.drawGUIString("Updates: " + GameView.performanceTestTickCounter, 0, 320, 1);
            Graphics.fontRenderer.drawGUIString("Update time: " + (GameView.performanceTestTickAccumulator / (float)GameView.performanceTestTickCounter) + " ms", 0, 400, 1);
        }*/
        //Log.d("render", "update");
    }

    private List<Entity> entitiesBuffer = new ArrayList<>();
    private void renderEntityList(List entitiesToRender, float renderedCameraX, float renderedCameraY, float cameraAngle, float partialTicks){
        visibleOnlyFilter(false, entitiesToRender, renderedCameraX, renderedCameraY, cameraScale, cameraAngle,0, entitiesBuffer);
        for(int i = 0; i < entitiesBuffer.size(); i++) {
            Entity e = (Entity)entitiesBuffer.get(i);
            e.render(this, partialTicks);
        }
    }

    private void renderScoreboard(float sbcAnimRender){
        Graphics.color = Graphics.WHITE;
        Graphics.modelsLoadedFromFiles[6].draw(scoreboardX, scoreboardY - 5, 0, 0, 0, 11.5f, 11.5f, 11.5f);
        Graphics.color = Coin.GOLD;
        Graphics.modelsLoadedFromFiles[2].draw(scoreboardX - 20, scoreboardY + 5, 1.68f, 0, 90, 0, 5, 5, 5);
        Graphics.color = Coin.SILVER;
        Graphics.modelsLoadedFromFiles[2].draw(scoreboardX - 20, scoreboardY - 8, 1.68f, 0, 90, 0, 5, 5, 5);
        Graphics.color = Coin.COPPER;
        Graphics.modelsLoadedFromFiles[2].draw(scoreboardX - 20, scoreboardY - 21, 1.68f, 0, 90, 0, 5, 5, 5);
        Graphics.color = Graphics.BLACK;
        GLES20.glDisable(GLES20.GL_CULL_FACE);
        Graphics.fontRenderer.drawCenteredString("SCORE", scoreboardX, scoreboardY + 20, 0.81f, 0.2f, -1);
        int total = 0;
        for(int i = 0; i < 3; i++) {
            int amount = (int) (coinCounters[i] * sbcAnimRender);
            total += (i + 1) * amount;
            String amountString = Transformations.intToString(amount, Transformations.RENDER_THREAD);
            Graphics.fontRenderer.drawCenteredString(amountString, scoreboardX, scoreboardY - 21 + i * 13, 0.81f, 0.1f, -1);
        }
        String totalString = Transformations.intToString("Total: ", total, Transformations.RENDER_THREAD);
        Graphics.fontRenderer.drawCenteredString(totalString, scoreboardX, scoreboardY - 35, 0.81f, 0.1f, -1);
        GLES20.glEnable(GLES20.GL_CULL_FACE);
    }

    //private final static float[] counterBgColour = {0.494f, 0.561f, 0.557f, 1f};
    private void renderCoinCounters(float sbcAnimRender){
        Graphics.enableLighting();
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        //Graphics.color = counterBgColour;
        //Graphics.SQUARE.draw(Graphics.getWidth() / 2,80, -700, 0, 0, 0, Graphics.getWidth(), 160, 0);
        for(int i = 0; i < 3; i++) {
            Graphics.color = Coin.getCoinColour(i);
            Graphics.modelsLoadedFromFiles[2].draw(Graphics.getWidth() - 80 - 250 * i,80, -500, 0, (i+1) * coinCountersAnim, 0, 40, -40, 40);
        }
        Graphics.disableLighting();
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
        for(int i = 0; i < 3; i++) {
            String amountString = Transformations.intToString((int)(Math.ceil(coinCounters[i] * (1 - sbcAnimRender))), Transformations.RENDER_THREAD);
            Graphics.color = Graphics.WHITE;
            Graphics.fontRenderer.drawStringRightAlign(amountString, Graphics.getWidth() - 160 - 250 * i + 3, 83, 0, 0.8f, 1);
            //Graphics.color = Coin.getCoinColour(i);
            Graphics.color = Graphics.DKGRAY;
            Graphics.fontRenderer.drawStringRightAlign(amountString, Graphics.getWidth() - 160 - 250 * i, 80, 0, 0.8f, 1);
        }
        coinCountersAnim = (coinCountersAnim + 0.5f) % 360;
    }

    public void visibleOnlyFilter(boolean isUpdaterThread, List<Entity> entities, float cameraPosX, float cameraPosY, float viewScale, float cameraAngle, float partialCollision, List<Entity> dest){
        /*
         * Calculating visibility bounds taking camera rotation in count
         */
        float screenTopLeftX = -Graphics.getWidth() / 2f / viewScale;
        float screenTopLeftY = Graphics.getHeight() / 2f / viewScale;
        float screenTopRightX = -screenTopLeftX;
        float screenTopRightY = screenTopLeftY;
        float[] reusableVector2 = getReusableVector2(isUpdaterThread);
        if(gravityAngle != 0){
            Transformations.rotate(screenTopLeftX, screenTopLeftY, cameraAngle, reusableVector2);
            screenTopLeftX = reusableVector2[0];
            screenTopLeftY = reusableVector2[1];
            Transformations.rotate(screenTopRightX, screenTopRightY, cameraAngle, reusableVector2);
            screenTopRightX = reusableVector2[0];
            screenTopRightY = reusableVector2[1];
        }
        float screenMaxX = Math.max(Math.abs(screenTopLeftX), Math.abs(screenTopRightX));
        float screenMinX = -screenMaxX;
        float screenMaxY = Math.max(Math.abs(screenTopLeftY), Math.abs(screenTopRightY));
        float screenMinY = -screenMaxY;
        float minX = cameraPosX + screenMinX;//these can give different results depending on the gravity/camera rotation!!!
        float maxX = cameraPosX + screenMaxX;
        float minY = cameraPosY + screenMinY;
        float maxY = cameraPosY + screenMaxY;
        /*
         * Filtering out the entities that are not within the bounds
         */
        dest.clear();
        for(int i = 0; i < entities.size(); i++) {
            Entity e = (Entity)entities.get(i);
            if(e.getPosX(partialCollision) + e.getBoxWidth() / 2 >= minX && e.getPosX(partialCollision) - e.getBoxWidth() / 2 < maxX && e.getPosY(partialCollision) + e.getBoxHeight() / 2 >= minY && e.getPosY(partialCollision) - e.getBoxHeight() / 2 < maxY)
                dest.add(e);
        }
    }

    public void onTouch(float x, float y) {
        if(victoryState > 0)
            return;
        if(!GameView.DEBUG)
            gameStarted = true;
        player.shouldDeattach = true;
        if(GameView.DEBUG){
            float offX = x - Graphics.getWidth() / 2;
            float offY = y - Graphics.getHeight() / 2;

            cameraX += offX / cameraScale;
            cameraY -= offY / cameraScale;
        }
    }

    public void onUnload(){
        player = null;
        platforms.clear();
        coins.clear();
        others.clear();
        platforms = null;
        coins = null;
        others = null;
        level = null;
    }

    public float getGravityAngle(){
        return gravityAngle;
    }

    public void setGravityAngle(float a){
        gravityAngle = a;
        Transformations.rotate(0, -GRAVITY, gravityAngle, updateVector2);
        directedGravityX = updateVector2[0];
        directedGravityY = updateVector2[1];
    }

    public float getDirectedGravityX(){
        return directedGravityX;
    }

    public float getDirectedGravityY(){
        return directedGravityY;
    }
}
