/*
 * Copyright (c) 2009-2010 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jme3game.cubefield;

import java.util.ArrayList;

import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingVolume;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Dome;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Kyle "bonechilla" Williams
 */
public class CubeField extends SimpleApplication implements AnalogListener {

    public static void main(String[] args) {
        CubeField app = new CubeField();
        app.start();
    }

    private BitmapFont defaultFont;

    private boolean START;
    private int difficulty, Score, colorInt, highCap, lowCap,diffHelp;
    private Node player;
    private Geometry fcube;
    private ArrayList<Geometry> cubeField;
    private ArrayList<ColorRGBA> obstacleColors;
    private float speed, coreTime,coreTime2;
    private float camAngle = 0;
    private BitmapText fpsScoreText, pressStart;

    private boolean solidBox = true;
    private Material playerMaterial;
    private Material floorMaterial;

    private float fpsRate = 1000f / 1f;

    /**
     * Initializes game 
     */
    @Override
    public void simpleInitApp() {
        Logger.getLogger("com.jme3").setLevel(Level.WARNING);

        flyCam.setEnabled(false);
        statsView.setCullHint(CullHint.Always);

        Keys();

        defaultFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        pressStart = new BitmapText(defaultFont, false);
        fpsScoreText = new BitmapText(defaultFont, false);

        loadText(fpsScoreText, "Current Score: 0", defaultFont, 0, 2, 0);
        loadText(pressStart, "PRESS ENTER", defaultFont, 0, 5, 0);
        
        player = createPlayer();
        rootNode.attachChild(player);
        cubeField = new ArrayList<Geometry>();
        obstacleColors = new ArrayList<ColorRGBA>();

        gameReset();
    }
    /**
     * Used to reset cubeField 
     */
    private void gameReset(){
        Score = 0;
        lowCap = 10;
        colorInt = 0;
        highCap = 40;
        difficulty = highCap;

        for (Geometry cube : cubeField){
            cube.removeFromParent();
        }
        cubeField.clear();

        if (fcube != null){
            fcube.removeFromParent();
        }
        fcube = createFirstCube();

        obstacleColors.clear();
        obstacleColors.add(ColorRGBA.Orange);
        obstacleColors.add(ColorRGBA.Red);
        obstacleColors.add(ColorRGBA.Yellow);
        renderer.setBackgroundColor(ColorRGBA.White);
        speed = lowCap / 400f;
        coreTime = 20.0f;
        coreTime2 = 10.0f;
        diffHelp=lowCap;
        player.setLocalTranslation(0,0,0);
    }

    @Override
    public void simpleUpdate(float tpf) {
        camTakeOver(tpf);
        if (START){
            gameLogic(tpf);
        }
        colorLogic();
    }
    /**
     * Forcefully takes over Camera adding functionality and placing it behind the character
     * @param tpf Tickes Per Frame
     */
    private void camTakeOver(float tpf) {
        cam.setLocation(player.getLocalTranslation().add(-8, 2, 0));
        cam.lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y);
        
        Quaternion rot = new Quaternion();
        rot.fromAngleNormalAxis(camAngle, Vector3f.UNIT_Z);
        cam.setRotation(cam.getRotation().mult(rot));
        camAngle *= FastMath.pow(.99f, fpsRate * tpf);
    }

    @Override
    public void requestClose(boolean esc) {
        if (!esc){
            System.out.println("The game was quit.");
        }else{
            System.out.println("Player has Collided. Final Score is " + Score);
        }
        context.destroy(false);
    }
    /**
     * Randomly Places a cube on the map between 30 and 90 paces away from player
     */
    private void randomizeCube() {
        Geometry cube = fcube.clone();
        int playerX = (int) player.getLocalTranslation().getX();
        int playerZ = (int) player.getLocalTranslation().getZ();
//        float x = FastMath.nextRandomInt(playerX + difficulty + 10, playerX + difficulty + 150);
        float x = FastMath.nextRandomInt(playerX + difficulty + 30, playerX + difficulty + 90);
        float z = FastMath.nextRandomInt(playerZ - difficulty - 50, playerZ + difficulty + 50);
        cube.getLocalTranslation().set(x, 0, z);

//        playerX+difficulty+30,playerX+difficulty+90

        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        if (!solidBox){
            mat.getAdditionalRenderState().setWireframe(true);
        }
        mat.setColor("Color", obstacleColors.get(FastMath.nextRandomInt(0, obstacleColors.size() - 1)));
        cube.setMaterial(mat);

        rootNode.attachChild(cube);
        cubeField.add(cube);
    }

    private Geometry createFirstCube() {
        Vector3f loc = player.getLocalTranslation();
        loc.addLocal(4, 0, 0);
        Box b = new Box(loc, 1, 1, 1);

        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geom.setMaterial(mat);

        return geom;
    }

    private Node createPlayer() {
        Dome b = new Dome(Vector3f.ZERO, 10, 100, 1);
        Geometry playerMesh = new Geometry("Box", b);

        playerMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        playerMaterial.setColor("Color", ColorRGBA.Red);
        playerMesh.setMaterial(playerMaterial);
        playerMesh.setName("player");

        Box floor = new Box(Vector3f.ZERO.add(playerMesh.getLocalTranslation().getX(),
                playerMesh.getLocalTranslation().getY() - 1, 0), 100, 0, 100);
        Geometry floorMesh = new Geometry("Box", floor);

        floorMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        floorMaterial.setColor("Color", ColorRGBA.LightGray);
        floorMesh.setMaterial(floorMaterial);
        floorMesh.setName("floor");

        Node playerNode = new Node();
        playerNode.attachChild(playerMesh);
        playerNode.attachChild(floorMesh);

        return playerNode;
    }

    /**
     * If Game is Lost display Score and Reset the Game
     */
    private void gameLost(){
        START = false;
        loadText(pressStart, "You lost! Press enter to try again.", defaultFont, 0, 5, 0);
        gameReset();
    }
    
    /**
     * Core Game Logic
     */
    private void gameLogic(float tpf){
    	//Subtract difficulty level in accordance to speed every 10 seconds
    	if(timer.getTimeInSeconds()>=coreTime2){
			coreTime2=timer.getTimeInSeconds()+10;
			if(difficulty<=lowCap){
				difficulty=lowCap;
			}
			else if(difficulty>lowCap){
				difficulty-=5;
				diffHelp+=1;
			}
		}
    	
        if(speed<.1f){
            speed+=.000001f*tpf*fpsRate;
        }

        player.move(speed * tpf * fpsRate, 0, 0);
        if (cubeField.size() > difficulty){
            cubeField.remove(0);
        }else if (cubeField.size() != difficulty){
            randomizeCube();
        }

        if (cubeField.isEmpty()){
            requestClose(false);
        }else{
            for (int i = 0; i < cubeField.size(); i++){
            	
            	//better way to check collision
                Geometry playerModel = (Geometry) player.getChild(0);
                Geometry cubeModel = cubeField.get(i);
                cubeModel.updateGeometricState();

                BoundingVolume pVol = playerModel.getWorldBound();
                BoundingVolume vVol = cubeModel.getWorldBound();

                if (pVol.intersects(vVol)){
                    gameLost();
                    return;
                }
                //Remove cube if 10 world units behind player
                if (cubeField.get(i).getLocalTranslation().getX() + 10 < player.getLocalTranslation().getX()){
                    cubeField.get(i).removeFromParent();
                    cubeField.remove(cubeField.get(i));
                }

            }
        }

        Score += fpsRate * tpf;
        fpsScoreText.setText("Current Score: "+Score);
    }
    /**
     * Sets up the keyboard bindings
     */
    private void Keys() {
        inputManager.addMapping("START", new KeyTrigger(KeyInput.KEY_RETURN));
        inputManager.addMapping("Left",  new KeyTrigger(KeyInput.KEY_LEFT));
        inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_RIGHT));
        inputManager.addListener(this, "START", "Left", "Right");
    }

    public void onAnalog(String binding, float value, float tpf) {
        if (binding.equals("START") && !START){
            START = true;
            guiNode.detachChild(pressStart);
            System.out.println("START");
        }else if (START == true && binding.equals("Left")){
            player.move(0, 0, -(speed / 2f) * value * fpsRate);
            camAngle -= value*tpf;
        }else if (START == true && binding.equals("Right")){
            player.move(0, 0, (speed / 2f) * value * fpsRate);
            camAngle += value*tpf;
        }
    }

    /**
     * Determines the colors of the player, floor, obstacle and background
     */
    private void colorLogic() {
    	if (timer.getTimeInSeconds() >= coreTime){
            
        	colorInt++;
            coreTime = timer.getTimeInSeconds() + 20;
        

	        switch (colorInt){
	            case 1:
	                obstacleColors.clear();
	                solidBox = false;
	                obstacleColors.add(ColorRGBA.Green);
	                renderer.setBackgroundColor(ColorRGBA.Black);
	                playerMaterial.setColor("Color", ColorRGBA.White);
			floorMaterial.setColor("Color", ColorRGBA.Black);
	                break;
	            case 2:
	                obstacleColors.set(0, ColorRGBA.Black);
	                solidBox = true;
	                renderer.setBackgroundColor(ColorRGBA.White);
	                playerMaterial.setColor("Color", ColorRGBA.Gray);
                        floorMaterial.setColor("Color", ColorRGBA.LightGray);
	                break;
	            case 3:
	                obstacleColors.set(0, ColorRGBA.Pink);
	                break;
	            case 4:
	                obstacleColors.set(0, ColorRGBA.Cyan);
	                obstacleColors.add(ColorRGBA.Magenta);
	                renderer.setBackgroundColor(ColorRGBA.Gray);
                        floorMaterial.setColor("Color", ColorRGBA.Gray);
	                playerMaterial.setColor("Color", ColorRGBA.White);
	                break;
	            case 5:
	                obstacleColors.remove(0);
	                renderer.setBackgroundColor(ColorRGBA.Pink);
	                solidBox = false;
	                playerMaterial.setColor("Color", ColorRGBA.White);
	                break;
	            case 6:
	                obstacleColors.set(0, ColorRGBA.White);
	                solidBox = true;
	                renderer.setBackgroundColor(ColorRGBA.Black);
	                playerMaterial.setColor("Color", ColorRGBA.Gray);
                        floorMaterial.setColor("Color", ColorRGBA.LightGray);
	                break;
	            case 7:
	                obstacleColors.set(0, ColorRGBA.Green);
	                renderer.setBackgroundColor(ColorRGBA.Gray);
	                playerMaterial.setColor("Color", ColorRGBA.Black);
                        floorMaterial.setColor("Color", ColorRGBA.Orange);
	                break;
	            case 8:
	                obstacleColors.set(0, ColorRGBA.Red);
                        floorMaterial.setColor("Color", ColorRGBA.Pink);
	                break;
	            case 9:
	                obstacleColors.set(0, ColorRGBA.Orange);
	                obstacleColors.add(ColorRGBA.Red);
	                obstacleColors.add(ColorRGBA.Yellow);
	                renderer.setBackgroundColor(ColorRGBA.White);
	                playerMaterial.setColor("Color", ColorRGBA.Red);
	                floorMaterial.setColor("Color", ColorRGBA.Gray);
	                colorInt=0;
	                break;
	            default:
	                break;
	        }
        }
    }
    /**
     * Sets up a BitmapText to be displayed
     * @param txt the Bitmap Text
     * @param text the 
     * @param font the font of the text
     * @param x    
     * @param y
     * @param z
     */
    private void loadText(BitmapText txt, String text, BitmapFont font, float x, float y, float z) {
        txt.setSize(font.getCharSet().getRenderedSize());
        txt.setLocalTranslation(txt.getLineWidth() * x, txt.getLineHeight() * y, z);
        txt.setText(text);
        guiNode.attachChild(txt);
    }
} 