package org.oscim.utils;

public class TessJNI {
    private long inst;

    public TessJNI() {
        inst = newTess(0);
    }

    public TessJNI(int bucketSize) {
        inst = newTess(bucketSize);
    }

    @Override
    protected void finalize() {
        dispose();
    }

    public void dispose() {
        if (inst != 0) {
            freeTess(inst);
            inst = 0;
        }
    }

    protected long instance() {
        return inst;
    }

    /**
     * See OpenGL Red Book for description of the winding rules
     * http://www.glprogramming.com/red/chapter11.html
     */
    public static final class WindingRule {
        public static final int ODD = 0;
        public static final int NONZERO = 1;
        public static final int POSITIVE = 2;
        public static final int NEGATIVE = 3;
        public static final int ABS_GEQ_TWO = 4;
    }

    public static final class ElementType {
        public static final int POLYGONS = 0;
        public static final int CONNECTED_POLYGONS = 1;
        public static final int BOUNDARY_CONTOURS = 2;
    }

    public void addContour2D(float[] points) {
        addContour2D(points, 0, points.length >> 1);
    }

    public void addContour2D(float[] points, int offset, int length) {
        if (length < 6)
            return;
        if ((length % 2 != 0) || (offset % 2 != 0) || (points.length >> 1) < (offset + length))
            throw new IllegalArgumentException("Invalid input: length:" + length
                    + ", offset:" + offset
                    + ", points.length:" + points.length);
        addContour(inst, 2, points, 8, offset, length);
    }

    public void addContour2D(int[] index, float[] contour) {
        addMultiContour2D(inst, index, contour, 0, index.length);
    }

    public void addContour2D(int[] index, float[] contour, int idxStart, int idxEnd) {
        addMultiContour2D(inst, index, contour, idxStart, idxEnd);
    }

    public boolean tesselate() {
        return tessContour2D(inst,
                TessJNI.WindingRule.POSITIVE,
                TessJNI.ElementType.POLYGONS,
                3, 2) == 1;
    }

    public boolean tesselate(int windingRule, int elementType) {
        return tessContour2D(inst, windingRule, elementType, 3, 2) == 1;
    }

    public int getVertexCount() {
        return getVertexCount(inst);
    }

    public int getElementCount() {
        return getElementCount(inst);
    }

    public void getVertices(float[] out, int offset, int length) {
        getVertices(inst, out, offset, length);
    }

    public void getVertices(short[] out, int offset, int length, float scale) {
        getVerticesS(inst, out, offset, length, scale);
    }

    public void getElements(int[] out, int offset, int length) {
        getElements(inst, out, offset, length);
    }

    public void getElements(short[] out, int offset, int length) {
        getElementsS(inst, out, offset, length);
    }

    public void getVertexIndices(int[] out, int offset, int length) {
        getVertexIndices(inst, out, offset, length);
    }

    public void getElementsWithInputVertexIds(short[] dst, int dstOffset, int offset, int length) {
        getElementsWithInputVertexIds(inst, dst, dstOffset, offset, length);
    }

    // @formatter:off
    /*JNI
    #include <tesselator.h>
    #include <string.h>
    #include <stdlib.h>
    void* heapAlloc( void* userData, unsigned int size ){
        TESS_NOTUSED( userData );
        return malloc( size );
    }
    void* heapRealloc( void *userData, void* ptr, unsigned int size ){
        TESS_NOTUSED( userData );
        return realloc( ptr, size );
    }
    void heapFree( void* userData, void* ptr ){
        TESS_NOTUSED( userData );
        free( ptr );
    }
     */
    static native long newTess(int size); /* {
        if (size <= 0)
           return (long)tessNewTess(0);
        if (size > 10)
            size = 10;
        TESSalloc ma;
        memset(&ma, 0, sizeof(ma));
        ma.memalloc = heapAlloc;
        ma.memfree = heapFree;
        ma.memrealloc = heapRealloc;
        //ma.userData = (void*)&allocated;
        ma.meshEdgeBucketSize = 2 << size;   // 512
        ma.meshVertexBucketSize = 2 << size; // 512
        ma.meshFaceBucketSize = 1 << size;     // 256
        ma.dictNodeBucketSize = 2 << size;     // 512
        ma.regionBucketSize = 1 << size;     // 256
        ma.extraVertices = 8;
        //ma.extraVertices = 256;
        return (long)tessNewTess(&ma);
    } */

    static native void freeTess(long inst); /* {
        tessDeleteTess((TESStesselator*) inst);
    } */

    /**
     * Adds a contour to be tesselated.
     * The type of the vertex coordinates is assumed to be TESSreal.
     *
     * @param tess    - pointer to tesselator object.
     * @param size    - number of coordinates per vertex. Must be 2 or 3.
     * @param pointer - pointer to the first coordinate of the first vertex in the array.
     * @param stride  - defines offset in bytes between consecutive vertices.
     * @param count   - number of vertices in contour.
     */
    static native void addContour(long inst, int size, float[] contour, int stride, int offset, int count);/* {
        tessAddContour((TESStesselator*) inst, size, contour + (offset * stride), stride, count);
    } */

    static native void addMultiContour2D(long inst, int[] index, float[] contour, int idxStart, int idxCount);/* {
        TESStesselator* tess = (TESStesselator*) inst;
        int offset = 0;
        // start at 0 to get the correct offset in contour..
        for (int i = 0; i < idxStart + idxCount; i++){
            int len = index[i];
            if ((len % 2 != 0) || (len < 0))
                break;
            if (len < 6 || i < idxStart) {
                offset += len;
                continue;
            }
            tessAddContour(tess, 2, contour + offset, 8, len >> 1);
            offset += len;
        }
    } */

    /**
     * Tesselate contours.
     *
     * @param tess        - pointer to tesselator object.
     * @param windingRule - winding rules used for tesselation, must be one of TessWindingRule.
     * @param elementType - defines the tesselation result element type, must be one of TessElementType.
     * @param polySize    - defines maximum vertices per polygons if output is polygons.
     * @param vertexSize  - defines the number of coordinates in tesselation result vertex, must be 2 or 3.
     * @param normal      - defines the normal of the input contours, of null the normal is calculated automatically.
     * @return 1 if succeed, 0 if failed.
     */
    static native int tessContour2D(long inst, int windingRule, int elementType, int polySize, int vertexSize);/*{
        return tessTesselate((TESStesselator*) inst, windingRule, elementType, polySize, vertexSize, 0);
    } */

    static native int getVertexCount(long inst); /*{
        return tessGetVertexCount((TESStesselator*) inst);
    }*/

    /**
     * Returns pointer to first coordinate of first vertex.
     */
    static native boolean getVertices(long inst, float[] out, int offset, int length);/*{
        const TESSreal* vertices = tessGetVertices((TESStesselator*) inst);
        if (!vertices)
            return 0;
        memcpy(out, vertices + offset, length * sizeof(TESSreal));
        return 1;
    }*/

    /**
     * Returns pointer to first coordinate of first vertex.
     */
    static native void getVerticesS(long inst, short[] out, int offset, int length, float scale);/*{
        const TESSreal* vertices = tessGetVertices((TESStesselator*) inst);
        for(int i = 0; i < length; i++)
            out[i] = (short)(vertices[offset++] * scale + 0.5f);
    }*/

    /**
     * Returns pointer to first vertex index.
     * <p/>
     * Vertex indices can be used to map the generated vertices to the original vertices.
     * Every point added using tessAddContour() will get a new index starting at 0.
     * New vertices generated at the intersections of segments are assigned value TESS_UNDEF.
     */
    static native boolean getVertexIndices(long inst, int[] out, int offset, int length);/* {
        const TESSindex* indices = tessGetVertexIndices((TESStesselator*) inst);
        if (!indices)
            return 0;
        memcpy(out, indices + offset, length * sizeof(TESSindex));
        return 1;
    } */

    /**
     * Returns number of elements in the tesselated output.
     */
    static native int getElementCount(long inst);/*{
        return tessGetElementCount((TESStesselator*) inst);
    }*/

    /**
     * Returns pointer to the first element.
     */
    static native boolean getElements(long inst, int[] out, int offset, int length);/*{
        const TESSindex* elements = tessGetElements((TESStesselator*) inst);
        if (!elements)
            return 0;
        memcpy(out, elements + offset, length * sizeof(TESSindex));
        return 1;
    }*/

    /**
     * Returns pointer to the first element.
     */
    static native void getElementsS(long inst, short[] out, int offset, int length);/*{
        const TESSindex* elements = tessGetElements((TESStesselator*) inst);
        for(int i = 0; i < length; i++)
            out[i] = (short)elements[offset++];
    }*/

    /**
     * Returns list of triangles indices (or to the first element of convex polygons).
     */
    static native void getElementsWithInputVertexIds(long inst, short[] out, int dstOffset, int offset, int length);/*{
        const TESSindex* elements = tessGetElements((TESStesselator*) inst);
        const TESSindex* indices = tessGetVertexIndices((TESStesselator*) inst);
        for(int i = 0; i < length; i++)
            out[dstOffset++] = (short)(indices[elements[offset++]]);
    }*/
}