/*******************************************************************************
 * 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.graphics.g2d;

import java.io.BufferedReader;
import java.io.IOException;

import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.RayCastCallback;
import com.badlogic.gdx.physics.box2d.World;

/** @author kalle_h
 * 
 *         ParticleEmitterBox2D use box2d rayCast:ing to achieve continuous collision detection against box2d fixtures. If
 *         particle detect collision it change it's direction before actual collision would occur. Velocity is 100% reflected.
 * 
 *         These particles does not have any other physical attributes or functionality. Particles can't collide to other
 *         particles. */
public class ParticleEmitterBox2D extends ParticleEmitter {
	final World world;
	final Vector2 startPoint = new Vector2();
	final Vector2 endPoint = new Vector2();
	/** collision flag */
	boolean particleCollided;
	float normalAngle;
	/** If velocities squared is shorter than this it could lead 0 length rayCast that cause c++ assertion at box2d */
	private final static float EPSILON = 0.001f;

	/** default visibility to prevent synthetic accesor creation */
	final RayCastCallback rayCallBack = new RayCastCallback() {
		public float reportRayFixture (Fixture fixture, Vector2 point, Vector2 normal, float fraction) {
			ParticleEmitterBox2D.this.particleCollided = true;
			ParticleEmitterBox2D.this.normalAngle = MathUtils.atan2(normal.y, normal.x) * MathUtils.radiansToDegrees;
			return fraction;
		}
	};

	/** Constructs default ParticleEmitterBox2D. Box2d World is used for rayCasting. Assumes that particles use same unit system
	 * that box2d world does.
	 * 
	 * @param world */
	public ParticleEmitterBox2D (World world) {
		super();
		this.world = world;
	}

	/** /**Constructs ParticleEmitterBox2D using bufferedReader. Box2d World is used for rayCasting. Assumes that particles use same
	 * unit system that box2d world does.
	 * 
	 * @param world
	 * @param reader
	 * @throws IOException */
	public ParticleEmitterBox2D (World world, BufferedReader reader) throws IOException {
		super(reader);
		this.world = world;
	}

	/** Constructs ParticleEmitterBox2D fully copying given emitter attributes. Box2d World is used for rayCasting. Assumes that
	 * particles use same unit system that box2d world does.
	 * 
	 * @param world
	 * @param emitter */
	public ParticleEmitterBox2D (World world, ParticleEmitter emitter) {
		super(emitter);
		this.world = world;
	}

	@Override
	protected Particle newParticle (Sprite sprite) {
		return new ParticleBox2D(sprite);
	}

	/** Particle that can collide to box2d fixtures */
	private class ParticleBox2D extends Particle {
		public ParticleBox2D (Sprite sprite) {
			super(sprite);
		}

		/** translate particle given amount. Continuous collision detection achieved by using RayCast from oldPos to newPos.
		 * 
		 * @param velocityX
		 * @param velocityY */
		@Override
		public void translate (float velocityX, float velocityY) {
			/** If velocities squares summed is shorter than Epsilon it could lead ~0 length rayCast that cause nasty c++ assertion
			 * inside box2d. This is so short distance that moving particle has no effect so this return early. */
			if ((velocityX * velocityX + velocityY * velocityY) < EPSILON) return;

			/** Position offset is half of sprite texture size. */
			final float x = getX() + getWidth() / 2f;
			final float y = getY() + getHeight() / 2f;

			/** collision flag to false */
			particleCollided = false;
			startPoint.set(x, y);
			endPoint.set(x + velocityX, y + velocityY);
			if (world != null) world.rayCast(rayCallBack, startPoint, endPoint);

			/** If ray collided boolean has set to true at rayCallBack */
			if (particleCollided) {
				// perfect reflection
				angle = 2f * normalAngle - angle - 180f;
				angleCos = MathUtils.cosDeg(angle);
				angleSin = MathUtils.sinDeg(angle);
				velocityX = velocity * angleCos;
				velocityY = velocity * angleSin;
			}

			super.translate(velocityX, velocityY);
		}
	}
}
