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

import java.util.Arrays;

import com.badlogic.gdx.graphics.g3d.model.MeshPart;
import com.badlogic.gdx.graphics.g3d.model.Node;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.physics.bullet.collision.btBvhTriangleMeshShape;
import com.badlogic.gdx.physics.bullet.collision.btCollisionShape;
import com.badlogic.gdx.physics.bullet.collision.btCompoundShape;
import com.badlogic.gdx.physics.bullet.linearmath.LinearMath;
import com.badlogic.gdx.physics.bullet.linearmath.LinearMathConstants;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.SharedLibraryLoader;

public class Bullet {
	/** The version of the Bullet library used by this wrapper. */
	public final static int VERSION = LinearMathConstants.BT_BULLET_VERSION;

	protected static boolean useRefCounting = false;
	protected static boolean enableLogging = true;

	/** Loads the native Bullet native library and initializes the gdx-bullet extension. Must be called before any of the bullet
	 * classes/methods can be used. */
	public static void init () {
		init(false);
	}

	/** Loads the native Bullet native library and initializes the gdx-bullet extension. Must be called before any of the bullet
	 * classes/methods can be used.
	 * @param useRefCounting Whether to use reference counting, causing object to be destroyed when no longer referenced. You must
	 *           use {@link BulletBase#obtain()} and {@link BulletBase#release()} when using reference counting. */
	public static void init (boolean useRefCounting) {
		init(useRefCounting, true);
	}

	/** Loads the native Bullet native library and initializes the gdx-bullet extension. Must be called before any of the bullet
	 * classes/methods can be used.
	 * @param useRefCounting Whether to use reference counting, causing object to be destroyed when no longer referenced. You must
	 *           use {@link BulletBase#obtain()} and {@link BulletBase#release()} when using reference counting.
	 * @param logging Whether to log an error on potential errors in the application. */
	public static void init (boolean useRefCounting, boolean logging) {
		Bullet.useRefCounting = useRefCounting;
		Bullet.enableLogging = logging;
		new SharedLibraryLoader().load("gdx-bullet");
		final int version = LinearMath.btGetVersion();
		if (version != VERSION) throw new GdxRuntimeException(
			"Bullet binaries version (" + version + ") does not match source version (" + VERSION + ")");
	}

	protected static class ShapePart {
		public Array<MeshPart> parts = new Array<MeshPart>();
		public Matrix4 transform = new Matrix4();
	}

	private final static Pool<ShapePart> shapePartPool = new Pool<ShapePart>() {
		@Override
		protected ShapePart newObject () {
			return new ShapePart();
		}
	};
	private final static Array<ShapePart> shapePartArray = new Array<ShapePart>();

	private final static Matrix4 idt = new Matrix4();
	private final static Matrix4 tmpM = new Matrix4();

	public static void getShapeParts (final Node node, final boolean applyTransform, final Array<ShapePart> out, final int offset,
		final Pool<ShapePart> pool) {
		final Matrix4 transform = applyTransform ? node.localTransform : idt;
		if (node.parts.size > 0) {
			ShapePart part = null;
			for (int i = offset, n = out.size; i < n; i++) {
				final ShapePart p = out.get(i);
				if (Arrays.equals(p.transform.val, transform.val)) {
					part = p;
					break;
				}
			}
			if (part == null) {
				part = pool.obtain();
				part.parts.clear();
				part.transform.set(transform);
				out.add(part);
			}
			for (int i = 0, n = node.parts.size; i < n; i++)
				part.parts.add(node.parts.get(i).meshPart);
		}
		if (node.hasChildren()) {
			final boolean transformed = applyTransform && !Arrays.equals(transform.val, idt.val);
			final int o = transformed ? out.size : offset;
			getShapeParts(node.getChildren(), out, o, pool);
			if (transformed) {
				for (int i = o, n = out.size; i < n; i++) {
					final ShapePart part = out.get(i);
					tmpM.set(part.transform);
					part.transform.set(transform).mul(tmpM);
				}
			}
		}
	}

	public static <T extends Node> void getShapeParts (final Iterable<T> nodes, final Array<ShapePart> out, final int offset,
		final Pool<ShapePart> pool) {
		for (T node : nodes)
			getShapeParts(node, true, out, offset, pool);
	}

	public static btCollisionShape obtainStaticNodeShape (final Node node, final boolean applyTransform) {
		getShapeParts(node, applyTransform, shapePartArray, 0, shapePartPool);
		btCollisionShape result = obtainStaticShape(shapePartArray);
		shapePartPool.freeAll(shapePartArray);
		shapePartArray.clear();
		return result;
	}

	/** Obtain a {@link btCollisionShape} based on the specified nodes, which can be used for a static body but not for a dynamic
	 * body. Depending on the specified nodes the result will be either a {@link btBvhTriangleMeshShape} or a
	 * {@link btCompoundShape} of multiple btBvhTriangleMeshShape's. Where possible, the same btBvhTriangleMeshShape will be reused
	 * if multiple nodes use the same (mesh) part. The node transformation (translation and rotation) will be included, but scaling
	 * will be ignored.
	 * @param nodes The nodes for which to obtain a node, typically this would be: `model.nodes`.
	 * @return The obtained shape, if you're using reference counting then you can release the shape when no longer needed. */
	public static btCollisionShape obtainStaticNodeShape (final Array<Node> nodes) {
		getShapeParts(nodes, shapePartArray, 0, shapePartPool);
		btCollisionShape result = obtainStaticShape(shapePartArray);
		shapePartPool.freeAll(shapePartArray);
		shapePartArray.clear();
		return result;
	}

	public static btCollisionShape obtainStaticShape (final Array<ShapePart> parts) {
		if (parts.size == 0) return null;
		if (parts.size == 1 && Arrays.equals(parts.get(0).transform.val, idt.val))
			return btBvhTriangleMeshShape.obtain(parts.get(0).parts);
		btCompoundShape result = new btCompoundShape();
		result.obtain();
		for (int i = 0, n = parts.size; i < n; i++) {
			final btBvhTriangleMeshShape shape = btBvhTriangleMeshShape.obtain(parts.get(i).parts);
			result.addChildShape(parts.get(i).transform, shape);
			shape.release();
		}
		return result;
	}
}
