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

import com.jme3.animation.AnimControl;
import com.jme3.animation.Animation;
import com.jme3.animation.SkeletonControl;
import com.jme3.asset.*;
import com.jme3.material.Material;
import com.jme3.material.MaterialList;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry;
import com.jme3.util.PlaceholderAssets;
import static com.jme3.util.xml.SAXUtil.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Loads Ogre3D mesh.xml files.
 */
public class MeshLoader extends DefaultHandler implements AssetLoader {

    private static final Logger logger = Logger.getLogger(MeshLoader.class.getName());
    public static boolean AUTO_INTERLEAVE = true;
    private static final Type[] TEXCOORD_TYPES =
            new Type[]{
        Type.TexCoord,
        Type.TexCoord2,
        Type.TexCoord3,
        Type.TexCoord4,
        Type.TexCoord5,
        Type.TexCoord6,
        Type.TexCoord7,
        Type.TexCoord8,};
    private AssetKey key;
    private String meshName;
    private String folderName;
    private AssetManager assetManager;
    private MaterialList materialList;
    // Data per submesh/sharedgeom
    private ShortBuffer sb;
    private IntBuffer ib;
    private FloatBuffer fb;
    private VertexBuffer vb;
    private Mesh mesh;
    private Geometry geom;
    private ByteBuffer indicesData;
    private FloatBuffer weightsFloatData;
    private boolean actuallyHasWeights = false;
    private int vertCount;
    private boolean usesSharedVerts;
    private boolean usesBigIndices;
    private boolean submeshNamesHack;
    // Global data
    private Mesh sharedMesh;
    private int meshIndex = 0;
    private int texCoordIndex = 0;
    private String ignoreUntilEnd = null;
    private List<Geometry> geoms = new ArrayList<Geometry>();
    private ArrayList<Boolean> usesSharedMesh = new ArrayList<Boolean>();
    private IntMap<List<VertexBuffer>> lodLevels = new IntMap<List<VertexBuffer>>();
    private AnimData animData;

    public MeshLoader() {
        super();
    }

    @Override
    public void startDocument() {
        geoms.clear();
        lodLevels.clear();

        sb = null;
        ib = null;
        fb = null;
        vb = null;
        mesh = null;
        geom = null;
        sharedMesh = null;

        usesSharedMesh.clear();
        usesSharedVerts = false;
        vertCount = 0;
        meshIndex = 0;
        texCoordIndex = 0;
        ignoreUntilEnd = null;

        animData = null;

        actuallyHasWeights = false;
        submeshNamesHack = false;
        indicesData = null;
        weightsFloatData = null;
    }

    @Override
    public void endDocument() {
    }

    private void pushIndex(int index) {
        if (ib != null) {
            ib.put(index);
        } else {
            sb.put((short) index);
        }
    }

    private void pushFace(String v1, String v2, String v3) throws SAXException {
        // TODO: fan/strip support
        switch (mesh.getMode()) {
            case Triangles:
                pushIndex(parseInt(v1));
                pushIndex(parseInt(v2));
                pushIndex(parseInt(v3));
                break;
            case Lines:
                pushIndex(parseInt(v1));
                pushIndex(parseInt(v2));
                break;
            case Points:
                pushIndex(parseInt(v1));
                break;
        }
    }

//    private boolean isUsingSharedVerts(Geometry geom) {
    // Old code for buffer sharer
    //return geom.getUserData(UserData.JME_SHAREDMESH) != null;
//    }
    private void startFaces(String count) throws SAXException {
        int numFaces = parseInt(count);
        int indicesPerFace = 0;

        switch (mesh.getMode()) {
            case Triangles:
                indicesPerFace = 3;
                break;
            case Lines:
                indicesPerFace = 2;
                break;
            case Points:
                indicesPerFace = 1;
                break;
            default:
                throw new SAXException("Strips or fans not supported!");
        }

        int numIndices = indicesPerFace * numFaces;

        vb = new VertexBuffer(VertexBuffer.Type.Index);
        if (!usesBigIndices) {
            sb = BufferUtils.createShortBuffer(numIndices);
            ib = null;
            vb.setupData(Usage.Static, indicesPerFace, Format.UnsignedShort, sb);
        } else {
            ib = BufferUtils.createIntBuffer(numIndices);
            sb = null;
            vb.setupData(Usage.Static, indicesPerFace, Format.UnsignedInt, ib);
        }
        mesh.setBuffer(vb);
    }

    private void applyMaterial(Geometry geom, String matName) {
        Material mat = null;
        if (matName == null) {
            // no material specified. use placeholder.
            mat = null;
        } else if (matName.endsWith(".j3m")) {
            // load as native jme3 material instance
            try {
                mat = assetManager.loadMaterial(matName);
            } catch (AssetNotFoundException ex) {
                // Warning will be raised (see below)
                if (!ex.getMessage().equals(matName)) {
                    throw ex;
                }
            }
        } else {
            if (materialList != null) {
                mat = materialList.get(matName);
            }
        }

        if (mat == null) {
            logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{matName, key});
            mat = PlaceholderAssets.getPlaceholderMaterial(assetManager);
            //mat.setKey(new MaterialKey(matName));
        }

        if (mat.isTransparent()) {
            geom.setQueueBucket(Bucket.Transparent);
        }

        geom.setMaterial(mat);
    }

    private void startSubMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException {
        mesh = new Mesh();
        if (opType == null || opType.equals("triangle_list")) {
            mesh.setMode(Mesh.Mode.Triangles);
            //} else if (opType.equals("triangle_strip")) {
            //    mesh.setMode(Mesh.Mode.TriangleStrip);
            //} else if (opType.equals("triangle_fan")) {
            //    mesh.setMode(Mesh.Mode.TriangleFan);
        } else if (opType.equals("line_list")) {
            mesh.setMode(Mesh.Mode.Lines);
        } else {
            throw new SAXException("Unsupported operation type: " + opType);
        }

        usesBigIndices = parseBool(use32bitIndices, false);
        usesSharedVerts = parseBool(usesharedvertices, false);
        if (usesSharedVerts) {
            usesSharedMesh.add(true);

            // Old code for buffer sharer
            // import vertexbuffers from shared geom
//            IntMap<VertexBuffer> sharedBufs = sharedMesh.getBuffers();
//            for (Entry<VertexBuffer> entry : sharedBufs) {
//                mesh.setBuffer(entry.getValue());
//            }
        } else {
            usesSharedMesh.add(false);
        }

        if (meshName == null) {
            geom = new Geometry("OgreSubmesh-" + (++meshIndex), mesh);
        } else {
            geom = new Geometry(meshName + "-geom-" + (++meshIndex), mesh);
        }

        if (usesSharedVerts) {
            // Old code for buffer sharer
            // this mesh is shared!
            //geom.setUserData(UserData.JME_SHAREDMESH, sharedMesh);
        }

        applyMaterial(geom, matName);
        geoms.add(geom);
    }

    private void startSharedGeom(String vertexcount) throws SAXException {
        sharedMesh = new Mesh();
        vertCount = parseInt(vertexcount);
        usesSharedVerts = false;

        geom = null;
        mesh = sharedMesh;
    }

    private void startGeometry(String vertexcount) throws SAXException {
        vertCount = parseInt(vertexcount);
    }

    /**
     * Normalizes weights if needed and finds largest amount of weights used for
     * all vertices in the buffer.
     */
    private void endBoneAssigns() {
//        if (mesh != sharedMesh && isUsingSharedVerts(geom)) {
//            return;
//        }

        if (!actuallyHasWeights) {
            // No weights were actually written (the tag didn't have any entries)
            // remove those buffers
            mesh.clearBuffer(Type.BoneIndex);
            mesh.clearBuffer(Type.BoneWeight);

            weightsFloatData = null;
            indicesData = null;

            return;
        }

        //int vertCount = mesh.getVertexCount();
        int maxWeightsPerVert = 0;
        weightsFloatData.rewind();
        for (int v = 0; v < vertCount; v++) {
            float w0 = weightsFloatData.get(),
                    w1 = weightsFloatData.get(),
                    w2 = weightsFloatData.get(),
                    w3 = weightsFloatData.get();

            if (w3 != 0) {
                maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
            } else if (w2 != 0) {
                maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
            } else if (w1 != 0) {
                maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
            } else if (w0 != 0) {
                maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
            }

            float sum = w0 + w1 + w2 + w3;
            if (sum != 1f) {
                weightsFloatData.position(weightsFloatData.position() - 4);
                // compute new vals based on sum
                float sumToB = sum == 0 ? 0 : 1f / sum;
                weightsFloatData.put(w0 * sumToB);
                weightsFloatData.put(w1 * sumToB);
                weightsFloatData.put(w2 * sumToB);
                weightsFloatData.put(w3 * sumToB);
            }
        }
        weightsFloatData.rewind();

        actuallyHasWeights = false;
        weightsFloatData = null;
        indicesData = null;

        mesh.setMaxNumWeights(maxWeightsPerVert);
    }

    private void startBoneAssigns() {
        if (mesh != sharedMesh && usesSharedVerts) {
            // will use bone assignments from shared mesh (?)
            return;
        }

        // current mesh will have bone assigns
        //int vertCount = mesh.getVertexCount();
        // each vertex has
        // - 4 bone weights
        // - 4 bone indices
        // create array-backed buffers for software skinning for access speed
        weightsFloatData = FloatBuffer.allocate(vertCount * 4);
        indicesData = ByteBuffer.allocate(vertCount * 4);

        VertexBuffer weights = new VertexBuffer(Type.BoneWeight);
        VertexBuffer indices = new VertexBuffer(Type.BoneIndex);

        weights.setupData(Usage.CpuOnly, 4, Format.Float, weightsFloatData);
        indices.setupData(Usage.CpuOnly, 4, Format.UnsignedByte, indicesData);
        
        mesh.setBuffer(weights);
        mesh.setBuffer(indices);
        
        //creating empty buffers for HW skinning 
        //the buffers will be setup if ever used.
        VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight);
        VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex);
        //setting usage to cpuOnly so that the buffer is not send empty to the GPU
        indicesHW.setUsage(Usage.CpuOnly);
        weightsHW.setUsage(Usage.CpuOnly);
        mesh.setBuffer(weightsHW);
        mesh.setBuffer(indicesHW);
    }

    private void startVertexBuffer(Attributes attribs) throws SAXException {
        if (parseBool(attribs.getValue("positions"), false)) {
            vb = new VertexBuffer(Type.Position);
            fb = BufferUtils.createFloatBuffer(vertCount * 3);
            vb.setupData(Usage.Static, 3, Format.Float, fb);
            mesh.setBuffer(vb);
        }
        if (parseBool(attribs.getValue("normals"), false)) {
            vb = new VertexBuffer(Type.Normal);
            fb = BufferUtils.createFloatBuffer(vertCount * 3);
            vb.setupData(Usage.Static, 3, Format.Float, fb);
            mesh.setBuffer(vb);
        }
        if (parseBool(attribs.getValue("colours_diffuse"), false)) {
            vb = new VertexBuffer(Type.Color);
            fb = BufferUtils.createFloatBuffer(vertCount * 4);
            vb.setupData(Usage.Static, 4, Format.Float, fb);
            mesh.setBuffer(vb);
        }
        if (parseBool(attribs.getValue("tangents"), false)) {
            int dimensions = parseInt(attribs.getValue("tangent_dimensions"), 3);
            vb = new VertexBuffer(Type.Tangent);
            fb = BufferUtils.createFloatBuffer(vertCount * dimensions);
            vb.setupData(Usage.Static, dimensions, Format.Float, fb);
            mesh.setBuffer(vb);
        }
        if (parseBool(attribs.getValue("binormals"), false)) {
            vb = new VertexBuffer(Type.Binormal);
            fb = BufferUtils.createFloatBuffer(vertCount * 3);
            vb.setupData(Usage.Static, 3, Format.Float, fb);
            mesh.setBuffer(vb);
        }

        int texCoords = parseInt(attribs.getValue("texture_coords"), 0);
        for (int i = 0; i < texCoords; i++) {
            String dimsStr = attribs.getValue("texture_coord_dimensions_" + i);
            if (dimsStr != null && dimsStr.startsWith("float")) {
                dimsStr = dimsStr.substring("float".length());
            }
            int dims = parseInt(dimsStr, 2);
            if (dims < 1 || dims > 4) {
                throw new SAXException("Texture coord dimensions must be 1 <= dims <= 4");
            }

            if (i <= 7) {
                vb = new VertexBuffer(TEXCOORD_TYPES[i]);
            } else {
                // more than 8 texture coordinates are not supported by ogre.
                throw new SAXException("More than 8 texture coordinates not supported");
            }
            fb = BufferUtils.createFloatBuffer(vertCount * dims);
            vb.setupData(Usage.Static, dims, Format.Float, fb);
            mesh.setBuffer(vb);
        }
    }

    private void startVertex() {
        texCoordIndex = 0;
    }

    private void pushAttrib(Type type, Attributes attribs) throws SAXException {
        try {
            FloatBuffer buf = (FloatBuffer) mesh.getBuffer(type).getData();
            buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
        } catch (Exception ex) {
            throw new SAXException("Failed to push attrib", ex);
        }
    }

    private void pushTangent(Attributes attribs) throws SAXException {
        try {
            VertexBuffer tangentBuf = mesh.getBuffer(Type.Tangent);
            FloatBuffer buf = (FloatBuffer) tangentBuf.getData();
            buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
            if (tangentBuf.getNumComponents() == 4) {
                buf.put(parseFloat(attribs.getValue("w")));
            }
        } catch (Exception ex) {
            throw new SAXException("Failed to push attrib", ex);
        }
    }

    private void pushTexCoord(Attributes attribs) throws SAXException {
        if (texCoordIndex >= 8) {
            return; // More than 8 not supported by ogre.
        }
        Type type = TEXCOORD_TYPES[texCoordIndex];

        VertexBuffer tcvb = mesh.getBuffer(type);
        FloatBuffer buf = (FloatBuffer) tcvb.getData();

        buf.put(parseFloat(attribs.getValue("u")));
        if (tcvb.getNumComponents() >= 2) {
            buf.put(parseFloat(attribs.getValue("v")));
            if (tcvb.getNumComponents() >= 3) {
                buf.put(parseFloat(attribs.getValue("w")));
                if (tcvb.getNumComponents() == 4) {
                    buf.put(parseFloat(attribs.getValue("x")));
                }
            }
        }

        texCoordIndex++;
    }

    private void pushColor(Attributes attribs) throws SAXException {
        FloatBuffer buf = (FloatBuffer) mesh.getBuffer(Type.Color).getData();
        String value = parseString(attribs.getValue("value"));
        String[] vals = value.split("\\s");
        if (vals.length != 3 && vals.length != 4) {
            throw new SAXException("Color value must contain 3 or 4 components");
        }

        ColorRGBA color = new ColorRGBA();
        color.r = parseFloat(vals[0]);
        color.g = parseFloat(vals[1]);
        color.b = parseFloat(vals[2]);
        if (vals.length == 3) {
            color.a = 1f;
        } else {
            color.a = parseFloat(vals[3]);
        }

        buf.put(color.r).put(color.g).put(color.b).put(color.a);
    }

    private void startLodFaceList(String submeshindex, String numfaces) {
        int index = Integer.parseInt(submeshindex);
        mesh = geoms.get(index).getMesh();
        int faceCount = Integer.parseInt(numfaces);

        VertexBuffer originalIndexBuffer = mesh.getBuffer(Type.Index);
        vb = new VertexBuffer(VertexBuffer.Type.Index);
        if (originalIndexBuffer.getFormat() == Format.UnsignedInt) {
            // LOD buffer should also be integer
            ib = BufferUtils.createIntBuffer(faceCount * 3);
            sb = null;
            vb.setupData(Usage.Static, 3, Format.UnsignedInt, ib);
        } else {
            sb = BufferUtils.createShortBuffer(faceCount * 3);
            ib = null;
            vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb);
        }

        List<VertexBuffer> levels = lodLevels.get(index);
        if (levels == null) {
            // Create the LOD levels list
            levels = new ArrayList<VertexBuffer>();

            // Add the first LOD level (always the original index buffer)
            levels.add(originalIndexBuffer);
            lodLevels.put(index, levels);
        }
        levels.add(vb);
    }

    private void startLevelOfDetail(String numlevels) {
//        numLevels = Integer.parseInt(numlevels);
    }

    private void endLevelOfDetail() {
        // set the lod data for each mesh
        for (Entry<List<VertexBuffer>> entry : lodLevels) {
            Mesh m = geoms.get(entry.getKey()).getMesh();
            List<VertexBuffer> levels = entry.getValue();
            VertexBuffer[] levelArray = new VertexBuffer[levels.size()];
            levels.toArray(levelArray);
            m.setLodLevels(levelArray);
        }
    }

    private void startLodGenerated(String depthsqr) {
    }

    private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException {
        int vert = parseInt(vertIndex);
        float w = parseFloat(weight);
        byte bone = (byte) parseInt(boneIndex);

        assert bone >= 0;
        assert vert >= 0 && vert < mesh.getVertexCount();

        int i;
        float v = 0;
        // see which weights are unused for a given bone
        for (i = vert * 4; i < vert * 4 + 4; i++) {
            v = weightsFloatData.get(i);
            if (v == 0) {
                break;
            }
        }
        if (v != 0) {
            logger.log(Level.WARNING, "Vertex {0} has more than 4 weights per vertex! Ignoring..", vert);
            return;
        }

        weightsFloatData.put(i, w);
        indicesData.put(i, bone);
        actuallyHasWeights = true;
    }

    private void startSkeleton(String name) {
        AssetKey assetKey = new AssetKey(folderName + name + ".xml");
        try {
            animData = (AnimData) assetManager.loadAsset(assetKey);
        } catch (AssetNotFoundException ex) {
            logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{assetKey, key});
            animData = null;
        }
    }

    private void startSubmeshName(String indexStr, String nameStr) {
        int index = Integer.parseInt(indexStr);
        if (index >= geoms.size()) {
            logger.log(Level.WARNING, "Submesh name index is larger than number of geometries: {0} >= {1}",
                    new Object[]{index, geoms.size()});
        } else {
            geoms.get(index).setName(nameStr);
        }
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException {
        if (ignoreUntilEnd != null) {
            return;
        }

        if (qName.equals("texcoord")) {
            pushTexCoord(attribs);
        } else if (qName.equals("vertexboneassignment")) {
            pushBoneAssign(attribs.getValue("vertexindex"),
                    attribs.getValue("boneindex"),
                    attribs.getValue("weight"));
        } else if (qName.equals("face")) {
            pushFace(attribs.getValue("v1"),
                    attribs.getValue("v2"),
                    attribs.getValue("v3"));
        } else if (qName.equals("position")) {
            pushAttrib(Type.Position, attribs);
        } else if (qName.equals("normal")) {
            pushAttrib(Type.Normal, attribs);
        } else if (qName.equals("tangent")) {
            pushTangent(attribs);
        } else if (qName.equals("binormal")) {
            pushAttrib(Type.Binormal, attribs);
        } else if (qName.equals("colour_diffuse")) {
            pushColor(attribs);
        } else if (qName.equals("vertex")) {
            startVertex();
        } else if (qName.equals("faces")) {
            startFaces(attribs.getValue("count"));
        } else if (qName.equals("geometry")) {
            String count = attribs.getValue("vertexcount");
            if (count == null) {
                count = attribs.getValue("count");
            }
            startGeometry(count);
        } else if (qName.equals("vertexbuffer")) {
            startVertexBuffer(attribs);
        } else if (qName.equals("lodfacelist")) {
            startLodFaceList(attribs.getValue("submeshindex"),
                    attribs.getValue("numfaces"));
        } else if (qName.equals("lodgenerated")) {
            startLodGenerated(attribs.getValue("fromdepthsquared"));
        } else if (qName.equals("levelofdetail")) {
            startLevelOfDetail(attribs.getValue("numlevels"));
        } else if (qName.equals("boneassignments")) {
            startBoneAssigns();
        } else if (qName.equals("submesh")) {
            if (submeshNamesHack) {
                // Hack for blender2ogre only
                startSubmeshName(attribs.getValue("index"), attribs.getValue("name"));
            } else {
                startSubMesh(attribs.getValue("material"),
                        attribs.getValue("usesharedvertices"),
                        attribs.getValue("use32bitindexes"),
                        attribs.getValue("operationtype"));
            }
        } else if (qName.equals("sharedgeometry")) {
            String count = attribs.getValue("vertexcount");
            if (count == null) {
                count = attribs.getValue("count");
            }

            if (count != null && !count.equals("0")) {
                startSharedGeom(count);
            }
        } else if (qName.equals("submeshes")) {
            // ok
        } else if (qName.equals("skeletonlink")) {
            startSkeleton(attribs.getValue("name"));
        } else if (qName.equals("submeshnames")) {
            // ok
            // setting submeshNamesHack to true will make "submesh" tag be interpreted
            // as a "submeshname" tag.
            submeshNamesHack = true;
        } else if (qName.equals("submeshname")) {
            startSubmeshName(attribs.getValue("index"), attribs.getValue("name"));
        } else if (qName.equals("mesh")) {
            // ok
        } else {
            logger.log(Level.WARNING, "Unknown tag: {0}. Ignoring.", qName);
            ignoreUntilEnd = qName;
        }
    }

    @Override
    public void endElement(String uri, String name, String qName) {
        if (ignoreUntilEnd != null) {
            if (ignoreUntilEnd.equals(qName)) {
                ignoreUntilEnd = null;
            }
            return;
        }


        // If submesh hack is enabled, ignore any submesh/submeshes
        // end tags.
        if (qName.equals("submesh") && !submeshNamesHack) {
            usesBigIndices = false;
            geom = null;
            mesh = null;
        } else if (qName.equals("submeshes") && !submeshNamesHack) {
            // IMPORTANT: restore sharedmesh, for use with shared boneweights
            geom = null;
            mesh = sharedMesh;
            usesSharedVerts = false;
        } else if (qName.equals("faces")) {
            if (ib != null) {
                ib.flip();
            } else {
                sb.flip();
            }

            vb = null;
            ib = null;
            sb = null;
        } else if (qName.equals("vertexbuffer")) {
            fb = null;
            vb = null;
        } else if (qName.equals("geometry")
                || qName.equals("sharedgeometry")) {
            // finish writing to buffers
            for (VertexBuffer buf : mesh.getBufferList().getArray()) {
                Buffer data = buf.getData();
                if (data.position() != 0) {
                    data.flip();
                }
            }
            mesh.updateBound();
            mesh.setStatic();

            if (qName.equals("sharedgeometry")) {
                geom = null;
                mesh = null;
            }
        } else if (qName.equals("lodfacelist")) {
            sb.flip();
            vb = null;
            sb = null;
        } else if (qName.equals("levelofdetail")) {
            endLevelOfDetail();
        } else if (qName.equals("boneassignments")) {
            endBoneAssigns();
        } else if (qName.equals("submeshnames")) {
            // Restore default handling for "submesh" tag.
            submeshNamesHack = false;
        }
    }

    @Override
    public void characters(char ch[], int start, int length) {
    }

    private Node compileModel() {
        Node model = new Node(meshName + "-ogremesh");

        for (int i = 0; i < geoms.size(); i++) {
            Geometry g = geoms.get(i);
            Mesh m = g.getMesh();

            // New code for buffer extract
            if (sharedMesh != null && usesSharedMesh.get(i)) {
                m.extractVertexData(sharedMesh);
            }

            model.attachChild(geoms.get(i));
        }

        // Do not attach shared geometry to the node!

        if (animData != null) {
            // This model uses animation

            for (int i = 0; i < geoms.size(); i++) {
                Geometry g = geoms.get(i);
                Mesh m = geoms.get(i).getMesh();
                
                //FIXME the parameter is now useless.
                //It was !HADWARE_SKINNING before, but since toggleing 
                //HW skinning does not happen at load time it was always true.
                //We should use something similar as for the HWBoneIndex and 
                //HWBoneWeight : create the vertex buffers empty so that they 
                //are put in the cache, and really populate them the first time 
                //software skinning is used on the mesh.
                m.generateBindPose(true);

            }

            // Put the animations in the AnimControl
            HashMap<String, Animation> anims = new HashMap<String, Animation>();
            ArrayList<Animation> animList = animData.anims;
            for (int i = 0; i < animList.size(); i++) {
                Animation anim = animList.get(i);
                anims.put(anim.getName(), anim);
            }

            AnimControl ctrl = new AnimControl(animData.skeleton);
            ctrl.setAnimations(anims);
            model.addControl(ctrl);

            // Put the skeleton in the skeleton control
            SkeletonControl skeletonControl = new SkeletonControl(animData.skeleton);

            // This will acquire the targets from the node
            model.addControl(skeletonControl);
        }

        return model;
    }

    public Object load(AssetInfo info) throws IOException {
        try {
            key = info.getKey();
            meshName = key.getName();
            folderName = key.getFolder();
            String ext = key.getExtension();
            meshName = meshName.substring(0, meshName.length() - ext.length() - 1);
            if (folderName != null && folderName.length() > 0) {
                meshName = meshName.substring(folderName.length());
            }
            assetManager = info.getManager();

            if (key instanceof OgreMeshKey) {
                // OgreMeshKey is being used, try getting the material list
                // from it
                OgreMeshKey meshKey = (OgreMeshKey) key;
                materialList = meshKey.getMaterialList();
                String materialName = meshKey.getMaterialName();

                // Material list not set but material name is available
                if (materialList == null && materialName != null) {
                    OgreMaterialKey materialKey = new OgreMaterialKey(folderName + materialName + ".material");
                    try {
                        materialList = (MaterialList) assetManager.loadAsset(materialKey);
                    } catch (AssetNotFoundException e) {
                        logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{materialKey, key});
                    }
                }
            } else {
                // Make sure to reset it to null so that previous state
                // doesn't leak onto this one
                materialList = null;
            }

            // If for some reason material list could not be found through
            // OgreMeshKey, or if regular ModelKey specified, load using 
            // default method.
            if (materialList == null) {
                OgreMaterialKey materialKey = new OgreMaterialKey(folderName + meshName + ".material");
                try {
                    materialList = (MaterialList) assetManager.loadAsset(materialKey);
                } catch (AssetNotFoundException e) {
                    logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{materialKey, key});
                }
            }

            // Added by larynx 25.06.2011
            // Android needs the namespace aware flag set to true                 
            // Kirill 30.06.2011
            // Now, hack is applied for both desktop and android to avoid
            // checking with JmeSystem.
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setNamespaceAware(true);

            XMLReader xr = factory.newSAXParser().getXMLReader();
            xr.setContentHandler(this);
            xr.setErrorHandler(this);

            InputStreamReader r = null;
            try {
                r = new InputStreamReader(info.openStream());
                xr.parse(new InputSource(r));
            } finally {
                if (r != null) {
                    r.close();
                }
            }

            return compileModel();
        } catch (SAXException ex) {
            IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml");
            ioEx.initCause(ex);
            throw ioEx;
        } catch (ParserConfigurationException ex) {
            IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml");
            ioEx.initCause(ex);
            throw ioEx;
        }

    }
}
