/*
 ** Copyright 2011, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 **
 **     http://www.apache.org/licenses/LICENSE-2.0
 **
 ** Unless required by applicable law or agreed to in writing, software
 ** distributed under the License is distributed on an "AS IS" BASIS,
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */

package com.android.ide.eclipse.gldebugger;

import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message;
import com.android.sdklib.util.SparseArray;

import java.nio.ByteBuffer;

class GLBuffer implements Cloneable {
    public final int name;
    public GLEnum usage;
    public GLEnum target;
    /** in SampleView.targetByteOrder */
    public ByteBuffer data;

    public GLBuffer(final int name) {
        this.name = name;
    }

    /** deep copy */
    @Override
    public GLBuffer clone() {
        try {
            GLBuffer copy = (GLBuffer) super.clone();
            if (data != null) {
                copy.data = ByteBuffer.allocate(data.capacity());
                copy.data.order(GLFramesView.TARGET_BYTE_ORDER);
                data.position(0);
                copy.data.put(data);
            }
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            assert false;
            return null;
        }
    }
}

class GLAttribPointer implements Cloneable {
    public int size; // number of values per vertex
    public GLEnum type; // data type
    public int stride; // bytes
    /**
     * element stride in bytes, used when fetching from buffer; not for fetching
     * from user pointer since server already packed elements
     */
    int elemStride; // in bytes
    /** element size in bytes */
    int elemSize;
    public int ptr; // pointer in debugger server or byte offset into buffer
    public GLBuffer buffer;
    public boolean normalized;
    public boolean enabled;

    /** deep copy, re-maps buffer into copyBuffers */
    public GLAttribPointer clone(SparseArray<GLBuffer> copyBuffers) {
        try {
            GLAttribPointer copy = (GLAttribPointer) super.clone();
            if (buffer != null)
                copy.buffer = copyBuffers.get(buffer.name);
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            assert false;
            return null;
        }
    }
}

public class GLServerVertex implements Cloneable {
    public SparseArray<GLBuffer> buffers = new SparseArray<GLBuffer>();
    public GLBuffer attribBuffer, indexBuffer; // current binding
    public GLAttribPointer attribPointers[];
    public float defaultAttribs[][];

    public GLServerVertex(final int MAX_VERTEX_ATTRIBS) {
        buffers.append(0, null);
        attribPointers = new GLAttribPointer[MAX_VERTEX_ATTRIBS];
        for (int i = 0; i < attribPointers.length; i++)
            attribPointers[i] = new GLAttribPointer();
        defaultAttribs = new float[MAX_VERTEX_ATTRIBS][4];
        for (int i = 0; i < defaultAttribs.length; i++) {
            defaultAttribs[i][0] = 0;
            defaultAttribs[i][1] = 0;
            defaultAttribs[i][2] = 0;
            defaultAttribs[i][3] = 1;
        }
    }

    /** deep copy */
    @Override
    public GLServerVertex clone() {
        try {
            GLServerVertex copy = (GLServerVertex) super.clone();

            copy.buffers = new SparseArray<GLBuffer>(buffers.size());
            for (int i = 0; i < buffers.size(); i++)
                if (buffers.valueAt(i) != null)
                    copy.buffers.append(buffers.keyAt(i), buffers.valueAt(i).clone());
                else
                    copy.buffers.append(buffers.keyAt(i), null);

            if (attribBuffer != null)
                copy.attribBuffer = copy.buffers.get(attribBuffer.name);
            if (indexBuffer != null)
                copy.indexBuffer = copy.buffers.get(indexBuffer.name);

            copy.attribPointers = new GLAttribPointer[attribPointers.length];
            for (int i = 0; i < attribPointers.length; i++)
                copy.attribPointers[i] = attribPointers[i].clone(copy.buffers);

            copy.defaultAttribs = defaultAttribs.clone();

            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            assert false;
            return null;
        }
    }

    /** returns true if processed */
    public boolean process(final Message msg) {
        switch (msg.getFunction()) {
            case glBindBuffer:
                glBindBuffer(msg);
                return true;
            case glBufferData:
                glBufferData(msg);
                return true;
            case glBufferSubData:
                glBufferSubData(msg);
                return true;
            case glDeleteBuffers:
                glDeleteBuffers(msg);
                return true;
            case glDrawArrays:
            case glDrawElements:
                return true;
            case glDisableVertexAttribArray:
                glDisableVertexAttribArray(msg);
                return true;
            case glEnableVertexAttribArray:
                glEnableVertexAttribArray(msg);
                return true;
            case glGenBuffers:
                glGenBuffers(msg);
                return true;
            case glVertexAttribPointer:
                glVertexAttribPointer(msg);
                return true;
            case glVertexAttrib1f:
                glVertexAttrib1f(msg);
                return true;
            case glVertexAttrib1fv:
                glVertexAttrib1fv(msg);
                return true;
            case glVertexAttrib2f:
                glVertexAttrib2f(msg);
                return true;
            case glVertexAttrib2fv:
                glVertexAttrib2fv(msg);
                return true;
            case glVertexAttrib3f:
                glVertexAttrib3f(msg);
                return true;
            case glVertexAttrib3fv:
                glVertexAttrib3fv(msg);
                return true;
            case glVertexAttrib4f:
                glVertexAttrib4f(msg);
                return true;
            case glVertexAttrib4fv:
                glVertexAttrib4fv(msg);
                return true;
            default:
                return false;
        }
    }

    // void API_ENTRY(glBindBuffer)(GLenum target, GLuint buffer)
    public void glBindBuffer(Message msg) {
        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
            attribBuffer = buffers.get(msg.getArg1());
            if (null != attribBuffer)
                attribBuffer.target = GLEnum.GL_ARRAY_BUFFER;
        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
            indexBuffer = buffers.get(msg.getArg1());
            if (null != indexBuffer)
                indexBuffer.target = GLEnum.GL_ELEMENT_ARRAY_BUFFER;
        } else
            assert false;
    }

    // void API_ENTRY(glBufferData)(GLenum target, GLsizeiptr size, const
    // GLvoid:size:in data, GLenum usage)
    public void glBufferData(Message msg) {
        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
            attribBuffer.usage = GLEnum.valueOf(msg.getArg3());
            attribBuffer.data = msg.getData().asReadOnlyByteBuffer();
            attribBuffer.data.order(GLFramesView.TARGET_BYTE_ORDER);
        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
            indexBuffer.usage = GLEnum.valueOf(msg.getArg3());
            indexBuffer.data = msg.getData().asReadOnlyByteBuffer();
            indexBuffer.data.order(GLFramesView.TARGET_BYTE_ORDER);
        } else
            assert false;
    }

    // void API_ENTRY(glBufferSubData)(GLenum target, GLintptr offset,
    // GLsizeiptr size, const GLvoid:size:in data)
    public void glBufferSubData(Message msg) {
        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
            if (attribBuffer.data.isReadOnly()) {
                ByteBuffer buffer = ByteBuffer.allocate(attribBuffer.data.capacity());
                buffer.order(GLFramesView.TARGET_BYTE_ORDER);
                buffer.put(attribBuffer.data);
                attribBuffer.data = buffer;
            }
            attribBuffer.data.position(msg.getArg1());
            attribBuffer.data.put(msg.getData().asReadOnlyByteBuffer());
        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
            if (indexBuffer.data.isReadOnly()) {
                ByteBuffer buffer = ByteBuffer.allocate(indexBuffer.data.capacity());
                buffer.order(GLFramesView.TARGET_BYTE_ORDER);
                buffer.put(indexBuffer.data);
                indexBuffer.data = buffer;
            }
            indexBuffer.data.position(msg.getArg1());
            indexBuffer.data.put(msg.getData().asReadOnlyByteBuffer());
        } else
            assert false;
    }

    // void glDeleteBuffers(GLsizei n, const GLuint* buffers)
    public void glDeleteBuffers(Message msg) {
        final int n = msg.getArg0();
        final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
        names.order(GLFramesView.TARGET_BYTE_ORDER);
        for (int i = 0; i < n; i++) {
            final int name = names.getInt();
            final GLBuffer buffer = buffers.get(name);
            for (int j = 0; j < attribPointers.length; j++)
                if (attribPointers[j].buffer == buffer) {
                    attribPointers[j].buffer = null;
                    attribPointers[j].enabled = false;
                }
            if (attribBuffer == buffer)
                attribBuffer = null;
            if (indexBuffer == buffer)
                indexBuffer = null;
            buffers.remove(name);
        }
    }

    // void glDisableVertexAttribArray(GLuint index)
    public void glDisableVertexAttribArray(Message msg) {
        if (msg.getArg0() >= 0 && msg.getArg0() < attribPointers.length)
            attribPointers[msg.getArg0()].enabled = false;
    }

    float fetchConvert(final ByteBuffer src, final GLEnum type, final boolean normalized) {
        if (GLEnum.GL_FLOAT == type)
            return Float.intBitsToFloat(src.getInt());
        else if (GLEnum.GL_UNSIGNED_INT == type)
            if (normalized)
                return (src.getInt() & 0xffffffffL) / (2e32f - 1);
            else
                return src.getInt() & 0xffffffffL;
        else if (GLEnum.GL_INT == type)
            if (normalized)
                return (src.getInt() * 2 + 1) / (2e32f - 1);
            else
                return src.getInt();
        else if (GLEnum.GL_UNSIGNED_SHORT == type)
            if (normalized)
                return (src.getShort() & 0xffff) / (2e16f - 1);
            else
                return src.getShort() & 0xffff;
        else if (GLEnum.GL_SHORT == type)
            if (normalized)
                return (src.getShort() * 2 + 1) / (2e16f - 1);
            else
                return src.getShort();
        else if (GLEnum.GL_UNSIGNED_BYTE == type)
            if (normalized)
                return (src.get() & 0xff) / (2e8f - 1);
            else
                return src.get() & 0xff;
        else if (GLEnum.GL_BYTE == type)
            if (normalized)
                return (src.get() * 2 + 1) / (2e8f - 1);
            else
                return src.get();
        else if (GLEnum.GL_FIXED == type)
            if (normalized)
                return (src.getInt() * 2 + 1) / (2e32f - 1);
            else
                return src.getInt() / (2e16f);
        else
            assert false;
        return 0;
    }

    static int typeSize(final GLEnum type) {
        switch (type) {
            case GL_FLOAT:
            case GL_UNSIGNED_INT:
            case GL_INT:
            case GL_FIXED:
                return 4;
            case GL_UNSIGNED_SHORT:
            case GL_SHORT:
                return 2;
            case GL_UNSIGNED_BYTE:
            case GL_BYTE:
                return 1;
            default:
                assert false;
                return 0;
        }
    }

    void fetch(final int maxAttrib, final int index, final int dstIdx, final ByteBuffer nonVBO,
            final float[][] fetchedAttribs) {
        for (int i = 0; i < maxAttrib; i++) {
            final GLAttribPointer attrib = attribPointers[i];
            int size = 0;
            if (attrib.enabled) {
                size = attrib.size;
                if (null != attrib.buffer) {
                    final ByteBuffer src = attrib.buffer.data;
                    src.position(attrib.ptr + index * attrib.elemStride);
                    for (int j = 0; j < size; j++)
                        fetchedAttribs[i][dstIdx * 4 + j] = fetchConvert(src, attrib.type,
                                attrib.normalized);
                } else
                    for (int j = 0; j < size; j++)
                        fetchedAttribs[i][dstIdx * 4 + j] = fetchConvert(nonVBO, attrib.type,
                                attrib.normalized);
            }
            if (size < 1)
                fetchedAttribs[i][dstIdx * 4 + 0] = defaultAttribs[i][0];
            if (size < 2)
                fetchedAttribs[i][dstIdx * 4 + 1] = defaultAttribs[i][1];
            if (size < 3)
                fetchedAttribs[i][dstIdx * 4 + 2] = defaultAttribs[i][2];
            if (size < 4)
                fetchedAttribs[i][dstIdx * 4 + 3] = defaultAttribs[i][3];
        }
    }

    /**
     * fetches and converts vertex data from buffers, defaults and user pointers
     * into MessageData; mainly for display use
     */
    public void glDrawArrays(MessageData msgData) {
        final Message msg = msgData.msg;
        if (!msg.hasArg7())
            return;
        final int maxAttrib = msg.getArg7();
        final int first = msg.getArg1(), count = msg.getArg2();
        msgData.attribs = new float[maxAttrib][count * 4];
        ByteBuffer arrays = null;
        if (msg.hasData()) // server sends user pointer attribs
        {
            arrays = msg.getData().asReadOnlyByteBuffer();
            arrays.order(GLFramesView.TARGET_BYTE_ORDER);
        }
        for (int i = 0; i < count; i++)
            fetch(maxAttrib, first + i, i, arrays, msgData.attribs);
        assert null == arrays || arrays.remaining() == 0;
    }

    // void glDrawElements(GLenum mode, GLsizei count, GLenum type, const
    // GLvoid* indices)
    /**
     * fetches and converts vertex data from buffers, defaults and user pointers
     * and indices from buffer/pointer into MessageData; mainly for display use
     */
    public void glDrawElements(MessageData msgData) {
        final Message msg = msgData.msg;
        if (!msg.hasArg7())
            return;
        final int maxAttrib = msg.getArg7();
        final int count = msg.getArg1();
        final GLEnum type = GLEnum.valueOf(msg.getArg2());
        msgData.attribs = new float[maxAttrib][count * 4];
        msgData.indices = new short[count];
        ByteBuffer arrays = null, index = null;
        if (msg.hasData()) // server sends user pointer attribs
        {
            arrays = msg.getData().asReadOnlyByteBuffer();
            arrays.order(GLFramesView.TARGET_BYTE_ORDER);
        }
        if (null == indexBuffer)
            index = arrays; // server also interleaves user pointer indices
        else {
            index = indexBuffer.data;
            index.position(msg.getArg3());
        }
        if (GLEnum.GL_UNSIGNED_SHORT == type) {
            for (int i = 0; i < count; i++) {
                msgData.indices[i] = index.getShort();
                fetch(maxAttrib, msgData.indices[i] & 0xffff, i, arrays, msgData.attribs);
            }
        } else if (GLEnum.GL_UNSIGNED_BYTE == type) {
            for (int i = 0; i < count; i++) {
                msgData.indices[i] = (short) (index.get() & 0xff);
                fetch(maxAttrib, msgData.indices[i], i, arrays, msgData.attribs);
            }
        } else
            assert false;
        assert null == arrays || arrays.remaining() == 0;
    }

    // void glEnableVertexAttribArray(GLuint index)
    public void glEnableVertexAttribArray(Message msg) {
        if (msg.getArg0() >= 0 && msg.getArg0() < attribPointers.length)
            attribPointers[msg.getArg0()].enabled = true;
    }

    // void API_ENTRY(glGenBuffers)(GLsizei n, GLuint:n:out buffers)
    public void glGenBuffers(Message msg) {
        final int n = msg.getArg0();
        final ByteBuffer buffer = msg.getData().asReadOnlyByteBuffer();
        buffer.order(GLFramesView.TARGET_BYTE_ORDER);
        for (int i = 0; i < n; i++) {
            final int name = buffer.getInt();
            final int index = buffers.indexOfKey(name);
            if (index < 0)
                buffers.append(name, new GLBuffer(name));
        }
    }

    // void glVertexAttribPointer(GLuint index, GLint size, GLenum type,
    // GLboolean normalized, GLsizei stride, const GLvoid* ptr)
    public void glVertexAttribPointer(Message msg) {
        GLAttribPointer attrib = attribPointers[msg.getArg0()];
        attrib.size = msg.getArg1();
        attrib.type = GLEnum.valueOf(msg.getArg2());
        attrib.normalized = msg.getArg3() != 0;
        attrib.stride = msg.getArg4();
        attrib.elemSize = attrib.size * typeSize(attrib.type);
        if (attrib.stride == 0)
            attrib.elemStride = attrib.elemSize;
        else
            attrib.elemStride = attrib.stride;
        attrib.ptr = msg.getArg5();
        attrib.buffer = attribBuffer;
    }

    // void glVertexAttrib1f(GLuint indx, GLfloat x)
    public void glVertexAttrib1f(Message msg) {
        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
                0, 0, 1);
    }

    // void glVertexAttrib1fv(GLuint indx, const GLfloat* values)
    public void glVertexAttrib1fv(Message msg) {
        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
        values.order(GLFramesView.TARGET_BYTE_ORDER);
        glVertexAttrib4f(msg.getArg0(),
                Float.intBitsToFloat(values.getInt()),
                0, 0, 1);
    }

    // void glVertexAttrib2f(GLuint indx, GLfloat x, GLfloat y)
    public void glVertexAttrib2f(Message msg) {
        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
                Float.intBitsToFloat(msg.getArg2()), 0, 1);
    }

    // void glVertexAttrib2fv(GLuint indx, const GLfloat* values)
    public void glVertexAttrib2fv(Message msg) {
        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
        values.order(GLFramesView.TARGET_BYTE_ORDER);
        glVertexAttrib4f(msg.getArg0(),
                Float.intBitsToFloat(values.getInt()),
                Float.intBitsToFloat(values.getInt()), 0, 1);
    }

    // void glVertexAttrib3f(GLuint indx, GLfloat x, GLfloat y, GLfloat z)
    public void glVertexAttrib3f(Message msg) {
        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
                Float.intBitsToFloat(msg.getArg2()),
                Float.intBitsToFloat(msg.getArg3()), 1);
    }

    // void glVertexAttrib3fv(GLuint indx, const GLfloat* values)
    public void glVertexAttrib3fv(Message msg) {
        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
        values.order(GLFramesView.TARGET_BYTE_ORDER);
        glVertexAttrib4f(msg.getArg0(),
                Float.intBitsToFloat(values.getInt()),
                Float.intBitsToFloat(values.getInt()),
                Float.intBitsToFloat(values.getInt()), 1);
    }

    public void glVertexAttrib4f(Message msg) {
        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
                Float.intBitsToFloat(msg.getArg2()),
                Float.intBitsToFloat(msg.getArg3()),
                Float.intBitsToFloat(msg.getArg4()));
    }

    void glVertexAttrib4f(int indx, float x, float y, float z, float w) {
        if (indx < 0 || indx >= defaultAttribs.length)
            return;
        defaultAttribs[indx][0] = x;
        defaultAttribs[indx][1] = y;
        defaultAttribs[indx][2] = z;
        defaultAttribs[indx][3] = w;
    }

    // void glVertexAttrib4fv(GLuint indx, const GLfloat* values)
    public void glVertexAttrib4fv(Message msg) {
        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
        values.order(GLFramesView.TARGET_BYTE_ORDER);
        glVertexAttrib4f(msg.getArg0(),
                Float.intBitsToFloat(values.getInt()),
                Float.intBitsToFloat(values.getInt()),
                Float.intBitsToFloat(values.getInt()),
                Float.intBitsToFloat(values.getInt()));
    }
}
