/*
 * 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 jme3tools.converters.model.strip;

import java.util.Arrays;

/**
 * To use, call generateStrips method, passing your triangle index list and 
 * then construct geometry/render resulting PrimitiveGroup objects.
 * Features:
 * <ul> 
 * <li>generates strips from arbitrary geometry. 
 * <li>flexibly optimizes for post TnL vertex caches (16 on GeForce1/2, 24 on GeForce3). 
 * <li>can stitch together strips using degenerate triangles, or not. 
 * <li>can output lists instead of strips. 
 * <li>can optionally throw excessively small strips into a list instead. 
 * <li>can remap indices to improve spatial locality in your vertex buffers.
 * </ul>
 * On cache sizes: Note that it's better to UNDERESTIMATE the cache size
 * instead of OVERESTIMATING. So, if you're targetting GeForce1, 2, and 3, be
 * conservative and use the GeForce1_2 cache size, NOT the GeForce3 cache size.
 * This will make sure you don't "blow" the cache of the GeForce1 and 2. Also
 * note that the cache size you specify is the "actual" cache size, not the
 * "effective" cache size you may have heard about. This is 16 for GeForce1 and 2,
 * and 24 for GeForce3.
 * 
 * Credit goes to Curtis Beeson and Joe Demers for the basis for this
 * stripifier and to Jason Regier and Jon Stone at Blizzard for providing a
 * much cleaner version of CreateStrips().
 * 
 * Ported to java by Artur Biesiadowski <abies@pg.gda.pl> 
 */
public class TriStrip {

    public static final int CACHESIZE_GEFORCE1_2 = 16;
    public static final int CACHESIZE_GEFORCE3 = 24;

    int cacheSize = CACHESIZE_GEFORCE1_2;
    boolean bStitchStrips = true;
    int minStripSize = 0;
    boolean bListsOnly = false;

    /**
	 *  
	 */
    public TriStrip() {
        super();
    }

    /**
	 * If set to true, will return an optimized list, with no strips at all.
	 * Default value: false
	 */
    public void setListsOnly(boolean _bListsOnly) {
        bListsOnly = _bListsOnly;
    }

    /**
	 * Sets the cache size which the stripfier uses to optimize the data.
	 * Controls the length of the generated individual strips. This is the
	 * "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 You may
	 * want to play around with this number to tweak performance. Default
	 * value: 16
	 */
    public void setCacheSize(int _cacheSize) {
        cacheSize = _cacheSize;
    }

    /**
	 * bool to indicate whether to stitch together strips into one huge strip
	 * or not. If set to true, you'll get back one huge strip stitched together
	 * using degenerate triangles. If set to false, you'll get back a large
	 * number of separate strips. Default value: true
	 */
    public void setStitchStrips(boolean _bStitchStrips) {
        bStitchStrips = _bStitchStrips;
    }

    /**
	 * Sets the minimum acceptable size for a strip, in triangles. All strips
	 * generated which are shorter than this will be thrown into one big,
	 * separate list. Default value: 0
	 */
    public void setMinStripSize(int _minStripSize) {
        minStripSize = _minStripSize;
    }

    /**
	 * @param in_indices
	 *            input index list, the indices you would use to render
	 * @return array of optimized/stripified PrimitiveGroups
	 */
    public PrimitiveGroup[] generateStrips(int[] in_indices) {
        int numGroups = 0;
        PrimitiveGroup[] primGroups;
        //put data in format that the stripifier likes
        IntVec tempIndices = new IntVec();
        int maxIndex = 0;

        for (int i = 0; i < in_indices.length; i++) {
            tempIndices.add(in_indices[i]);
            if (in_indices[i] > maxIndex)
                maxIndex = in_indices[i];
        }

        StripInfoVec tempStrips = new StripInfoVec();
        FaceInfoVec tempFaces = new FaceInfoVec();

        Stripifier stripifier = new Stripifier();

        //do actual stripification
        stripifier.stripify(tempIndices, cacheSize, minStripSize, maxIndex, tempStrips, tempFaces);

        //stitch strips together
        IntVec stripIndices = new IntVec();
        int numSeparateStrips = 0;

        if (bListsOnly) {
            //if we're outputting only lists, we're done
            numGroups = 1;
            primGroups = new PrimitiveGroup[numGroups];
            primGroups[0] = new PrimitiveGroup();
            PrimitiveGroup[] primGroupArray = primGroups;

            //count the total number of indices
            int numIndices = 0;
            for (int i = 0; i < tempStrips.size(); i++) {
                numIndices += tempStrips.at(i).m_faces.size() * 3;
            }

            //add in the list
            numIndices += tempFaces.size() * 3;

            primGroupArray[0].type = PrimitiveGroup.PT_LIST;
            primGroupArray[0].indices = new int[numIndices];
            primGroupArray[0].numIndices = numIndices;

            //do strips
            int indexCtr = 0;
            for (int i = 0; i < tempStrips.size(); i++) {
                for (int j = 0; j < tempStrips.at(i).m_faces.size(); j++) {
                    //degenerates are of no use with lists
                    if (!Stripifier.isDegenerate(tempStrips.at(i).m_faces.at(j))) {
                        primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v0;
                        primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v1;
                        primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v2;
                    } else {
                        //we've removed a tri, reduce the number of indices
                        primGroupArray[0].numIndices -= 3;
                    }
                }
            }

            //do lists
            for (int i = 0; i < tempFaces.size(); i++) {
                primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v0;
                primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v1;
                primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v2;
            }
        } else {
            numSeparateStrips = stripifier.createStrips(tempStrips, stripIndices, bStitchStrips);

            //if we're stitching strips together, we better get back only one
            // strip from CreateStrips()
            
            //convert to output format
            numGroups = numSeparateStrips; //for the strips
            if (tempFaces.size() != 0)
                numGroups++; //we've got a list as well, increment
            primGroups = new PrimitiveGroup[numGroups];
            for (int i = 0; i < primGroups.length; i++) {
                primGroups[i] = new PrimitiveGroup();
            }

            PrimitiveGroup[] primGroupArray = primGroups;

            //first, the strips
            int startingLoc = 0;
            for (int stripCtr = 0; stripCtr < numSeparateStrips; stripCtr++) {
                int stripLength = 0;

                if (!bStitchStrips) {
                    int i;
                    //if we've got multiple strips, we need to figure out the
                    // correct length
                    for (i = startingLoc; i < stripIndices.size(); i++) {
                        if (stripIndices.get(i) == -1)
                            break;
                    }

                    stripLength = i - startingLoc;
                } else
                    stripLength = stripIndices.size();

                primGroupArray[stripCtr].type = PrimitiveGroup.PT_STRIP;
                primGroupArray[stripCtr].indices = new int[stripLength];
                primGroupArray[stripCtr].numIndices = stripLength;

                int indexCtr = 0;
                for (int i = startingLoc; i < stripLength + startingLoc; i++)
                    primGroupArray[stripCtr].indices[indexCtr++] = stripIndices.get(i);

                //we add 1 to account for the -1 separating strips
                //this doesn't break the stitched case since we'll exit the
                // loop
                startingLoc += stripLength + 1;
            }

            //next, the list
            if (tempFaces.size() != 0) {
                int faceGroupLoc = numGroups - 1; //the face group is the last
                // one
                primGroupArray[faceGroupLoc].type = PrimitiveGroup.PT_LIST;
                primGroupArray[faceGroupLoc].indices = new int[tempFaces.size() * 3];
                primGroupArray[faceGroupLoc].numIndices = tempFaces.size() * 3;
                int indexCtr = 0;
                for (int i = 0; i < tempFaces.size(); i++) {
                    primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v0;
                    primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v1;
                    primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v2;
                }
            }
        }
        return primGroups;
    }

    /**
	 * Function to remap your indices to improve spatial locality in your
	 * vertex buffer.
	 * 
	 * in_primGroups: array of PrimitiveGroups you want remapped numGroups:
	 * number of entries in in_primGroups numVerts: number of vertices in your
	 * vertex buffer, also can be thought of as the range of acceptable values
	 * for indices in your primitive groups. remappedGroups: array of remapped
	 * PrimitiveGroups
	 * 
	 * Note that, according to the remapping handed back to you, you must
	 * reorder your vertex buffer.
	 *  
	 */

    public static int[] remapIndices(int[] indices, int numVerts) {
        int[] indexCache = new int[numVerts];
        Arrays.fill(indexCache, -1);

        int numIndices = indices.length;
        int[] remappedIndices = new int[numIndices];
        int indexCtr = 0;
        for (int j = 0; j < numIndices; j++) {
            int cachedIndex = indexCache[indices[j]];
            if (cachedIndex == -1) //we haven't seen this index before
                {
                //point to "last" vertex in VB
                remappedIndices[j] = indexCtr;

                //add to index cache, increment
                indexCache[indices[j]] = indexCtr++;
            } else {
                //we've seen this index before
                remappedIndices[j] = cachedIndex;
            }
        }

        return remappedIndices;
    }

    public static void remapArrays(float[] vertexBuffer, int vertexSize, int[] indices) {
        int[] remapped = remapIndices(indices, vertexBuffer.length / vertexSize);
        float[] bufferCopy = vertexBuffer.clone();
        for (int i = 0; i < remapped.length; i++) {
            int from = indices[i] * vertexSize;
            int to = remapped[i] * vertexSize;
            for (int j = 0; j < vertexSize; j++) {
                vertexBuffer[to + j] = bufferCopy[from + j];
            }
        }

        System.arraycopy(remapped, 0, indices, 0, indices.length);
    }

}
