package xaero.rotatenjump;

import android.content.Context;
import android.content.res.AssetManager;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.MotionEvent;

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

import xaero.rotatenjump.game.Game;
import xaero.rotatenjump.game.Level;
import xaero.rotatenjump.game.graphics.Graphics;
import xaero.rotatenjump.game.touch.TouchHandler;
import xaero.rotatenjump.gui.Gui;
import xaero.rotatenjump.gui.LevelSelect;

/**
 * Created by xaero on 06/07/2017.
 */

public class GameView extends GLSurfaceView implements GLSurfaceView.Renderer {
    //TODO TEST
    /*public static long performanceTestStart = 0;
    public static long performanceTestCounter = 0;
    public static long performanceTestAccumulator = 0;
    public static long performanceTestTickCounter = 0;
    public static long performanceTestTickAccumulator = 0;
    public static long performanceTestLength = 300000;*/

    public static GameView instance;
    public static final boolean DEBUG = false;
    private static final int TICK_LENGTH = 25;
    public static final Gui LEVEL_SELECT = new LevelSelect();
    private Game game;
    private Gui currentScreen;
    private static Gui newScreen;

    private boolean running = true;

    private static long lastDebug = 0;
    private static int tps = 0;

    public static final Object sync = new Object();
    private boolean refreshNeeded = false;
    private Level level;
    private static Level newLevel;

    public static void newLevel(Level level){
        newLevel = level;
    }

    private float touchX;
    private float touchY;
    private TouchHandler touchHandler;

    private Context context;
    private AssetManager assetManager;

    //public static Model textureTest;
    private long beginningOfTime;
    private int tickCount = 0;

    private float getActualTickCount(){
        return (System.currentTimeMillis() - beginningOfTime) / (float)TICK_LENGTH;
    }

    public GameView(Context context, AssetManager assetManager){
        super(context);
        instance = this;
        this.context = context;
        this.assetManager = assetManager;

        /*
         * OpenGL
         */
        //OpenGL context
        setEGLContextClientVersion(2);
        setRenderer(this/*Renderer implementaion is also this class*/);
        //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        /*
         * Other stuff
         */
        touchX = -1;
        touchY = -1;
        newLevel = Level.LEVEL1;
        touchHandler = new TouchHandler(this);
        newScreen = LEVEL_SELECT;
        invalidate();
        (updaterThread = new Thread(updater)).start();
        //performanceTestStart = System.currentTimeMillis();//TODO TEST
    }

    public static void changeScreen(Gui s){
        newScreen = s;
    }

    private Thread updaterThread;

    Runnable updater = new Runnable(){
        @Override
        public void run() {
            int ticksNeeded;
            while(running) {
                float actualCount = getActualTickCount() + 1;//+ 1 because the updater needs to be 1 step ahead
                if (!refreshNeeded){
                    ticksNeeded = (int)actualCount - tickCount;
                    if (ticksNeeded > 0) {
                        //Log.d("game tick", "ticks: " + ticksNeeded);
                        if (ticksNeeded > 30)
                            Log.d("game tick", "LAG, skipping " + (ticksNeeded - 30) + " ticks");
                        if (newLevel != null) {
                            synchronized (sync) {
                                Game oldGame = game;
                                level = newLevel;
                                game = new Game(touchHandler, level);
                                if(oldGame != null)
                                    oldGame.onUnload();
                                newLevel = null;
                                beginningOfTime = System.currentTimeMillis();
                                tickCount = 0;
                                continue; //restart the iteration
                            }
                        }
                        long before = System.currentTimeMillis();
                        game.preUpdate();
                        for (int i = 0; i < ticksNeeded && i < 30; i++) {
                            /*long performanceTestBefore = System.currentTimeMillis();//TODO TEST
                            if(before - performanceTestStart < performanceTestLength)//TODO TEST
                                performanceTestTickCounter++;//TODO TEST*/
                            game.gameUpdate();
                            tps++;
                            /*if(before - performanceTestStart < performanceTestLength)//TODO TEST
                                performanceTestTickAccumulator += System.currentTimeMillis() - performanceTestBefore;//TODO TEST*/
                        }
                        game.updateTimeToRender = (float) ((System.currentTimeMillis() - before) / 1000.0);
                        // Log.d("test", ((System.nanoTime() - actualLastGameUpdate) / 1000000) + "");

                        tickCount = (int) actualCount;
                        refreshNeeded = true;
                        //Log.d("updated", ""+tickCount);
                        if (System.currentTimeMillis() - lastDebug > 1000) {
                            //Log.d("game tick", "tps: " + tps);
                            tps = 0;
                            lastDebug = System.currentTimeMillis();
                        }
                    }
                   // System.gc();
                }
                if(refreshNeeded)//can become false between the lines
                //int sleepTime = (int) (TICK_LENGTH * (1.0f - (actualCount - tickCount)));
               // if(sleepTime >= 10) {
                    try {
                        Thread.sleep(5000);//sleepTime);
                   } catch (InterruptedException e) {}
                //}
            }
        }
    };

    public void guiTouch(float x, float y, int type){
        if(type == MotionEvent.ACTION_DOWN) {
            touchX = x;
            touchY = y;
            if(currentScreen != null)
                currentScreen.onTouch(x, y);
        } else if(type == MotionEvent.ACTION_UP) {
            touchX = -1;
            touchY = -1;
            if(currentScreen != null)
                currentScreen.onRelease(x, y);
        } else if(type == MotionEvent.ACTION_MOVE){
            touchX = x;
            touchY = y;
            if(currentScreen != null)
                currentScreen.onMove(x, y);
        }
    }

    public void ingameTouch(float x, float y, int type){
        if(type == MotionEvent.ACTION_DOWN) {
            game.onTouch(x, y);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_MOVE) {
            if(currentScreen != null || newScreen != null)
                touchHandler.addGuiEvent(event.getX() * Graphics.screenScale, event.getY() * Graphics.screenScale, event.getAction());
            else
                touchHandler.addIngameEvent(event.getX() * Graphics.screenScale, event.getY() * Graphics.screenScale, event.getAction());
            return true;
        }
        return super.onTouchEvent(event);
    }

    public void stop(){
        running = false;
    }

    public boolean onBackPressed() {
        if(currentScreen == null) {
            game.forceEnd = true;
            return true;
        }
        return false;
    }

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        GLES20.glClearColor(1f, 1f, 1f, 1.0f);
        Graphics.load(context, assetManager);
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        Graphics.onSurfaceChanged(width, height);
        GLES20.glViewport(0, 0, width, height);
    }

    private void tryRefreshing(float actualTick){
        if((int)actualTick >= tickCount && refreshNeeded) {
            //Log.d("refreshing", ""+actualTick + " " + tickCount);
            game.refresh();
            lastRefreshedTick = tickCount;
            refreshNeeded = false;
            updaterThread.interrupt();
        }
    }
    private int lastRefreshedTick;
    @Override
    public void onDrawFrame(GL10 unused) {
        /*long before = System.currentTimeMillis();//TODO TEST
        if(before - performanceTestStart < performanceTestLength)//TODO TEST
            performanceTestCounter++;//TODO TEST*/
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        //GLES20.glClearColor(0.992f, 0.953f, 0.906f, 1.0f);
        GLES20.glClearColor(Graphics.BG_COLOUR[0], Graphics.BG_COLOUR[1], Graphics.BG_COLOUR[2], Graphics.BG_COLOUR[3]);
        Graphics.color = Graphics.BLACK;

        GLES20.glEnable(GLES20.GL_BLEND);
        //GLES20.glEnable(GLES20.GL_ALPHA);
        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);

        synchronized (sync) {
            float actualTick = getActualTickCount();
            float partialTicks = 1.1f;
            for(int i = 0; i < 3 && partialTicks > 1; i++) {//give the updater 2 extra chances to catch up
                tryRefreshing(actualTick);
                partialTicks = actualTick - lastRefreshedTick;
            }
            //Log.d("rendering", ""+actualTick + " " + partialTicks);
            if (partialTicks > 1) {
                //Log.d("test", lastRefreshedTick + " " + partialTicks);
                partialTicks = 1;
            }
            GLES20.glEnable(GLES20.GL_CULL_FACE);
            GLES20.glEnable(GLES20.GL_DEPTH_TEST);
            GLES20.glDepthFunc(GLES20.GL_LEQUAL);
            GLES20.glDepthMask(true);
            Graphics.setPerspective(true);
            Graphics.enableLighting();
            game.render(partialTicks);

            if(newScreen != currentScreen){
                if(currentScreen != null)
                    currentScreen.onClose();
                currentScreen = newScreen;
                if(currentScreen != null)
                    currentScreen.init();
            }
            if(currentScreen != null) {
                touchHandler.handleGuiEvents();
                currentScreen.preRender(touchX, touchY);
                currentScreen.render(touchX, touchY);
            }
        }
        /*
        int passed = (int) (System.currentTimeMillis() - before);
        try {
            Thread.sleep(100 - passed);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        /*if(System.currentTimeMillis() - performanceTestStart < performanceTestLength) {//TODO TEST
            int passed = (int) (System.currentTimeMillis() - before);
            performanceTestAccumulator += passed;
        }*/
    }

    public void onDestroy() {
        running = false;
    }

    public Gui getCurrentScreen(){
        return currentScreen;
    }

    public Thread getUpdaterThread(){
        return updaterThread;
    }
}
