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

import org.jbox2d.callbacks.ContactListener;
import org.jbox2d.collision.ContactID;
import org.jbox2d.collision.Manifold;
import org.jbox2d.collision.ManifoldPoint;
import org.jbox2d.collision.WorldManifold;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.MathUtils;
import org.jbox2d.common.Transform;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.pooling.IWorldPool;

/** The class manages contact between two shapes. A contact exists for each overlapping AABB in the broad-phase (except if
 * filtered). Therefore a contact object may exist that has no contact points.
 * 
 * @author daniel */
public abstract class Contact {

	// Flags stored in m_flags
	// Used when crawling contact graph when forming islands.
	public static final int ISLAND_FLAG = 0x0001;
	// Set when the shapes are touching.
	public static final int TOUCHING_FLAG = 0x0002;
	// This contact can be disabled (by user)
	public static final int ENABLED_FLAG = 0x0004;
	// This contact needs filtering because a fixture filter was changed.
	public static final int FILTER_FLAG = 0x0008;
	// This bullet contact had a TOI event
	public static final int BULLET_HIT_FLAG = 0x0010;

	public static final int TOI_FLAG = 0x0020;

	public int m_flags;

	// World pool and list pointers.
	public Contact m_prev;
	public Contact m_next;

	// Nodes for connecting bodies.
	public ContactEdge m_nodeA = null;
	public ContactEdge m_nodeB = null;

	public Fixture m_fixtureA;
	public Fixture m_fixtureB;

	public int m_indexA;
	public int m_indexB;

	public final Manifold m_manifold;

	public float m_toiCount;
	public float m_toi;

	public float m_friction;
	public float m_restitution;

	public float m_tangentSpeed;

	protected final IWorldPool pool;

	protected Contact (IWorldPool argPool) {
		m_fixtureA = null;
		m_fixtureB = null;
		m_nodeA = new ContactEdge();
		m_nodeB = new ContactEdge();
		m_manifold = new Manifold();
		pool = argPool;
	}

	/** initialization for pooling */
	public void init (Fixture fA, int indexA, Fixture fB, int indexB) {
		m_flags = ENABLED_FLAG;

		m_fixtureA = fA;
		m_fixtureB = fB;

		m_indexA = indexA;
		m_indexB = indexB;

		m_manifold.pointCount = 0;

		m_prev = null;
		m_next = null;

		m_nodeA.contact = null;
		m_nodeA.prev = null;
		m_nodeA.next = null;
		m_nodeA.other = null;

		m_nodeB.contact = null;
		m_nodeB.prev = null;
		m_nodeB.next = null;
		m_nodeB.other = null;

		m_toiCount = 0;
		m_friction = Contact.mixFriction(fA.m_friction, fB.m_friction);
		m_restitution = Contact.mixRestitution(fA.m_restitution, fB.m_restitution);

		m_tangentSpeed = 0;
	}

	/** Get the contact manifold. Do not set the point count to zero. Instead call Disable. */
	public Manifold getManifold () {
		return m_manifold;
	}

	/** Get the world manifold. */
	public void getWorldManifold (WorldManifold worldManifold) {
		final Body bodyA = m_fixtureA.getBody();
		final Body bodyB = m_fixtureB.getBody();
		final Shape shapeA = m_fixtureA.getShape();
		final Shape shapeB = m_fixtureB.getShape();

		worldManifold.initialize(m_manifold, bodyA.getTransform(), shapeA.m_radius, bodyB.getTransform(), shapeB.m_radius);
	}

	/** Is this contact touching
	 * 
	 * @return */
	public boolean isTouching () {
		return (m_flags & TOUCHING_FLAG) == TOUCHING_FLAG;
	}

	/** Enable/disable this contact. This can be used inside the pre-solve contact listener. The contact is only disabled for the
	 * current time step (or sub-step in continuous collisions).
	 * 
	 * @param flag */
	public void setEnabled (boolean flag) {
		if (flag) {
			m_flags |= ENABLED_FLAG;
		} else {
			m_flags &= ~ENABLED_FLAG;
		}
	}

	/** Has this contact been disabled?
	 * 
	 * @return */
	public boolean isEnabled () {
		return (m_flags & ENABLED_FLAG) == ENABLED_FLAG;
	}

	/** Get the next contact in the world's contact list.
	 * 
	 * @return */
	public Contact getNext () {
		return m_next;
	}

	/** Get the first fixture in this contact.
	 * 
	 * @return */
	public Fixture getFixtureA () {
		return m_fixtureA;
	}

	public int getChildIndexA () {
		return m_indexA;
	}

	/** Get the second fixture in this contact.
	 * 
	 * @return */
	public Fixture getFixtureB () {
		return m_fixtureB;
	}

	public int getChildIndexB () {
		return m_indexB;
	}

	public void setFriction (float friction) {
		m_friction = friction;
	}

	public float getFriction () {
		return m_friction;
	}

	public void resetFriction () {
		m_friction = Contact.mixFriction(m_fixtureA.m_friction, m_fixtureB.m_friction);
	}

	public void setRestitution (float restitution) {
		m_restitution = restitution;
	}

	public float getRestitution () {
		return m_restitution;
	}

	public void resetRestitution () {
		m_restitution = Contact.mixRestitution(m_fixtureA.m_restitution, m_fixtureB.m_restitution);
	}

	public void setTangentSpeed (float speed) {
		m_tangentSpeed = speed;
	}

	public float getTangentSpeed () {
		return m_tangentSpeed;
	}

	public abstract void evaluate (Manifold manifold, Transform xfA, Transform xfB);

	/** Flag this contact for filtering. Filtering will occur the next time step. */
	public void flagForFiltering () {
		m_flags |= FILTER_FLAG;
	}

	// djm pooling
	private final Manifold oldManifold = new Manifold();

	public void update (ContactListener listener) {

		oldManifold.set(m_manifold);

		// Re-enable this contact.
		m_flags |= ENABLED_FLAG;

		boolean touching = false;
		boolean wasTouching = (m_flags & TOUCHING_FLAG) == TOUCHING_FLAG;

		boolean sensorA = m_fixtureA.isSensor();
		boolean sensorB = m_fixtureB.isSensor();
		boolean sensor = sensorA || sensorB;

		Body bodyA = m_fixtureA.getBody();
		Body bodyB = m_fixtureB.getBody();
		Transform xfA = bodyA.getTransform();
		Transform xfB = bodyB.getTransform();
		// log.debug("TransformA: "+xfA);
		// log.debug("TransformB: "+xfB);

		if (sensor) {
			Shape shapeA = m_fixtureA.getShape();
			Shape shapeB = m_fixtureB.getShape();
			touching = pool.getCollision().testOverlap(shapeA, m_indexA, shapeB, m_indexB, xfA, xfB);

			// Sensors don't generate manifolds.
			m_manifold.pointCount = 0;
		} else {
			evaluate(m_manifold, xfA, xfB);
			touching = m_manifold.pointCount > 0;

			// Match old contact ids to new contact ids and copy the
			// stored impulses to warm start the solver.
			for (int i = 0; i < m_manifold.pointCount; ++i) {
				ManifoldPoint mp2 = m_manifold.points[i];
				mp2.normalImpulse = 0.0f;
				mp2.tangentImpulse = 0.0f;
				ContactID id2 = mp2.id;

				for (int j = 0; j < oldManifold.pointCount; ++j) {
					ManifoldPoint mp1 = oldManifold.points[j];

					if (mp1.id.isEqual(id2)) {
						mp2.normalImpulse = mp1.normalImpulse;
						mp2.tangentImpulse = mp1.tangentImpulse;
						break;
					}
				}
			}

			if (touching != wasTouching) {
				bodyA.setAwake(true);
				bodyB.setAwake(true);
			}
		}

		if (touching) {
			m_flags |= TOUCHING_FLAG;
		} else {
			m_flags &= ~TOUCHING_FLAG;
		}

		if (listener == null) {
			return;
		}

		if (wasTouching == false && touching == true) {
			listener.beginContact(this);
		}

		if (wasTouching == true && touching == false) {
			listener.endContact(this);
		}

		if (sensor == false && touching) {
			listener.preSolve(this, oldManifold);
		}
	}

	/** Friction mixing law. The idea is to allow either fixture to drive the restitution to zero. For example, anything slides on
	 * ice.
	 * 
	 * @param friction1
	 * @param friction2
	 * @return */
	public static final float mixFriction (float friction1, float friction2) {
		return MathUtils.sqrt(friction1 * friction2);
	}

	/** Restitution mixing law. The idea is allow for anything to bounce off an inelastic surface. For example, a superball bounces
	 * on anything.
	 * 
	 * @param restitution1
	 * @param restitution2
	 * @return */
	public static final float mixRestitution (float restitution1, float restitution2) {
		return restitution1 > restitution2 ? restitution1 : restitution2;
	}
}
