/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.tests.bullet;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.attributes.FloatAttribute;
import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute;
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.collision.Collision;
import com.badlogic.gdx.physics.bullet.collision.btBoxShape;
import com.badlogic.gdx.physics.bullet.dynamics.btDefaultVehicleRaycaster;
import com.badlogic.gdx.physics.bullet.dynamics.btDynamicsWorld;
import com.badlogic.gdx.physics.bullet.dynamics.btRaycastVehicle;
import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody;
import com.badlogic.gdx.physics.bullet.dynamics.btVehicleRaycaster;
import com.badlogic.gdx.physics.bullet.dynamics.btRaycastVehicle.btVehicleTuning;
import com.badlogic.gdx.physics.bullet.dynamics.btWheelInfo;

/** @author Xoppa */
public class VehicleTest extends BaseBulletTest {
	public btVehicleRaycaster raycaster;
	public btRaycastVehicle vehicle;
	public btVehicleTuning tuning;
	BulletEntity chassis;
	BulletEntity wheels[] = new BulletEntity[4];

	boolean downPressed;
	boolean upPressed;
	boolean leftPressed;
	boolean rightPressed;
	Vector3 tmpV = new Vector3();

	protected btVehicleRaycaster getRaycaster () {
		return new btDefaultVehicleRaycaster((btDynamicsWorld)world.collisionWorld);
	}

	@Override
	public void create () {
		super.create();
		instructions = "Tap to shoot\nArrow keys to drive\nR to reset\nLong press to toggle debug mode\nSwipe for next test";

		final Model chassisModel = objLoader.loadModel(Gdx.files.internal("data/car.obj"));
		disposables.add(chassisModel);
		chassisModel.materials.get(0).clear();
		chassisModel.materials.get(0).set(ColorAttribute.createDiffuse(Color.RED), ColorAttribute.createSpecular(Color.WHITE));
		final Model wheelModel = objLoader.loadModel(Gdx.files.internal("data/wheel.obj"));
		disposables.add(wheelModel);
		wheelModel.materials.get(0).clear();
		wheelModel.materials.get(0).set(ColorAttribute.createDiffuse(Color.BLACK), ColorAttribute.createSpecular(Color.WHITE),
			FloatAttribute.createShininess(128));
		Texture checkboard = new Texture(Gdx.files.internal("data/g3d/checkboard.png"));
		final Model largeGroundModel = modelBuilder.createBox(200f, 2f, 200f,
			new Material(TextureAttribute.createDiffuse(checkboard), ColorAttribute.createSpecular(Color.WHITE),
				FloatAttribute.createShininess(16f)),
			Usage.Position | Usage.Normal | Usage.TextureCoordinates);
		largeGroundModel.manageDisposable(checkboard);
		disposables.add(largeGroundModel);
		world.addConstructor("largeground", new BulletConstructor(largeGroundModel, 0f));

		BoundingBox bounds = new BoundingBox();
		Vector3 chassisHalfExtents = chassisModel.calculateBoundingBox(bounds).getDimensions(new Vector3()).scl(0.5f);
		Vector3 wheelHalfExtents = wheelModel.calculateBoundingBox(bounds).getDimensions(new Vector3()).scl(0.5f);

		world.addConstructor("chassis", new BulletConstructor(chassisModel, 5f, new btBoxShape(chassisHalfExtents)));
		world.addConstructor("wheel", new BulletConstructor(wheelModel, 0, null));

		for (int i = 0; i < 1000; i += 200)
			world.add("largeground", 0f, -1f, i);

		chassis = world.add("chassis", 0, 3f, 0);
		wheels[0] = world.add("wheel", 0, 0, 0);
		wheels[1] = world.add("wheel", 0, 0, 0);
		wheels[2] = world.add("wheel", 0, 0, 0);
		wheels[3] = world.add("wheel", 0, 0, 0);

		// Create the vehicle
		raycaster = getRaycaster();
		tuning = new btVehicleTuning();
		vehicle = new btRaycastVehicle(tuning, (btRigidBody)chassis.body, raycaster);
		chassis.body.setActivationState(Collision.DISABLE_DEACTIVATION);
		((btDynamicsWorld)world.collisionWorld).addVehicle(vehicle);

		vehicle.setCoordinateSystem(0, 1, 2);

		btWheelInfo wheelInfo;
		Vector3 point = new Vector3();
		Vector3 direction = new Vector3(0, -1, 0);
		Vector3 axis = new Vector3(-1, 0, 0);
		wheelInfo = vehicle.addWheel(point.set(chassisHalfExtents).scl(0.9f, -0.8f, 0.7f), direction, axis,
			wheelHalfExtents.z * 0.3f, wheelHalfExtents.z, tuning, true);
		wheelInfo = vehicle.addWheel(point.set(chassisHalfExtents).scl(-0.9f, -0.8f, 0.7f), direction, axis,
			wheelHalfExtents.z * 0.3f, wheelHalfExtents.z, tuning, true);
		wheelInfo = vehicle.addWheel(point.set(chassisHalfExtents).scl(0.9f, -0.8f, -0.5f), direction, axis,
			wheelHalfExtents.z * 0.3f, wheelHalfExtents.z, tuning, false);
		wheelInfo = vehicle.addWheel(point.set(chassisHalfExtents).scl(-0.9f, -0.8f, -0.5f), direction, axis,
			wheelHalfExtents.z * 0.3f, wheelHalfExtents.z, tuning, false);
	}

	float maxForce = 100f;
	float currentForce = 0f;
	float acceleration = 50f; // force/second
	float maxAngle = 60f;
	float currentAngle = 0f;
	float steerSpeed = 45f; // angle/second

	@Override
	public void update () {
		final float delta = Gdx.graphics.getDeltaTime();
		float angle = currentAngle;
		if (rightPressed) {
			if (angle > 0f) angle = 0f;
			angle = MathUtils.clamp(angle - steerSpeed * delta, -maxAngle, 0f);
		} else if (leftPressed) {
			if (angle < 0f) angle = 0f;
			angle = MathUtils.clamp(angle + steerSpeed * delta, 0f, maxAngle);
		} else
			angle = 0f;
		if (angle != currentAngle) {
			currentAngle = angle;
			vehicle.setSteeringValue(angle * MathUtils.degreesToRadians, 0);
			vehicle.setSteeringValue(angle * MathUtils.degreesToRadians, 1);
		}

		float force = currentForce;
		if (upPressed) {
			if (force < 0f) force = 0f;
			force = MathUtils.clamp(force + acceleration * delta, 0f, maxForce);
		} else if (downPressed) {
			if (force > 0f) force = 0f;
			force = MathUtils.clamp(force - acceleration * delta, -maxForce, 0f);
		} else
			force = 0f;
		if (force != currentForce) {
			currentForce = force;
			vehicle.applyEngineForce(force, 0);
			vehicle.applyEngineForce(force, 1);
		}

		super.update();

		for (int i = 0; i < wheels.length; i++) {
			vehicle.updateWheelTransform(i, true);
			vehicle.getWheelInfo(i).getWorldTransform().getOpenGLMatrix(wheels[i].transform.val);
		}

		chassis.transform.getTranslation(camera.position);
		tmpV.set(camera.position).sub(5, 0, 5).y = 0f;
		camera.position.add(tmpV.nor().scl(-6f)).y = 4.f;
		chassis.transform.getTranslation(tmpV);
		camera.lookAt(tmpV);
		camera.up.set(Vector3.Y);
		camera.update();
	}

	@Override
	public boolean tap (float x, float y, int count, int button) {
		shoot(x, y);
		return true;
	}

	@Override
	public void dispose () {
		super.dispose();
		vehicle.dispose();
		vehicle = null;
		raycaster.dispose();
		raycaster = null;
		tuning.dispose();
		tuning = null;
	}

	@Override
	public boolean keyDown (int keycode) {
		switch (keycode) {
		case Keys.DOWN:
			downPressed = true;
			break;
		case Keys.UP:
			upPressed = true;
			break;
		case Keys.LEFT:
			leftPressed = true;
			break;
		case Keys.RIGHT:
			rightPressed = true;
			break;
		}
		return super.keyDown(keycode);
	}

	@Override
	public boolean keyUp (int keycode) {
		switch (keycode) {
		case Keys.DOWN:
			downPressed = false;
			break;
		case Keys.UP:
			upPressed = false;
			break;
		case Keys.LEFT:
			leftPressed = false;
			break;
		case Keys.RIGHT:
			rightPressed = false;
			break;
		case Keys.R:
			chassis.body.setWorldTransform(chassis.transform.setToTranslation(0, 5, 0));
			chassis.body.setInterpolationWorldTransform(chassis.transform);
			((btRigidBody)(chassis.body)).setLinearVelocity(Vector3.Zero);
			((btRigidBody)(chassis.body)).setAngularVelocity(Vector3.Zero);
			chassis.body.activate();
			break;
		}
		return super.keyUp(keycode);
	}
}
