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

import com.jme3.math.Spline;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import java.util.Iterator;
import java.util.List;

/**
 * A
 * <code>Curve</code> is a visual, line-based representation of a {@link Spline}.
 * The underlying Spline will be sampled N times where N is the number of
 * segments as specified in the constructor. Each segment will represent one
 * line in the generated mesh.
 *
 * @author Nehon
 */
public class Curve extends Mesh {

    private Spline spline;
    private Vector3f temp = new Vector3f();

    /**
     * Serialization only. Do not use.
     */
    public Curve() {
    }

    /**
     * Create a curve mesh. Use a CatmullRom spline model that does not cycle.
     *
     * @param controlPoints the control points to use to create this curve
     * @param nbSubSegments the number of subsegments between the control points
     */
    public Curve(Vector3f[] controlPoints, int nbSubSegments) {
        this(new Spline(Spline.SplineType.CatmullRom, controlPoints, 10, false), nbSubSegments);
    }

    /**
     * Create a curve mesh from a Spline
     *
     * @param spline the spline to use
     * @param nbSubSegments the number of subsegments between the control points
     */
    public Curve(Spline spline, int nbSubSegments) {
        super();
        this.spline = spline;
        switch (spline.getType()) {
            case CatmullRom:
                this.createCatmullRomMesh(nbSubSegments);
                break;
            case Bezier:
                this.createBezierMesh(nbSubSegments);
                break;
            case Nurb:
                this.createNurbMesh(nbSubSegments);
                break;
            case Linear:
            default:
                this.createLinearMesh();
                break;
        }
    }

    private void createCatmullRomMesh(int nbSubSegments) {
        float[] array = new float[((spline.getControlPoints().size() - 1) * nbSubSegments + 1) * 3];
        short[] indices = new short[(spline.getControlPoints().size() - 1) * nbSubSegments * 2];
        int i = 0;
        int cptCP = 0;
        for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
            Vector3f vector3f = it.next();
            array[i] = vector3f.x;
            i++;
            array[i] = vector3f.y;
            i++;
            array[i] = vector3f.z;
            i++;
            if (it.hasNext()) {
                for (int j = 1; j < nbSubSegments; j++) {
                    spline.interpolate((float) j / nbSubSegments, cptCP, temp);
                    array[i] = temp.getX();
                    i++;
                    array[i] = temp.getY();
                    i++;
                    array[i] = temp.getZ();
                    i++;
                }
            }
            cptCP++;
        }

        i = 0;
        int k;
        for (int j = 0; j < (spline.getControlPoints().size() - 1) * nbSubSegments; j++) {
            k = j;
            indices[i] = (short) k;
            i++;
            k++;
            indices[i] = (short) k;
            i++;
        }

        this.setMode(Mesh.Mode.Lines);
        this.setBuffer(VertexBuffer.Type.Position, 3, array);
        this.setBuffer(VertexBuffer.Type.Index, 2, indices);//(spline.getControlPoints().size() - 1) * nbSubSegments * 2
        this.updateBound();
        this.updateCounts();
    }

    /**
     * This method creates the Bezier path for this curve.
     *
     * @param nbSubSegments amount of subsegments between position control
     * points
     */
    private void createBezierMesh(int nbSubSegments) {
        if (nbSubSegments == 0) {
            nbSubSegments = 1;
        }
        int centerPointsAmount = (spline.getControlPoints().size() + 2) / 3;

        //calculating vertices
        float[] array = new float[((centerPointsAmount - 1) * nbSubSegments + 1) * 3];
        int currentControlPoint = 0;
        List<Vector3f> controlPoints = spline.getControlPoints();
        int lineIndex = 0;
        for (int i = 0; i < centerPointsAmount - 1; ++i) {
            Vector3f vector3f = controlPoints.get(currentControlPoint);
            array[lineIndex++] = vector3f.x;
            array[lineIndex++] = vector3f.y;
            array[lineIndex++] = vector3f.z;
            for (int j = 1; j < nbSubSegments; ++j) {
                spline.interpolate((float) j / nbSubSegments, currentControlPoint, temp);
                array[lineIndex++] = temp.getX();
                array[lineIndex++] = temp.getY();
                array[lineIndex++] = temp.getZ();
            }
            currentControlPoint += 3;
        }
        Vector3f vector3f = controlPoints.get(currentControlPoint);
        array[lineIndex++] = vector3f.x;
        array[lineIndex++] = vector3f.y;
        array[lineIndex++] = vector3f.z;

        //calculating indexes
        int i = 0, k;
        short[] indices = new short[(centerPointsAmount - 1) * nbSubSegments << 1];
        for (int j = 0; j < (centerPointsAmount - 1) * nbSubSegments; ++j) {
            k = j;
            indices[i++] = (short) k;
            ++k;
            indices[i++] = (short) k;
        }

        this.setMode(Mesh.Mode.Lines);
        this.setBuffer(VertexBuffer.Type.Position, 3, array);
        this.setBuffer(VertexBuffer.Type.Index, 2, indices);
        this.updateBound();
        this.updateCounts();
    }

    /**
     * This method creates the Nurb path for this curve.
     *
     * @param nbSubSegments amount of subsegments between position control
     * points
     */
    private void createNurbMesh(int nbSubSegments) {
    	if(spline.getControlPoints() != null && spline.getControlPoints().size() > 0) {
    		if(nbSubSegments == 0) {
        		nbSubSegments = spline.getControlPoints().size() + 1;
        	} else {
        		nbSubSegments = spline.getControlPoints().size() * nbSubSegments + 1;
        	}
            float minKnot = spline.getMinNurbKnot();
            float maxKnot = spline.getMaxNurbKnot();
            float deltaU = (maxKnot - minKnot) / nbSubSegments;

            float[] array = new float[(nbSubSegments + 1) * 3];

            float u = minKnot;
            Vector3f interpolationResult = new Vector3f();
            for (int i = 0; i < array.length; i += 3) {
                spline.interpolate(u, 0, interpolationResult);
                array[i] = interpolationResult.x;
                array[i + 1] = interpolationResult.y;
                array[i + 2] = interpolationResult.z;
                u += deltaU;
            }

            //calculating indexes
            int i = 0;
            short[] indices = new short[nbSubSegments << 1];
            for (int j = 0; j < nbSubSegments; ++j) {
                indices[i++] = (short) j;
                indices[i++] = (short) (j + 1);
            }

            this.setMode(Mesh.Mode.Lines);
            this.setBuffer(VertexBuffer.Type.Position, 3, array);
            this.setBuffer(VertexBuffer.Type.Index, 2, indices);
            this.updateBound();
            this.updateCounts();
    	}
    }

    private void createLinearMesh() {
        float[] array = new float[spline.getControlPoints().size() * 3];
        short[] indices = new short[(spline.getControlPoints().size() - 1) * 2];
        int i = 0;
        int cpt = 0;
        int k;
        int j = 0;
        for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
            Vector3f vector3f = it.next();
            array[i] = vector3f.getX();
            i++;
            array[i] = vector3f.getY();
            i++;
            array[i] = vector3f.getZ();
            i++;
            if (it.hasNext()) {
                k = j;
                indices[cpt] = (short) k;
                cpt++;
                k++;
                indices[cpt] = (short) k;
                cpt++;
                j++;
            }
        }

        this.setMode(Mesh.Mode.Lines);
        this.setBuffer(VertexBuffer.Type.Position, 3, array);
        this.setBuffer(VertexBuffer.Type.Index, 2, indices);
        this.updateBound();
        this.updateCounts();
    }

    /**
     * This method returns the length of the curve.
     *
     * @return the length of the curve
     */
    public float getLength() {
        return spline.getTotalLength();
    }
}
