package com.kangacult.octogone;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.attributes.BlendingAttribute;
import com.badlogic.gdx.graphics.g3d.attributes.DepthTestAttribute;
import com.badlogic.gdx.graphics.g3d.loader.G3dModelLoader;
import com.badlogic.gdx.graphics.glutils.ImmediateModeRenderer20;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.physics.bullet.Bullet;
import com.badlogic.gdx.physics.bullet.DebugDrawer;
import com.badlogic.gdx.physics.bullet.collision.btBroadphaseProxy;
import com.badlogic.gdx.physics.bullet.collision.btBvhTriangleMeshShape;
import com.badlogic.gdx.physics.bullet.collision.btCollisionDispatcher;
import com.badlogic.gdx.physics.bullet.collision.btDbvtBroadphase;
import com.badlogic.gdx.physics.bullet.collision.btDefaultCollisionConfiguration;
import com.badlogic.gdx.physics.bullet.dynamics.btDiscreteDynamicsWorld;
import com.badlogic.gdx.physics.bullet.dynamics.btDynamicsWorld;
import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody;
import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody.btRigidBodyConstructionInfo;
import com.badlogic.gdx.physics.bullet.dynamics.btSequentialImpulseConstraintSolver;
import com.badlogic.gdx.physics.bullet.linearmath.btIDebugDraw;
import com.badlogic.gdx.utils.UBJsonReader;
import com.kangacult.octogone.Audio.SoundType;

public class Core extends ApplicationAdapter {
    public ModelBatch batch;
    public G3dModelLoader loader;
    public Model octoModel;
    public Model quadModel;
    public DebugDrawer debugDrawer;
    private ImmediateModeRenderer20 imRender;
    public InputPoller input = new InputPoller();

    public ModelInstance octagon;
    public btBvhTriangleMeshShape shape;
    public btRigidBody body;
    public MotionState motion;
    public BoundingBox box = new BoundingBox();
    public btDynamicsWorld world;
    public btDbvtBroadphase broadphase;
    public btDefaultCollisionConfiguration config;
    public btCollisionDispatcher dispatcher;
    public btSequentialImpulseConstraintSolver solver;
    
    /** Enemy waves. */
    public int wave;
    public int enemyTickets = 0;
    public Vector3 spawnPoint = new Vector3();
    public Player player = new Player();
    public Enemies enemies = new Enemies();
    public Projectiles projectiles = new Projectiles();
    public Audio audio = new Audio();
    public Fountains fountains = new Fountains();

    public Camera camera;
    public float deltaTime = 0f;
    public float gameClock;
    public boolean viewDebugInfo = false;
    public boolean viewOverlay = false;

    public Overlay overlay;

	// Game State
	public enum gameState {
		MENU, GAME
	}
	public gameState gState;

	//Game Preferences
	public boolean fullScreen;
	
	@Override
	public void create () {
		gamePrefs();

        Bullet.init(false, false);

        //Load Assets
        batch = new ModelBatch();
	    loader = new G3dModelLoader(new UBJsonReader());
	    octoModel = loader.loadModel(Gdx.files.internal("Octagon.g3db"));
	    octoModel.calculateBoundingBox(box);
	    for (Material m: octoModel.materials) {
            DepthTestAttribute depth = new DepthTestAttribute();
            depth.depthMask = true;
            m.set(depth);
            
            BlendingAttribute blend = new BlendingAttribute();
            blend.sourceFunction = GL20.GL_SRC_ALPHA;
            blend.destFunction = GL20.GL_ONE_MINUS_SRC_ALPHA;
            m.set(blend);
	    }
	    quadModel = loader.loadModel(Gdx.files.internal("Quad.g3db"));
	    for (Material m: quadModel.materials) {
            DepthTestAttribute depth = new DepthTestAttribute();
            depth.depthMask = false;
            m.set(depth);
            
            BlendingAttribute blend = new BlendingAttribute();
            blend.sourceFunction = GL20.GL_SRC_ALPHA;
            blend.destFunction = GL20.GL_ONE_MINUS_SRC_ALPHA;
            m.set(blend);
	    }
	    octagon = new ModelInstance(octoModel);
        debugDrawer = new DebugDrawer();
        debugDrawer.setDebugMode(btIDebugDraw.DebugDrawModes.DBG_MAX_DEBUG_DRAW_MODE);
        imRender = new ImmediateModeRenderer20(false, true, 0);
	    
	    //Physics initialization
        broadphase = new btDbvtBroadphase();
        config = new btDefaultCollisionConfiguration();
        dispatcher = new btCollisionDispatcher(config);
        solver = new btSequentialImpulseConstraintSolver();
	    world = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, config);
	    world.setGravity(new Vector3(0f, 0f, -20f));
	    shape = new btBvhTriangleMeshShape(octoModel.meshParts, true);
	    motion = new MotionState(octagon.transform);
	    btRigidBodyConstructionInfo constructionInfo = 
	            new btRigidBodyConstructionInfo(0, motion, shape, Vector3.Zero);
	    body = new btRigidBody(constructionInfo);
	    body.setFriction(0.5f);
	    body.setRollingFriction(0.1f);
	    body.setRestitution(0.1f);
	    constructionInfo.dispose();
	    world.addRigidBody(body,
	            (short) btBroadphaseProxy.CollisionFilterGroups.StaticFilter,
	            (short) btBroadphaseProxy.CollisionFilterGroups.AllFilter);
	    world.setDebugDrawer(debugDrawer);
	    
	    //Initialize entities
	    player.init(world, quadModel, loader);
	    enemies.init(quadModel);
	    projectiles.init(quadModel);
	    fountains.init(loader);

	    //Camera
        camera = new Camera(75, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        camera.near = 0.15f;
        camera.far = 400f;
        camera.up.set(Vector3.Z);

        //OpenGL
        Gdx.gl.glClearColor(0.54f, 0.73f, 0.82f, 0.0f);
        Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);
        Gdx.gl.glDepthFunc(GL20.GL_LEQUAL);
        
        Gdx.input.setCursorCatched(true);

        //Input
		input.init();
		
		//Audio
        audio.init();

		//Game State
		gState = gameState.MENU;

		//Game Clock
		gameClock = 0f;

		//Overlay
		overlay = new Overlay(wave);
		
		reset();
	}
	
	public void reset() {
		GamePrefs.instance.saveSettings();
		gameClock = 0f;
	    wave = 0;
	    enemyTickets = 0;
	    player.reset();
	    enemies.reset(world);
	    projectiles.reset(world);
	    fountains.reset();
	    overlay.gameOver = false;
	}

	@Override
	public void render () {
	    //Update state

		if (gState == gameState.GAME) {
			input.poll();
			deltaTime = Gdx.graphics.getDeltaTime();
		} else {
			if(input.keypressed()) {
				gState = gameState.GAME;
			}
		}

		if (player.alive){
			gameClock += deltaTime;
		} else {
	    	GamePrefs.instance.highScore(gameClock);
			GamePrefs.instance.highWave(wave);
		}

	    if (input.reset()){
			reset();
		}

	    if (input.jump){
	    	player.jump();
		}

	    player.update(deltaTime, world, camera, enemies, input, audio, overlay);
	    enemies.update(deltaTime, world, player, projectiles, camera, audio);
	    projectiles.update(deltaTime, world, player, camera, audio);
	    fountains.update(deltaTime, player, audio);
		if (enemies.getNumEnemiesAlive() < 1) {
			if (wave>0) {
				audio.pauseMusicAndStopSound(2.5f); //about longest wave_start sound
				audio.playSound(SoundType.WAVE_START, player.position, player.direction, player.position, true);
			}
			
		    wave++;
		    enemyTickets += wave * 8;
		    overlay.setWaveNum(wave);
		}
		while(enemyTickets > 0) {
		    spawnPoint.set(0f, box.getHeight() / 2f, enemies.height/2f);
		    spawnPoint.rotate(Vector3.Z, MathUtils.random(360f));
		    if (! enemies.spawn(world, spawnPoint))
		        break;
		    enemyTickets--;
		}

	    world.stepSimulation(deltaTime, 8, 1f/60f);

		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
		camera.update();
		batch.begin(camera);
		batch.render(octagon);
		batch.flush();
		Gdx.gl.glDepthMask(false);
		enemies.render(batch);
		projectiles.render(batch);
		fountains.render(batch);
		player.render(batch);
		Gdx.gl.glDepthMask(true);
		batch.end();

		if (Gdx.input.isKeyJustPressed(Keys.SEMICOLON))
		    viewDebugInfo = !viewDebugInfo;
		
		if (Gdx.input.isKeyJustPressed(Keys.F)){
			GamePrefs.instance.fullScreen = !GamePrefs.instance.fullScreen;
			GamePrefs.instance.saveSettings();
		}
		

		if (viewDebugInfo) {
		    debugDrawer.begin(camera);
		    world.debugDrawWorld();
		    debugDrawer.end();
		}
		
		audio.update(deltaTime);

		// Overlay Text
		if (gState == gameState.GAME) {
			overlay.draw(gameClock);
		} else {
			overlay.drawMenu();
		}

	}
	
	@Override
	public void dispose () {
	    projectiles.dispose();
	    player.dispose(world);
	    enemies.dispose(world);

	    world.dispose();
	    config.dispose();
	    dispatcher.dispose();
	    solver.dispose();
	    broadphase.dispose();

		octoModel.dispose();
		quadModel.dispose();
		batch.dispose();
		debugDrawer.dispose();
		imRender.dispose();
		
		audio.dispose();
	}

	public void newWave()
	{
		wave++;
		overlay.setWaveNum(wave);
		audio.playSound(SoundType.WAVE_START, player.position, player.direction, player.position);
	}

	public void gamePrefs()
	{
		// Load game Preferences
		GamePrefs.instance.load();

		// Fullscreen
		if (GamePrefs.instance.fullScreen) {
			fullScreen = true;
			Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode());
		}
	}
}
