/*******************************************************************************
 * Copyright (c) 2013, Daniel Murphy
 * 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.
 * 
 * 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 HOLDER 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 org.jbox2d.dynamics;

import org.jbox2d.collision.AABB;
import org.jbox2d.collision.RayCastInput;
import org.jbox2d.collision.RayCastOutput;
import org.jbox2d.collision.broadphase.BroadPhase;
import org.jbox2d.collision.shapes.MassData;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.collision.shapes.ShapeType;
import org.jbox2d.common.MathUtils;
import org.jbox2d.common.Transform;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.contacts.Contact;
import org.jbox2d.dynamics.contacts.ContactEdge;

/** A fixture is used to attach a shape to a body for collision detection. A fixture inherits its transform from its parent.
 * Fixtures hold additional non-geometric data such as friction, collision filters, etc. Fixtures are created via
 * Body::CreateFixture.
 * 
 * @warning you cannot reuse fixtures.
 * 
 * @author daniel */
public class Fixture {

	public float m_density;

	public Fixture m_next;
	public Body m_body;

	public Shape m_shape;

	public float m_friction;
	public float m_restitution;

	public FixtureProxy[] m_proxies;
	public int m_proxyCount;

	public final Filter m_filter;

	public boolean m_isSensor;

	public Object m_userData;

	public Fixture () {
		m_userData = null;
		m_body = null;
		m_next = null;
		m_proxies = null;
		m_proxyCount = 0;
		m_shape = null;
		m_filter = new Filter();
	}

	/** Get the type of the child shape. You can use this to down cast to the concrete shape.
	 * 
	 * @return the shape type. */
	public ShapeType getType () {
		return m_shape.getType();
	}

	/** Get the child shape. You can modify the child shape, however you should not change the number of vertices because this will
	 * crash some collision caching mechanisms.
	 * 
	 * @return */
	public Shape getShape () {
		return m_shape;
	}

	/** Is this fixture a sensor (non-solid)?
	 * 
	 * @return the true if the shape is a sensor.
	 * @return */
	public boolean isSensor () {
		return m_isSensor;
	}

	/** Set if this fixture is a sensor.
	 * 
	 * @param sensor */
	public void setSensor (boolean sensor) {
		if (sensor != m_isSensor) {
			m_body.setAwake(true);
			m_isSensor = sensor;
		}
	}

	/** Set the contact filtering data. This is an expensive operation and should not be called frequently. This will not update
	 * contacts until the next time step when either parent body is awake. This automatically calls refilter.
	 * 
	 * @param filter */
	public void setFilterData (final Filter filter) {
		m_filter.set(filter);

		refilter();
	}

	/** Get the contact filtering data.
	 * 
	 * @return */
	public Filter getFilterData () {
		return m_filter;
	}

	/** Call this if you want to establish collision that was previously disabled by ContactFilter::ShouldCollide. */
	public void refilter () {
		if (m_body == null) {
			return;
		}

		// Flag associated contacts for filtering.
		ContactEdge edge = m_body.getContactList();
		while (edge != null) {
			Contact contact = edge.contact;
			Fixture fixtureA = contact.getFixtureA();
			Fixture fixtureB = contact.getFixtureB();
			if (fixtureA == this || fixtureB == this) {
				contact.flagForFiltering();
			}
			edge = edge.next;
		}

		World world = m_body.getWorld();

		if (world == null) {
			return;
		}

		// Touch each proxy so that new pairs may be created
		BroadPhase broadPhase = world.m_contactManager.m_broadPhase;
		for (int i = 0; i < m_proxyCount; ++i) {
			broadPhase.touchProxy(m_proxies[i].proxyId);
		}
	}

	/** Get the parent body of this fixture. This is NULL if the fixture is not attached.
	 * 
	 * @return the parent body.
	 * @return */
	public Body getBody () {
		return m_body;
	}

	/** Get the next fixture in the parent body's fixture list.
	 * 
	 * @return the next shape.
	 * @return */
	public Fixture getNext () {
		return m_next;
	}

	public void setDensity (float density) {
		assert (density >= 0f);
		m_density = density;
	}

	public float getDensity () {
		return m_density;
	}

	/** Get the user data that was assigned in the fixture definition. Use this to store your application specific data.
	 * 
	 * @return */
	public Object getUserData () {
		return m_userData;
	}

	/** Set the user data. Use this to store your application specific data.
	 * 
	 * @param data */
	public void setUserData (Object data) {
		m_userData = data;
	}

	/** Test a point for containment in this fixture. This only works for convex shapes.
	 * 
	 * @param p a point in world coordinates.
	 * @return */
	public boolean testPoint (final Vec2 p) {
		return m_shape.testPoint(m_body.m_xf, p);
	}

	/** Cast a ray against this shape.
	 * 
	 * @param output the ray-cast results.
	 * @param input the ray-cast input parameters.
	 * @param output
	 * @param input */
	public boolean raycast (RayCastOutput output, RayCastInput input, int childIndex) {
		return m_shape.raycast(output, input, m_body.m_xf, childIndex);
	}

	/** Get the mass data for this fixture. The mass data is based on the density and the shape. The rotational inertia is about
	 * the shape's origin.
	 * 
	 * @return */
	public void getMassData (MassData massData) {
		m_shape.computeMass(massData, m_density);
	}

	/** Get the coefficient of friction.
	 * 
	 * @return */
	public float getFriction () {
		return m_friction;
	}

	/** Set the coefficient of friction. This will _not_ change the friction of existing contacts.
	 * 
	 * @param friction */
	public void setFriction (float friction) {
		m_friction = friction;
	}

	/** Get the coefficient of restitution.
	 * 
	 * @return */
	public float getRestitution () {
		return m_restitution;
	}

	/** Set the coefficient of restitution. This will _not_ change the restitution of existing contacts.
	 * 
	 * @param restitution */
	public void setRestitution (float restitution) {
		m_restitution = restitution;
	}

	/** Get the fixture's AABB. This AABB may be enlarge and/or stale. If you need a more accurate AABB, compute it using the shape
	 * and the body transform.
	 * 
	 * @return */
	public AABB getAABB (int childIndex) {
		assert (childIndex >= 0 && childIndex < m_proxyCount);
		return m_proxies[childIndex].aabb;
	}

	/** Compute the distance from this fixture.
	 * 
	 * @param p a point in world coordinates.
	 * @return distance */
	public float computeDistance (Vec2 p, int childIndex, Vec2 normalOut) {
		return m_shape.computeDistanceToOut(m_body.getTransform(), p, childIndex, normalOut);
	}

	// We need separation create/destroy functions from the constructor/destructor because
	// the destructor cannot access the allocator (no destructor arguments allowed by C++).

	public void create (Body body, FixtureDef def) {
		m_userData = def.userData;
		m_friction = def.friction;
		m_restitution = def.restitution;

		m_body = body;
		m_next = null;

		m_filter.set(def.filter);

		m_isSensor = def.isSensor;

		m_shape = def.shape.clone();

		// Reserve proxy space
		int childCount = m_shape.getChildCount();
		if (m_proxies == null) {
			m_proxies = new FixtureProxy[childCount];
			for (int i = 0; i < childCount; i++) {
				m_proxies[i] = new FixtureProxy();
				m_proxies[i].fixture = null;
				m_proxies[i].proxyId = BroadPhase.NULL_PROXY;
			}
		}

		if (m_proxies.length < childCount) {
			FixtureProxy[] old = m_proxies;
			int newLen = MathUtils.max(old.length * 2, childCount);
			m_proxies = new FixtureProxy[newLen];
			System.arraycopy(old, 0, m_proxies, 0, old.length);
			for (int i = 0; i < newLen; i++) {
				if (i >= old.length) {
					m_proxies[i] = new FixtureProxy();
				}
				m_proxies[i].fixture = null;
				m_proxies[i].proxyId = BroadPhase.NULL_PROXY;
			}
		}
		m_proxyCount = 0;

		m_density = def.density;
	}

	public void destroy () {
		// The proxies must be destroyed before calling this.
		assert (m_proxyCount == 0);

		// Free the child shape.
		m_shape = null;
		m_proxies = null;
		m_next = null;

		// TODO pool shapes
		// TODO pool fixtures
	}

	// These support body activation/deactivation.
	public void createProxies (BroadPhase broadPhase, final Transform xf) {
		assert (m_proxyCount == 0);

		// Create proxies in the broad-phase.
		m_proxyCount = m_shape.getChildCount();

		for (int i = 0; i < m_proxyCount; ++i) {
			FixtureProxy proxy = m_proxies[i];
			m_shape.computeAABB(proxy.aabb, xf, i);
			proxy.proxyId = broadPhase.createProxy(proxy.aabb, proxy);
			proxy.fixture = this;
			proxy.childIndex = i;
		}
	}

	/** Internal method
	 * 
	 * @param broadPhase */
	public void destroyProxies (BroadPhase broadPhase) {
		// Destroy proxies in the broad-phase.
		for (int i = 0; i < m_proxyCount; ++i) {
			FixtureProxy proxy = m_proxies[i];
			broadPhase.destroyProxy(proxy.proxyId);
			proxy.proxyId = BroadPhase.NULL_PROXY;
		}

		m_proxyCount = 0;
	}

	private final AABB pool1 = new AABB();
	private final AABB pool2 = new AABB();
	private final Vec2 displacement = new Vec2();

	/** Internal method
	 * 
	 * @param broadPhase
	 * @param xf1
	 * @param xf2 */
	protected void synchronize (BroadPhase broadPhase, final Transform transform1, final Transform transform2) {
		if (m_proxyCount == 0) {
			return;
		}

		for (int i = 0; i < m_proxyCount; ++i) {
			FixtureProxy proxy = m_proxies[i];

			// Compute an AABB that covers the swept shape (may miss some rotation effect).
			final AABB aabb1 = pool1;
			final AABB aab = pool2;
			m_shape.computeAABB(aabb1, transform1, proxy.childIndex);
			m_shape.computeAABB(aab, transform2, proxy.childIndex);

			proxy.aabb.lowerBound.x = aabb1.lowerBound.x < aab.lowerBound.x ? aabb1.lowerBound.x : aab.lowerBound.x;
			proxy.aabb.lowerBound.y = aabb1.lowerBound.y < aab.lowerBound.y ? aabb1.lowerBound.y : aab.lowerBound.y;
			proxy.aabb.upperBound.x = aabb1.upperBound.x > aab.upperBound.x ? aabb1.upperBound.x : aab.upperBound.x;
			proxy.aabb.upperBound.y = aabb1.upperBound.y > aab.upperBound.y ? aabb1.upperBound.y : aab.upperBound.y;
			displacement.x = transform2.p.x - transform1.p.x;
			displacement.y = transform2.p.y - transform1.p.y;

			broadPhase.moveProxy(proxy.proxyId, proxy.aabb, displacement);
		}
	}
}
