
using Android.Content.Res;
using Android.Graphics;
using Android.Opengl;
using Android.Util;
using Java.IO;
using Java.Nio;
using System;
using System.IO;

namespace PikkartSample.Droid
{
    public class Mesh
    {
        private string mName; /**< mesh name */
        /**
         * \brief Get this mesh name.
         * @return the mesh name as a String.
         */
        public string Name() { return mName; }

        private bool _meshLoaded = false;
        public bool MeshLoaded
        {
            get { return _meshLoaded; }
        }
        private bool _glLoaded = false;
        public bool GLLoaded
        {
            get { return _glLoaded; }
        }

        private ByteBuffer texture;
        private int[] textureDims = new int[2];

        private ByteBuffer mVertices_Buffer; /**< vertices data */
        private ByteBuffer mTexCoords_Buffer; /**< texture coordinates data */
        private ByteBuffer mNormals_Buffer; /**< normals data */
        private ByteBuffer mIndex_Buffer; /**< triangle indices data */

        private int mIndices_Number = 0; /**< number of indices */
        private int mVertices_Number = 0; /**< number of vertices */

        private int mTexture_GL_ID = 0; /**< mesh texture opengl id */

        private int mProgram_GL_ID = 0; /**< mesh shader program opengl id */

        /**
         * This mesh vertex shader code. A very basic vetex shader
         */
        public static string MESH_VERTEX_SHADER = " \n" + "\n"
            + "attribute vec4 vertexPosition; \n"
            + "attribute vec2 vertexTexCoord; \n" + "\n"
            + "varying vec2 texCoord; \n" + "\n"
            + "uniform mat4 modelViewProjectionMatrix; \n" + "\n"
            + "void main() \n" + "{ \n"
            + "   gl_Position = modelViewProjectionMatrix * vertexPosition; \n"
            + "   texCoord = vertexTexCoord; \n"
            + "} \n";
    /**
     * This mesh fragment shader code. A very basic fragment shader
     */
    public static string MESH_FRAGMENT_SHADER = " \n" + "\n"
            + "precision mediump float; \n" + " \n"
            + "varying vec2 texCoord; \n" + " \n"
            + "uniform sampler2D texSampler2D; \n" + " \n"
            + "void main() \n" + "{ \n"
            + "   gl_FragColor = texture2D(texSampler2D, texCoord); \n"
            + "} \n";

    /**
     * \brief Generate a ByteBuffer from a float array.
     * @param array the float array.
     * @return the generated ByteBuffer.
     */
    protected ByteBuffer fillBuffer(float[] array)
        {
            ByteBuffer bb = ByteBuffer.AllocateDirect(4 * array.Length);
            bb.Order(ByteOrder.LittleEndian);
            foreach (float d in array)
            {
                bb.PutFloat(d);
            }
            bb.Rewind();
            return bb;
        }

        /**
         * \brief Generate a ByteBuffer from a short array.
         * @param array the short array.
         * @return the generated ByteBuffer.
         */
        protected ByteBuffer fillBuffer(short[] array)
        {
            ByteBuffer bb = ByteBuffer.AllocateDirect(2 * array.Length);
            bb.Order(ByteOrder.LittleEndian);
            foreach (short s in array)
            {
                bb.PutShort(s);
            }
            bb.Rewind();
            return bb;
        }

        /**
         * \brief Load mesh data from a json file stored in the app asset dir.
         * @param am the app AssetManager.
         * @param file the file to load
         * @return true on success.
         */
        private bool LoadMesh(AssetManager am, string file)
        {
            Stream stream;
            try
            {
                stream = am.Open(file);
            }
            catch (Exception e)
            {
                //Log.e("appTest", "Error loading mesh file from app asset, msg:" + e.getMessage());
                return false;
            }
            Reader isReader = new InputStreamReader(stream);
            JsonReader reader = new JsonReader(isReader);

            mVertices_Buffer = ByteBuffer.AllocateDirect(524288);
            mVertices_Buffer.Order(ByteOrder.LittleEndian);

            mTexCoords_Buffer = ByteBuffer.AllocateDirect(524288);
            mTexCoords_Buffer.Order(ByteOrder.LittleEndian);

            mNormals_Buffer = ByteBuffer.AllocateDirect(524288);
            mNormals_Buffer.Order(ByteOrder.LittleEndian);

            mIndex_Buffer = ByteBuffer.AllocateDirect(524288);
            mIndex_Buffer.Order(ByteOrder.LittleEndian);

            mIndices_Number = 0;
            mVertices_Number = 0;

            try
            {
                reader.BeginObject();
                do
                {
                    String txt = reader.NextName();
                    if (txt.CompareTo("name") == 0)
                    {
                        String val = reader.NextString();
                        mName = val;
                    }
                    else if (txt.CompareTo("vertices") == 0)
                    {
                        reader.BeginArray();
                        do
                        {
                            reader.BeginObject();
                            String element_name = "";
                            int element_size = 0;
                            String element_type = "float32";
                            Boolean element_normalized = false;
                            do
                            {
                                String elmnt = reader.NextName();
                                if (elmnt.CompareTo("name") == 0)
                                {
                                    element_name = reader.NextString();
                                }
                                else if (elmnt.CompareTo("size") == 0)
                                {
                                    element_size = reader.NextInt();
                                }
                                else if (elmnt.CompareTo("type") == 0)
                                {
                                    element_type = reader.NextString();
                                }
                                else if (elmnt.CompareTo("normalized") == 0)
                                {
                                    element_normalized = reader.NextBoolean();
                                }
                                else if (elmnt.CompareTo("values") == 0)
                                {
                                    reader.BeginArray();
                                    if (element_type.CompareTo("float32") == 0 && element_name.CompareTo("position_buffer") == 0)
                                    {
                                        do
                                        {
                                            float x = (float)reader.NextDouble() * -0.2f + 0.5f;
                                            //reader.HasNext;
                                            float y = (float)reader.NextDouble() * -0.2f + 0.5f;
                                            //reader.HasNext;
                                            float z = (float)reader.NextDouble() * -0.2f;
                                            mVertices_Buffer.PutFloat(x);
                                            mVertices_Buffer.PutFloat(y);
                                            mVertices_Buffer.PutFloat(z);
                                            mVertices_Number++;
                                        }
                                        while (reader.HasNext);
                                    }
                                    else if (element_type.CompareTo("float32") == 0 && element_name.CompareTo("normal_buffer") == 0)
                                    {
                                        do
                                        {
                                            float v = (float)reader.NextDouble();
                                            mNormals_Buffer.PutFloat(v);
                                        }
                                        while (reader.HasNext);
                                    }
                                    else if (element_type.CompareTo("float32") == 0 && element_name.CompareTo("texcoord_buffer") == 0)
                                    {
                                        do
                                        {
                                            float v = (float)reader.NextDouble();
                                            mTexCoords_Buffer.PutFloat(v);
                                        }
                                        while (reader.HasNext);
                                    }
                                    reader.EndArray();
                                }
                                else
                                {
                                    reader.SkipValue();
                                }
                                Log.Debug("appTest", reader.ToString());
                            }
                            while (reader.HasNext);
                            reader.EndObject();
                        }
                        while (reader.HasNext);
                        reader.EndArray();
                    }
                    else if (txt.CompareTo("connectivity") == 0)
                    {
                        reader.BeginArray();
                        do
                        {
                            reader.BeginObject();
                            String element_name = "";
                            String element_mode = "";
                            Boolean element_indexed = true;
                            String element_indexType = "";
                            do
                            {
                                String elmnt = reader.NextName();
                                if (elmnt.CompareTo("name") == 0)
                                {
                                    element_name = reader.NextString();
                                }
                                else if (elmnt.CompareTo("mode") == 0)
                                {
                                    element_mode = reader.NextString();
                                }
                                else if (elmnt.CompareTo("indexed") == 0)
                                {
                                    element_indexed = reader.NextBoolean();
                                }
                                else if (elmnt.CompareTo("indexType") == 0)
                                {
                                    element_indexType = reader.NextString();
                                }
                                else if (elmnt.CompareTo("indices") == 0)
                                {
                                    reader.BeginArray();
                                    if (element_mode.CompareTo("triangles_list") == 0 && element_indexed && element_indexType.CompareTo("uint32") == 0)
                                    {
                                        do
                                        {
                                            int v = reader.NextInt();
                                            mIndex_Buffer.PutShort((short)v);
                                            mIndices_Number++;
                                        }
                                        while (reader.HasNext);
                                    }
                                    reader.EndArray();
                                }
                                else
                                {
                                    reader.SkipValue();
                                }
                                Log.Debug("appTest", reader.ToString());

                            }
                            while (reader.HasNext);
                            reader.EndObject();
                        }
                        while (reader.HasNext);
                        reader.EndArray();
                    }
                    else
                    {
                        reader.SkipValue();
                    }
                    Log.Debug("appTest", reader.ToString());
                }
                while (reader.HasNext);
                reader.EndObject();
            }
            catch (Exception e)
            {
                Log.Error("appTest", "Error parsing mesh json, msg:" + e.Message);
                return false;
            }

            mVertices_Number /= 3;

            mVertices_Buffer.Rewind();
            mTexCoords_Buffer.Rewind();
            mNormals_Buffer.Rewind();
            mIndex_Buffer.Rewind();

            return true;
        }

        /**
         * \brief Create a Mesh from a json file (comntaining mesh data) and a texture file.
         * @param am the app AssetManager.
         * @param mesh_file the mesh file to load
         * @param texture_file the texture file to load
         * @return true on success.
         */
        public bool InitMesh(AssetManager am, String mesh_file, String texture_file)
        {
            _glLoaded = false;
            LoadMesh(am, mesh_file);
            texture = RenderUtils.loadTexture(am, texture_file, textureDims);
            _meshLoaded = true;

            return true;
        }

        public bool InitMeshGL()
        {
            mTexture_GL_ID = RenderUtils.loadTextureFromByteBuffer(texture, textureDims[0], textureDims[1]);
            mProgram_GL_ID = RenderUtils.createProgramFromShaderSrc(MESH_VERTEX_SHADER, MESH_FRAGMENT_SHADER);
            _glLoaded = true;
            return true;
        }

        /**
         * \brief Draw this mesh (in OpenGL).
         * @param modelViewProjection this mesh model-view-projection matrix.
         */
        public void DrawMesh(float[] modelViewProjection)
        {
            //set up gl state
            GLES20.GlEnable(GLES20.GlDepthTest);
            //GLES20.GlDisable(GLES20.GlCullFaceMode);
            GLES20.GlCullFace(GLES20.GlBack);
            GLES20.GlFrontFace(GLES20.GlCw);

            //set shader program to use
            GLES20.GlUseProgram(mProgram_GL_ID);
            RenderUtils.CheckGLError("DrawMesh:glUseProgram");

            //find attrib and unifroms in shader program
            int vertexHandle = GLES20.GlGetAttribLocation(mProgram_GL_ID, "vertexPosition");
            //int normalHandle = GLES20.GlGetAttribLocation(Program_GL_ID, "vertexNormal");
            int textureCoordHandle = GLES20.GlGetAttribLocation(mProgram_GL_ID, "vertexTexCoord");
            int mvpMatrixHandle = GLES20.GlGetUniformLocation(mProgram_GL_ID, "modelViewProjectionMatrix");
            int texSampler2DHandle = GLES20.GlGetUniformLocation(mProgram_GL_ID, "texSampler2D");
            RenderUtils.CheckGLError("DrawMesh:get attribs and uniforms");

            //upload mesh data to OpenGL attribs
            GLES20.GlVertexAttribPointer(vertexHandle, 3, GLES20.GlFloat, false, 0, mVertices_Buffer);
            //GLES20.GlVertexAttribPointer(normalHandle, 3, GLES20.GlFloat, false, 0, Normals_Buffer);
            GLES20.GlVertexAttribPointer(textureCoordHandle, 2, GLES20.GlFloat, false, 0, mTexCoords_Buffer);
            RenderUtils.CheckGLError("DrawMesh:put attrib pointers");

            //enable gl attribs to use
            GLES20.GlEnableVertexAttribArray(vertexHandle);
            //GLES20.GlEnableVertexAttribArray(normalHandle);
            GLES20.GlEnableVertexAttribArray(textureCoordHandle);
            RenderUtils.CheckGLError("DrawMesh:enable attrib arrays");

            // activate texture 0, bind it, and pass to shader
            GLES20.GlActiveTexture(GLES20.GlTexture0);
            GLES20.GlBindTexture(GLES20.GlTexture2d, mTexture_GL_ID);
            GLES20.GlUniform1i(texSampler2DHandle, 0);
            RenderUtils.CheckGLError("DrawMesh:activate texturing");

            // pass the model view matrix to the shader
            GLES20.GlUniformMatrix4fv(mvpMatrixHandle, 1, false, modelViewProjection, 0);
            RenderUtils.CheckGLError("DrawMesh:upload matrix");

            // finally draw the teapot
            GLES20.GlDrawElements(GLES20.GlTriangles, mIndices_Number, GLES20.GlUnsignedShort, mIndex_Buffer);
            RenderUtils.CheckGLError("DrawMesh:draw elements");

            // disable the enabled arrays
            GLES20.GlDisableVertexAttribArray(vertexHandle);
            //GLES20.GlDisableVertexAttribArray(normalHandle);
            GLES20.GlDisableVertexAttribArray(textureCoordHandle);
            RenderUtils.CheckGLError("DrawMesh:disable attrib arrays");
        }
    }

}