/*******************************************************************************
 * 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; // NO_UCD
  // 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 = 0;

    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;
  }
}
