/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * 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.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * 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 OWNER 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 com.jme3.bullet.collision.shapes;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.bullet.util.NativeMeshUtil;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.BufferUtils;

/**
 * Basic mesh collision shape
 *
 * @author normenhansen
 */
public class MeshCollisionShape extends CollisionShape {

    private static final String VERTEX_BASE = "vertexBase";
    private static final String TRIANGLE_INDEX_BASE = "triangleIndexBase";
    private static final String TRIANGLE_INDEX_STRIDE = "triangleIndexStride";
    private static final String VERTEX_STRIDE = "vertexStride";
    private static final String NUM_TRIANGLES = "numTriangles";
    private static final String NUM_VERTICES = "numVertices";
    private static final String NATIVE_BVH = "nativeBvh";
    protected int numVertices, numTriangles, vertexStride, triangleIndexStride;
    protected ByteBuffer triangleIndexBase, vertexBase;
    protected long meshId = 0;
    protected long nativeBVHBuffer = 0;
    private boolean memoryOptimized;

    public MeshCollisionShape() {
    }

    /**
     * Creates a collision shape from the given Mesh. 
     * Default behavior, more optimized for memory usage.
     *
     * @param mesh
     */
    public MeshCollisionShape(Mesh mesh) {
        this(mesh, true);
    }

    /**
     * Creates a collision shape from the given Mesh.
     * <code>memoryOptimized</code> determines if optimized instead of 
     * quantized BVH will be used.
     * Internally, <code>memoryOptimized</code> BVH is slower to calculate (~4x) 
     * but also smaller (~0.5x). 
     * It is preferable to use the memory optimized version and then serialize
     * the resulting MeshCollisionshape as this will also save the
     * generated BVH. 
     * An exception can be procedurally / generated collision shapes, where
     * the generation time is more of a concern
     *
     * @param mesh the Mesh to use
     * @param memoryOptimized True to generate a memory optimized BVH,
     * false to generate quantized BVH.
     */
    public MeshCollisionShape(final Mesh mesh, final boolean memoryOptimized) {
        this.memoryOptimized = memoryOptimized;
        this.createCollisionMesh(mesh);
    }

    /**
     * Advanced constructor, usually you don’t want to use this, but the Mesh
     * based one. Passing false values can lead to a crash, use at own risk
     *
     * This constructor bypasses all copy logic normally used, this allows for
     * faster bullet shape generation when using procedurally generated Meshes.
     *
     *
     * @param indices the raw index buffer
     * @param vertices the raw vertex buffer
     * @param memoryOptimized use quantisize BVH, uses less memory, but slower
     */
    public MeshCollisionShape(ByteBuffer indices, ByteBuffer vertices, boolean memoryOptimized) {
        this.triangleIndexBase = indices;
        this.vertexBase = vertices;
        this.numVertices = vertices.limit() / 4 / 3;
        this.numTriangles = this.triangleIndexBase.limit() / 4 / 3;
        this.vertexStride = 12;
        this.triangleIndexStride = 12;
        this.memoryOptimized = memoryOptimized;
        this.createShape(true);
    }
    
    private void createCollisionMesh(Mesh mesh) {
        this.triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4);
        this.vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4);
        this.numVertices = mesh.getVertexCount();
        this.vertexStride = 12; // 3 verts * 4 bytes per.
        this.numTriangles = mesh.getTriangleCount();
        this.triangleIndexStride = 12; // 3 index entries * 4 bytes each.

        IndexBuffer indices = mesh.getIndicesAsList();
        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
        vertices.rewind();

        int verticesLength = mesh.getVertexCount() * 3;
        for (int i = 0; i < verticesLength; i++) {
            float tempFloat = vertices.get();
            vertexBase.putFloat(tempFloat);
        }

        int indicesLength = mesh.getTriangleCount() * 3;
        for (int i = 0; i < indicesLength; i++) {
            triangleIndexBase.putInt(indices.get(i));
        }
        vertices.rewind();
        vertices.clear();

        this.createShape(true);
    }

    @Override
    public void write(final JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule capsule = ex.getCapsule(this);
        capsule.write(numVertices, MeshCollisionShape.NUM_VERTICES, 0);
        capsule.write(numTriangles, MeshCollisionShape.NUM_TRIANGLES, 0);
        capsule.write(vertexStride, MeshCollisionShape.VERTEX_STRIDE, 0);
        capsule.write(triangleIndexStride, MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0);

        triangleIndexBase.position(0);
        byte[] triangleIndexBasearray = new byte[triangleIndexBase.limit()];
        triangleIndexBase.get(triangleIndexBasearray);
        capsule.write(triangleIndexBasearray, MeshCollisionShape.TRIANGLE_INDEX_BASE, null);

        vertexBase.position(0);
        byte[] vertexBaseArray = new byte[vertexBase.limit()];
        vertexBase.get(vertexBaseArray);
        capsule.write(vertexBaseArray, MeshCollisionShape.VERTEX_BASE, null);

        if (memoryOptimized) {
            byte[] data = saveBVH(objectId);
            capsule.write(data, MeshCollisionShape.NATIVE_BVH, null);
        }
    }

    @Override
    public void read(final JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule capsule = im.getCapsule(this);
        this.numVertices = capsule.readInt(MeshCollisionShape.NUM_VERTICES, 0);
        this.numTriangles = capsule.readInt(MeshCollisionShape.NUM_TRIANGLES, 0);
        this.vertexStride = capsule.readInt(MeshCollisionShape.VERTEX_STRIDE, 0);
        this.triangleIndexStride = capsule.readInt(MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0);

        this.triangleIndexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.TRIANGLE_INDEX_BASE, null));
        this.vertexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.VERTEX_BASE, null));

        byte[] nativeBvh = capsule.readByteArray(MeshCollisionShape.NATIVE_BVH, null);
        if (nativeBvh == null) {
            // Either using non memory optimized BVH or old J3O file
            memoryOptimized = false;
            createShape(true);
        } else {
            // Using memory optimized BVH, load from J3O, then assign it.
            memoryOptimized = true;
            createShape(false);
            nativeBVHBuffer = setBVH(nativeBvh, this.objectId);
        }
    }

    private void createShape(boolean buildBvt) {
        this.meshId = NativeMeshUtil.createTriangleIndexVertexArray(this.triangleIndexBase, this.vertexBase, this.numTriangles, this.numVertices, this.vertexStride, this.triangleIndexStride);
        Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Mesh {0}", Long.toHexString(this.meshId));
        this.objectId = createShape(memoryOptimized, buildBvt, this.meshId);
        Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(this.objectId));
        this.setScale(this.scale);
        this.setMargin(this.margin);
    }

    /**
     * returns the pointer to the native buffer used by the in place
     * de-serialized shape, must be freed when not used anymore!
     */
    private native long setBVH(byte[] buffer, long objectid);
    
    private native byte[] saveBVH(long objectId);
    
    private native long createShape(boolean memoryOptimized, boolean buildBvt, long meshId);

    @Override
    public void finalize() throws Throwable {
        super.finalize();
        Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(this.meshId));
        if (this.meshId > 0) {
            this.finalizeNative(this.meshId, this.nativeBVHBuffer);
        }
    }

    private native void finalizeNative(long objectId, long nativeBVHBuffer);
}