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

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Shape.Type;
import com.badlogic.gdx.utils.GdxRuntimeException;

public class Fixture {
	/*JNI
#include <Box2D/Box2D.h>
	 */
	/** body **/
	private Body body;

	/** the address of the fixture **/
	protected long addr;

	/** the shape, initialized lazy **/
	protected Shape shape;

	/** user specified data **/
	protected Object userData;

	/** Constructs a new fixture
	 * @param addr the address of the fixture */
	protected Fixture (Body body, long addr) {
		this.body = body;
		this.addr = addr;
	}

	protected void reset (Body body, long addr) {
		this.body = body;
		this.addr = addr;
		this.shape = null;
		this.userData = null;
	}

	/** Get the type of the child shape. You can use this to down cast to the concrete shape.
	 * @return the shape type. */
	public Type getType () {
		int type = jniGetType(addr);
		switch (type) {
		case 0:
			return Type.Circle;
		case 1:
			return Type.Edge;
		case 2:
			return Type.Polygon;
		case 3:
			return Type.Chain;
		default:
			throw new GdxRuntimeException("Unknown shape type!");
		}
	}

	private native int jniGetType (long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		b2Shape::Type type = fixture->GetType();
		switch( type )
		{
		case b2Shape::e_circle: return 0;
		case b2Shape::e_edge: return 1;
		case b2Shape::e_polygon: return 2;
		case b2Shape::e_chain: return 3;
		default:
			return -1;
		}
	*/

	/** Returns the shape of this fixture */
	public Shape getShape () {
		if (shape == null) {
			long shapeAddr = jniGetShape(addr);
			if (shapeAddr == 0) throw new GdxRuntimeException("Null shape address!");
			int type = Shape.jniGetType(shapeAddr);

			switch (type) {
			case 0:
				shape = new CircleShape(shapeAddr);
				break;
			case 1:
				shape = new EdgeShape(shapeAddr);
				break;
			case 2:
				shape = new PolygonShape(shapeAddr);
				break;
			case 3:
				shape = new ChainShape(shapeAddr);
				break;
			default:
				throw new GdxRuntimeException("Unknown shape type!");
			}
		}

		return shape;
	}

	private native long jniGetShape (long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return (jlong)fixture->GetShape();
	*/

	/** Set if this fixture is a sensor. */
	public void setSensor (boolean sensor) {
		jniSetSensor(addr, sensor);
	}

	private native void jniSetSensor (long addr, boolean sensor); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->SetSensor(sensor);
	*/

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

	private native boolean jniIsSensor (long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->IsSensor();
	*/

	/** Set the contact filtering data. This will not update contacts until the next time step when either parent body is active and
	 * awake. This automatically calls Refilter. */
	public void setFilterData (Filter filter) {
		jniSetFilterData(addr, filter.categoryBits, filter.maskBits, filter.groupIndex);
	}

	private native void jniSetFilterData (long addr, short categoryBits, short maskBits, short groupIndex); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		b2Filter filter;
		filter.categoryBits = categoryBits;
		filter.maskBits = maskBits;
		filter.groupIndex = groupIndex;
		fixture->SetFilterData(filter);
	*/

	/** Get the contact filtering data. */
	private final short[] tmp = new short[3];
	private final Filter filter = new Filter();

	public Filter getFilterData () {
		jniGetFilterData(addr, tmp);
		filter.maskBits = tmp[0];
		filter.categoryBits = tmp[1];
		filter.groupIndex = tmp[2];
		return filter;
	}

	private native void jniGetFilterData (long addr, short[] filter); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		unsigned short* filterOut = (unsigned short*)filter;
		b2Filter f = fixture->GetFilterData();
		filterOut[0] = f.maskBits;
		filterOut[1] = f.categoryBits;
		filterOut[2] = f.groupIndex;
	*/

	/** Call this if you want to establish collision that was previously disabled by b2ContactFilter::ShouldCollide. */
	public void refilter () {
		jniRefilter(addr);
	}

	private native void jniRefilter (long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->Refilter();
	*/

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

	/** Test a point for containment in this fixture.
	 * @param p a point in world coordinates. */
	public boolean testPoint (Vector2 p) {
		return jniTestPoint(addr, p.x, p.y);
	}

	/** Test a point for containment in this fixture.
	 * @param x the x-coordinate
	 * @param y the y-coordinate */
	public boolean testPoint (float x, float y) {
		return jniTestPoint(addr, x, y);
	}

	private native boolean jniTestPoint (long addr, float x, float y); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->TestPoint( b2Vec2( x, y ) );
	*/

// const b2Body* GetBody() const;
//
// /// Get the next fixture in the parent body's fixture list.
// /// @return the next shape.
// b2Fixture* GetNext();
// const b2Fixture* GetNext() const;
//
// /// Get the user data that was assigned in the fixture definition. Use this to
// /// store your application specific data.
// void* GetUserData() const;
//
// /// Set the user data. Use this to store your application specific data.
// void SetUserData(void* data);
//
// /// Cast a ray against this shape.
// /// @param output the ray-cast results.
// /// @param input the ray-cast input parameters.
// bool RayCast(b2RayCastOutput* output, const b2RayCastInput& input) const;
//
// /// 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. This operation
// /// may be expensive.
// void GetMassData(b2MassData* massData) const;

	/** Set the density of this fixture. This will _not_ automatically adjust the mass of the body. You must call
	 * b2Body::ResetMassData to update the body's mass. */
	public void setDensity (float density) {
		jniSetDensity(addr, density);
	}

	private native void jniSetDensity (long addr, float density); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->SetDensity(density);
	*/

	/** Get the density of this fixture. */
	public float getDensity () {
		return jniGetDensity(addr);
	}

	private native float jniGetDensity (long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->GetDensity();
	*/

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

	private native float jniGetFriction (long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->GetFriction();
	*/

	/** Set the coefficient of friction. */
	public void setFriction (float friction) {
		jniSetFriction(addr, friction);
	}

	private native void jniSetFriction (long addr, float friction); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->SetFriction(friction);
	*/

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

	private native float jniGetRestitution (long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->GetRestitution();
	*/

	/** Set the coefficient of restitution. */
	public void setRestitution (float restitution) {
		jniSetRestitution(addr, restitution);
	}

	private native void jniSetRestitution (long addr, float restitution); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->SetRestitution(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.
// const b2AABB& GetAABB() const;

	/** Sets custom user data. */
	public void setUserData (Object userData) {
		this.userData = userData;
	}

	/** @return custom user data */
	public Object getUserData () {
		return userData;
	}
}
