/*
 ** 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.ide.eclipse.gldebugger.DebuggerMessage.Message.Function;
import com.android.sdklib.util.SparseIntArray;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Shell;

import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;

public class CodeGen implements IRunnableWithProgress {
    private FileWriter codeFile, makeFile, namesHeaderFile, namesSourceFile;
    private PrintWriter code, make, namesHeader, namesSource;
    private FileOutputStream dataOut;
    private SparseIntArray bufferNames,
            framebufferNames, programNames, textureNames, shaderNames, renderbufferNames;

    /** return true if msg was a texture upload */
    private boolean codeGenTextureUpload(final Message msg, final boolean replaceCopy) {
        String s = null;
        switch (msg.getFunction()) {
            case glCompressedTexImage2D:
                s = MessageFormatter.format(msg, true).replace("arg7", "texData");
                break;
            case glCompressedTexSubImage2D:
            case glTexImage2D:
            case glTexSubImage2D:
                s = MessageFormatter.format(msg, true).replace("arg8", "texData");
                break;
            case glCopyTexImage2D:
                if (!replaceCopy) {
                    code.write(MessageFormatter.format(msg, true));
                    code.write(";CHKERR;\n");
                    return true;
                }
                assert msg.getArg2() == msg.getPixelFormat(); // TODO
                s = "//" + MessageFormatter.format(msg, true) + "\n";
                s += String.format("glTexImage2D(%s, %d, %s, %d, %d, %d, %s, %s, texData);CHKERR;",
                        GLEnum.valueOf(msg.getArg0()), msg.getArg1(),
                        GLEnum.valueOf(msg.getArg2()), msg.getArg5(), msg.getArg6(),
                        msg.getArg7(), GLEnum.valueOf(msg.getPixelFormat()),
                        GLEnum.valueOf(msg.getPixelType()));
                break;
            case glCopyTexSubImage2D:
                if (!replaceCopy) {
                    code.write(MessageFormatter.format(msg, true));
                    code.write(";CHKERR;\n");
                    return true;
                }
                // FIXME: check the texture format & type, and convert
                s = "//" + MessageFormatter.format(msg, true) + "\n";
                s += String.format(
                        "glTexSubImage2D(%s, %d, %d, %d, %d, %d, %s, %s, texData);CHKERR;",
                        GLEnum.valueOf(msg.getArg0()), msg.getArg1(), msg.getArg2(),
                        msg.getArg3(), msg.getArg6(), msg.getArg7(),
                        GLEnum.valueOf(msg.getPixelFormat()), GLEnum.valueOf(msg.getPixelType()));
                break;
            default:
                return false;
        }

        if (msg.hasData()) {
            final byte[] data = MessageProcessor.lzfDecompressChunks(msg.getData());
            try {
                code.write("{\n");
                code.format("    void * texData = malloc(%d);CHKERR;\n", data.length);
                code.format("    FILE * texFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
                code.format("    assert(texFile);CHKERR;\n");
                code.format("    fseek(texFile, %d, SEEK_SET);CHKERR;\n", dataOut.getChannel()
                        .position());
                dataOut.write(data);
                code.format("    fread(texData, %d, 1, texFile);CHKERR;\n", data.length);
                code.format("    fclose(texFile);CHKERR;\n");
                code.format("    " + s + ";\n");
                code.format("    free(texData);CHKERR;\n");
                code.format("}\n");
            } catch (IOException e) {
                e.printStackTrace();
                assert false;
            }
        } else
            code.write(s.replace("texData", "NULL") + ";\n");
        return true;
    }

    private void codeGenServerState(final GLServerState serverState) {
        code.write("// CodeGenServerState\n");
        for (int i = 0; i < serverState.enableDisables.size(); i++) {
            final GLEnum key = GLEnum.valueOf(serverState.enableDisables.keyAt(i));
            if (serverState.enableDisables.valueAt(i) == 0)
                code.format("glDisable(%s);CHKERR;\n", key);
            else
                code.format("glEnable(%s);CHKERR;\n", key);
        }
        for (int i = 0; i < serverState.lastSetter.size(); i++) {
            final Function key = Function.valueOf(serverState.lastSetter.keyAt(i));
            final Message msg = serverState.lastSetter.valueAt(i);
            if (msg == null) {
                code.format("// %s is default\n", key);
                continue;
            }
            final String s = MessageFormatter.format(msg, true);
            code.write(s);
            code.write(";\n");
        }
        // TODO: stencil and integers
    }

    private void codeGenServerShader(final GLServerShader serverShader) {
        code.write("// CodeGenServerShader\n");
        for (int i = 0; i < serverShader.shaders.size(); i++) {
            final int name = serverShader.shaders.keyAt(i);
            final GLShader shader = serverShader.shaders.valueAt(i);
            final String id = "shader_" + name;
            if (shaderNames.indexOfKey(name) < 0) {
                namesSource.format("GLuint %s = 0;\n", id);
                namesHeader.format("extern GLuint %s;\n", id);
            }
            code.format("%s = glCreateShader(%s);CHKERR;\n", id, shader.type);
            shaderNames.put(name, name);

            if (shader.source != null) {
                final String src = shader.source.replace("\r", "").replace("\n", "\\n\\\n")
                        .replace("\"", "\\\"");
                code.format("glShaderSource(%s, 1, (const GLchar *[]){\"%s\"}, NULL);CHKERR;\n",
                                id, src);
                code.format("glCompileShader(%s);CHKERR;\n", id);
            }
        }

        for (int i = 0; i < serverShader.programs.size(); i++) {
            final int name = serverShader.programs.keyAt(i);
            final GLProgram program = serverShader.programs.valueAt(i);
            final String id = "program_" + name;
            if (programNames.indexOfKey(name) < 0) {
                namesSource.format("GLuint %s = 0;\n", id);
                namesHeader.format("extern GLuint %s;\n", id);
            }
            code.format("%s = glCreateProgram();CHKERR;\n", id);
            programNames.put(name, name);
            code.format("glAttachShader(%s, shader_%d);CHKERR;\n", id,
                    program.vert);
            code.format("glAttachShader(%s, shader_%d);CHKERR;\n", id,
                    program.frag);
            code.format("glLinkProgram(%s);CHKERR;\n", id);
            if (serverShader.current == program)
                code.format("glUseProgram(%s);CHKERR;\n", id);
        }
    }

    private void codeGenServerTexture(final GLServerTexture serverTexture, final boolean replaceCopy) {
        code.write("// CodeGenServerTexture\n");
        for (int i = 0; i < serverTexture.textures.size(); i++) {
            final int name = serverTexture.textures.keyAt(i);
            final GLTexture tex = serverTexture.textures.valueAt(i);
            final String id = "texture_" + name;
            if (textureNames.indexOfKey(name) < 0) {
                namesHeader.format("extern GLuint %s;\n", id);
                namesSource.format("GLuint %s = 0;\n", id);
            }
            code.format("%s = 0;\n", id);
            textureNames.put(name, name);

            if (name == 0)
                continue;
            code.format("glGenTextures(1, &%s);CHKERR;\n", id);
            String s = String.format("glBindTexture(%s, texture_%d);CHKERR;\n", tex.target,
                    tex.name);
            code.write(s);
            for (final Message msg : tex.contentChanges) {
                if (codeGenTextureUpload(msg, replaceCopy))
                    continue;
                switch (msg.getFunction()) {
                    case glGenerateMipmap:
                        s = MessageFormatter.format(msg, true);
                        break;
                    default:
                        assert false;
                }
                code.write(s + ";\n");
            }
            code.format("glTexParameteriv(%s, GL_TEXTURE_WRAP_S, (GLint[]){%s});CHKERR;\n",
                    tex.target, tex.wrapS);
            code.format("glTexParameteriv(%s, GL_TEXTURE_WRAP_T, (GLint[]){%s});CHKERR;\n",
                    tex.target, tex.wrapT);
            code.format("glTexParameteriv(%s, GL_TEXTURE_MIN_FILTER, (GLint[]){%s});CHKERR;\n",
                    tex.target, tex.min);
            code.format("glTexParameteriv(%s, GL_TEXTURE_MAG_FILTER, (GLint[]){%s});CHKERR;\n",
                    tex.target, tex.mag);
        }
        for (int i = 0; i < serverTexture.tmu2D.length; i++) {
            code.format("glActiveTexture(%s);CHKERR;\n",
                    GLEnum.valueOf(GLEnum.GL_TEXTURE0.value + i));
            code.format("glBindTexture(GL_TEXTURE_2D, texture_%d);CHKERR;\n",
                    serverTexture.tmu2D[i]);
        }
        for (int i = 0; i < serverTexture.tmuCube.length; i++) {
            code.format("glActiveTexture(%s);CHKERR;\n",
                    GLEnum.valueOf(GLEnum.GL_TEXTURE0.value + i));
            code.format("glBindTexture(GL_TEXTURE_CUBE_MAP, texture_%d);CHKERR;\n",
                    serverTexture.tmuCube[i]);
        }
        code.format("glActiveTexture(%s);CHKERR;\n", serverTexture.activeTexture);
        if (serverTexture.tex2D == null)
            code.format("glBindTexture(GL_TEXTURE_2D, 0);CHKERR;\n");
        else
            code.format("glBindTexture(GL_TEXTURE_2D, texture_%d);CHKERR;\n",
                    serverTexture.tex2D.name);
        if (serverTexture.texCube == null)
            code.format("glBindTexture(GL_TEXTURE_CUBE_MAP, 0);CHKERR;\n");
        else
            code.format("glBindTexture(GL_TEXTURE_CUBE_MAP, texture_%d);CHKERR;\n",
                    serverTexture.texCube.name);
    }

    private void codeGenBufferData(final ByteBuffer buffer, final String call) {
        ByteBuffer bfr = buffer;
        if (buffer.isReadOnly()) {
            bfr = ByteBuffer.allocate(buffer.capacity());
            bfr.put(buffer);
        }
        final byte[] data = bfr.array();
        try {
            code.write("{\n");
            code.format("    void * bufferData = malloc(%d);\n", data.length);
            code.format("    FILE * bufferFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");\n");
            code.format("    assert(bufferFile);\n");
            code.format("    fseek(bufferFile, %d, SEEK_SET);\n", dataOut.getChannel()
                    .position());
            dataOut.write(data);
            code.format("    fread(bufferData, %d, 1, bufferFile);\n", data.length);
            code.format("    fclose(bufferFile);\n");
            code.format("    " + call + ";CHKERR;\n");
            code.format("    free(bufferData);\n");
            code.format("}\n");
        } catch (IOException e) {
            e.printStackTrace();
            assert false;
        }
    }

    private void codeGenServerVertex(final GLServerVertex v) {
        code.write("// CodeGenServerVertex\n");
        for (int i = 0; i < v.buffers.size(); i++) {
            final int name = v.buffers.keyAt(i);
            final String id = "buffer_" + name;
            final GLBuffer buffer = v.buffers.valueAt(i);
            if (bufferNames.indexOfKey(name) < 0) {
                namesHeader.format("extern GLuint %s;\n", id);
                namesSource.format("GLuint %s = 0;\n", id);
            }
            code.format("%s = 0;\n", id);
            bufferNames.put(name, name);
            if (name == 0)
                continue;
            code.format("glGenBuffers(1, &%s);CHKERR;\n", id);
            if (buffer.target != null) {
                code.format("glBindBuffer(%s, %s);CHKERR;\n", buffer.target, id);
                if (buffer.data != null) {
                    String s = String.format("glBufferData(%s, %d, bufferData, %s)", buffer.target,
                            buffer.data.capacity(), buffer.usage);
                    codeGenBufferData(buffer.data, s);
                }
            }
        }
        // TODO: use MAX_VERTEX_ATTRIBS
        for (int i = 0; i < v.defaultAttribs.length; i++)
            code.format("glVertexAttrib4f(%d, %f, %f, %f, %f);CHKERR;\n", i,
                    v.defaultAttribs[i][0],
                    v.defaultAttribs[i][1], v.defaultAttribs[i][2], v.defaultAttribs[i][3]);
        for (int i = 0; i < v.attribPointers.length; i++) {
            final GLAttribPointer att = v.attribPointers[i];
            if (att.type == null)
                continue;
            if (att.buffer != null)
                code.format("glBindBuffer(GL_ARRAY_BUFFER, buffer_%d);CHKERR;\n", att.buffer.name);
            else
                code.format("glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
            code.format("glVertexAttribPointer(%d, %d, %s, %b, %d, (const GLvoid *)%d);CHKERR;\n",
                    i, att.size, att.type, att.normalized, att.stride, att.ptr);
        }
        if (v.attribBuffer != null)
            code.format("glBindBuffer(GL_ARRAY_BUFFER, buffer_%d);CHKERR;\n", v.attribBuffer.name);
        else
            code.write("glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
        if (v.indexBuffer != null)
            code.format("glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_%d);CHKERR;\n",
                    v.indexBuffer.name);
        else
            code.write("glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);CHKERR;\n");
    }

    private void codeGenGenNames(final Message msg) {
        final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
        names.order(GLFramesView.TARGET_BYTE_ORDER);
        SparseIntArray namesArray = null;
        for (int i = 0; i < msg.getArg0(); i++) {
            String id = "";
            final int name = names.getInt();
            switch (msg.getFunction()) {
                case glGenBuffers:
                    id = "buffer";
                    namesArray = bufferNames;
                    break;
                case glGenFramebuffers:
                    id = "framebuffer";
                    namesArray = framebufferNames;
                    break;
                case glGenRenderbuffers:
                    id = "renderbuffer";
                    namesArray = renderbufferNames;
                    break;
                case glGenTextures:
                    id = "texture";
                    namesArray = textureNames;
                    break;
                default:
                    assert false;
            }
            id += "_" + name;
            if (namesArray.indexOfKey(name) < 0) {
                namesHeader.format("extern GLuint %s;\n", id);
                namesSource.format("GLuint %s = 0;\n", id);
            }
            code.format("%s = 0;\n", id);
            namesArray.put(name, name);
            code.format("%s(1, &%s);CHKERR;\n", msg.getFunction(), id);
        }
    }

    private void codeGenDeleteNames(final Message msg) {
        final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
        names.order(GLFramesView.TARGET_BYTE_ORDER);
        SparseIntArray namesArray = null;
        for (int i = 0; i < msg.getArg0(); i++) {
            String id = null;
            final int name = names.getInt();
            switch (msg.getFunction()) {
                case glDeleteBuffers:
                    id = "buffer";
                    namesArray = bufferNames;
                    break;
                case glDeleteFramebuffers:
                    id = "framebuffer";
                    namesArray = framebufferNames;
                    break;
                case glDeleteRenderbuffers:
                    id = "renderbuffer";
                    namesArray = renderbufferNames;
                    break;
                case glDeleteTextures:
                    id = "texture";
                    namesArray = textureNames;
                    break;
                default:
                    assert false;
            }
            id += "_" + name;
            code.format("%s = 0;\n", id);
            namesArray.put(name, 0);
            code.format("%s(1, &%s);CHKERR;\n", msg.getFunction(), id);
        }
    }

    private void codeGenBindNames(final Message msg) {
        String id = null;
        SparseIntArray namesArray = null;
        final int name = msg.getArg1();
        switch (msg.getFunction()) {
            case glBindBuffer:
                id = "buffer";
                namesArray = bufferNames;
                break;
            case glBindFramebuffer:
                id = "framebuffer";
                namesArray = framebufferNames;
                break;
            case glBindRenderbuffer:
                id = "renderbuffer";
                namesArray = renderbufferNames;
                break;
            case glBindTexture:
                id = "texture";
                namesArray = textureNames;
                break;
            default:
                assert false;
        }
        id += "_" + name;
        if (namesArray.indexOfKey(name) < 0) {
            namesHeader.format("extern GLuint %s;\n", id);
            namesSource.format("GLuint %s = 0;\n", id);
        } else if (namesArray.get(name) != name)
            code.format("%s = %d;\n", id, name); // name was deleted
        namesArray.put(name, name);
        code.write(MessageFormatter.format(msg, true));
        code.write(";CHKERR;\n");
    }

    private void codeGenDrawArrays(final GLServerVertex v, final MessageData msgData)
            throws IOException {
        final int maxAttrib = msgData.msg.getArg7();
        if (maxAttrib < 1) {
            code.write("// no vertex data\n");
            return;
        }
        final byte[] data = msgData.msg.getData().toByteArray();
        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
        final int first = msgData.msg.getArg1(), count = msgData.msg.getArg2();
        int attribDataStride = 0;
        for (int i = 0; i < maxAttrib; i++) {
            final GLAttribPointer att = v.attribPointers[i];
            if (!att.enabled)
                continue;
            if (att.buffer != null)
                continue;
            attribDataStride += att.elemSize;
        }
        assert attribDataStride * count == data.length;
        code.write("{\n");
        if (attribDataStride > 0) {
            code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
            code.format("    assert(attribFile);CHKERR;\n");
            code.format("    fseek(attribFile, %d, SEEK_SET);CHKERR;\n", dataOut.getChannel()
                    .position());
            dataOut.write(data);
            code.format("    char * const attribData = (char *)malloc(%d);\n", first
                    * attribDataStride + data.length);
            code.format("    assert(attribData);\n");
            code.format("    fread(attribData + %d, %d, 1, attribFile);\n",
                    first * attribDataStride, data.length);
            code.format("    fclose(attribFile);\n");
            code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
            int attribDataOffset = 0;
            for (int i = 0; i < maxAttrib; i++) {
                final GLAttribPointer att = v.attribPointers[i];
                if (!att.enabled)
                    continue;
                if (att.buffer != null)
                    continue;
                code.format(
                        "    glVertexAttribPointer(%d, %d, %s, %b, %d, attribData + %d);CHKERR;\n",
                        i, att.size, att.type, att.normalized,
                        attribDataStride, attribDataOffset);
                attribDataOffset += att.elemSize;
            }
            if (v.attribBuffer != null)
                code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
                        v.attribBuffer.name);
        }
        code.format("    glDrawArrays(%s, %d, %d);CHKERR;\n", mode, first, count);
        if (attribDataStride > 0)
            code.format("    free(attribData);CHKERR;\n");
        code.write("};\n");
    }

    private void codeGenDrawElements(final GLServerVertex v, final MessageData msgData)
            throws IOException {
        final int maxAttrib = msgData.msg.getArg7();
        if (maxAttrib < 1) {
            code.write("// no vertex data\n");
            return;
        }
        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
        final int count = msgData.msg.getArg1();
        final GLEnum type = GLEnum.valueOf(msgData.msg.getArg2());
        String typeName = "GLubyte";
        if (type == GLEnum.GL_UNSIGNED_SHORT)
            typeName = "GLushort";
        int attribDataStride = 0;
        for (int i = 0; i < maxAttrib; i++) {
            final GLAttribPointer att = v.attribPointers[i];
            if (!att.enabled)
                continue;
            if (att.buffer != null)
                continue;
            attribDataStride += att.elemSize;
        }
        code.write("{\n");
        if (v.indexBuffer == null || attribDataStride > 0) {
            // need to load user pointer indices and/or attributes
            final byte[] element = new byte[attribDataStride];
            final ByteBuffer data = msgData.msg.getData().asReadOnlyByteBuffer();
            data.order(GLFramesView.TARGET_BYTE_ORDER);
            final ByteBuffer indexData = ByteBuffer.allocate(count * GLServerVertex.typeSize(type));
            indexData.order(GLFramesView.TARGET_BYTE_ORDER);
            final ByteBuffer attribData = ByteBuffer.allocate(count * attribDataStride);
            attribData.order(GLFramesView.TARGET_BYTE_ORDER);
            int maxIndex = -1;
            ByteBuffer indexSrc = data;
            if (v.indexBuffer != null) {
                indexSrc = v.indexBuffer.data;
                indexSrc.position(msgData.msg.getArg3());
            }
            indexSrc.order(GLFramesView.TARGET_BYTE_ORDER);
            for (int i = 0; i < count; i++) {
                int index = -1;
                if (type == GLEnum.GL_UNSIGNED_BYTE) {
                    byte idx = indexSrc.get();
                    index = idx & 0xff;
                    indexData.put(idx);
                } else if (type == GLEnum.GL_UNSIGNED_SHORT) {
                    short idx = indexSrc.getShort();
                    index = idx & 0xffff;
                    indexData.putShort(idx);
                } else
                    assert false;
                data.get(element);
                attribData.put(element);
                if (index > maxIndex)
                    maxIndex = index;
            }
            code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
            code.format("    assert(attribFile);CHKERR;\n");
            code.format("    fseek(attribFile, 0x%X, SEEK_SET);CHKERR;\n",
                    dataOut.getChannel().position());
            dataOut.write(indexData.array());
            code.format("    %s * const indexData = (%s *)malloc(%d);\n", typeName, typeName,
                    indexData.capacity());
            code.format("    assert(indexData);\n");
            code.format("    fread(indexData, %d, 1, attribFile);\n", indexData.capacity());
            if (attribDataStride > 0) {
                code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
                for (int i = 0; i < maxAttrib; i++) {
                    final GLAttribPointer att = v.attribPointers[i];
                    if (!att.enabled)
                        continue;
                    if (att.buffer != null)
                        continue;
                    code.format("    char * const attrib%d = (char *)malloc(%d);\n",
                            i, att.elemSize * (maxIndex + 1));
                    code.format("    assert(attrib%d);\n", i);
                    code.format(
                            "    glVertexAttribPointer(%d, %d, %s, %b, %d, attrib%d);CHKERR;\n",
                            i, att.size, att.type, att.normalized, att.elemSize, i);
                }
                dataOut.write(attribData.array());
                code.format("    for (%s i = 0; i < %d; i++) {\n", typeName, count);
                for (int i = 0; i < maxAttrib; i++) {
                    final GLAttribPointer att = v.attribPointers[i];
                    if (!att.enabled)
                        continue;
                    if (att.buffer != null)
                        continue;
                    code.format(
                            "        fread(attrib%d + indexData[i] * %d, %d, 1, attribFile);\n",
                            i, att.elemSize, att.elemSize);
                }
                code.format("    }\n");
                if (v.attribBuffer != null)
                    code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
                            v.attribBuffer.name);
            }
            code.format("    fclose(attribFile);\n");
        }
        if (v.indexBuffer != null)
            code.format("    glDrawElements(%s, %d, %s, (const void *)%d);CHKERR;\n",
                    mode, count, type, msgData.msg.getArg3());
        else {
            code.format("    glDrawElements(%s, %d, %s, indexData);CHKERR;\n",
                    mode, count, type);
            code.format("    free(indexData);\n");
        }
        for (int i = 0; i < maxAttrib; i++) {
            final GLAttribPointer att = v.attribPointers[i];
            if (!att.enabled)
                continue;
            if (att.buffer != null)
                continue;
            code.format("    free(attrib%d);\n", i);
        }
        code.write("};\n");
    }

    private void codeGenDraw(final GLServerVertex v, final MessageData msgData)
            throws IOException {
        final int maxAttrib = msgData.msg.getArg7();
        if (maxAttrib < 1) {
            code.write("// no vertex data\n");
            return;
        }
        final int count = msgData.attribs[0].length / 4;
        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
        final ByteBuffer attribData = ByteBuffer.allocate(maxAttrib * count * 16);
        attribData.order(GLFramesView.TARGET_BYTE_ORDER);
        for (int i = 0; i < count; i++)
            for (int j = 0; j < maxAttrib; j++)
                for (int k = 0; k < 4; k++)
                    attribData.putFloat(msgData.attribs[j][i * 4 + k]);
        assert attribData.remaining() == 0;
        code.write("{\n");
        code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
        code.format("    assert(attribFile);CHKERR;\n");
        code.format("    fseek(attribFile, 0x%X, SEEK_SET);CHKERR;\n",
                dataOut.getChannel().position());
        dataOut.write(attribData.array());
        code.format("    char * const attribData = (char *)malloc(%d);\n", attribData.capacity());
        code.format("    assert(attribData);\n");
        code.format("    fread(attribData, %d, 1, attribFile);\n", attribData.capacity());
        code.format("    fclose(attribFile);\n");
        code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
        for (int i = 0; i < maxAttrib; i++) {
            final GLAttribPointer att = v.attribPointers[i];
            assert msgData.attribs[i].length == count * 4;
            code.format(
                    "    glVertexAttribPointer(%d, %d, GL_FLOAT, GL_FALSE, %d, attribData + %d);CHKERR;\n",
                        i, att.size, maxAttrib * 16, i * 16);
        }
        code.format("    glDrawArrays(%s, 0, %d);CHKERR;\n", mode, count);
        code.format("    free(attribData);\n");
        if (v.attribBuffer != null)
            code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
                        v.attribBuffer.name);
        code.write("};\n");
    }

    private void codeGenFunction(final Context ctx, final MessageData msgData)
            throws IOException {
        final Message msg = msgData.msg;
        String call = MessageFormatter.format(msg, true);
        switch (msg.getFunction()) {
            case glActiveTexture:
            case glAttachShader:
            case glBindAttribLocation:
                break;
            case glBindBuffer:
            case glBindFramebuffer:
            case glBindRenderbuffer:
            case glBindTexture:
                codeGenBindNames(msg);
                return;
            case glBlendColor:
            case glBlendEquation:
            case glBlendEquationSeparate:
            case glBlendFunc:
            case glBlendFuncSeparate:
                break;
            case glBufferData:
                call = MessageFormatter.format(msg, true).replace("arg2", "bufferData");
                codeGenBufferData(msg.getData().asReadOnlyByteBuffer(), call);
                return;
            case glBufferSubData:
                call = MessageFormatter.format(msg, true).replace("arg3", "bufferData");
                codeGenBufferData(msg.getData().asReadOnlyByteBuffer(), call);
                return;
            case glCheckFramebufferStatus:
            case glClear:
            case glClearColor:
            case glClearDepthf:
            case glClearStencil:
            case glColorMask:
            case glCompileShader:
                break;
            case glCompressedTexImage2D:
            case glCompressedTexSubImage2D:
            case glCopyTexImage2D:
            case glCopyTexSubImage2D:
                codeGenTextureUpload(msg, false);
                return;
            case glCreateProgram:
                namesHeader.format("extern GLuint program_%d;\n", msg.getRet());
                namesSource.format("GLuint program_%d = 0;\n", msg.getRet());
                code.format("program_%d = glCreateProgram();CHKERR;\n", msg.getRet());
                return;
            case glCreateShader:
                namesHeader.format("extern GLuint shader_%d;\n", msg.getRet());
                namesSource.format("GLuint shader_%d = 0;\n", msg.getRet());
                code.format("shader_%d = %s;\n", msg.getRet(), call);
                return;
            case glCullFace:
                break;
            case glDeleteBuffers:
            case glDeleteFramebuffers:
            case glDeleteProgram:
                programNames.put(msg.getArg0(), 0);
                break;
            case glDeleteRenderbuffers:
                codeGenDeleteNames(msg);
                return;
            case glDeleteShader:
                shaderNames.put(msg.getArg0(), 0);
                return;
            case glDeleteTextures:
                codeGenDeleteNames(msg);
                return;
            case glDepthFunc:
            case glDepthMask:
            case glDepthRangef:
            case glDetachShader:
            case glDisable:
            case glDisableVertexAttribArray:
                break;
            case glDrawArrays:
                // CodeGenDraw(ctx.serverVertex, msgData);
                codeGenDrawArrays(ctx.serverVertex, msgData);
                return;
            case glDrawElements:
                // CodeGenDraw(ctx.serverVertex, msgData);
                codeGenDrawElements(ctx.serverVertex, msgData);
                return;
            case glEnable:
            case glEnableVertexAttribArray:
            case glFinish:
            case glFlush:
            case glFramebufferRenderbuffer:
            case glFramebufferTexture2D:
            case glFrontFace:
                break;
            case glGenBuffers:
                codeGenGenNames(msg);
                return;
            case glGenerateMipmap:
                break;
            case glGenFramebuffers:
            case glGenRenderbuffers:
            case glGenTextures:
                codeGenGenNames(msg);
                return;
            case glGetActiveAttrib:
            case glGetActiveUniform:
            case glGetAttachedShaders:
                break;
            case glGetAttribLocation:
                call = String.format("assert(%d == %s)", msg.getRet(), call);
                break;
            case glGetBooleanv:
            case glGetBufferParameteriv:
                return; // TODO
            case glGetError:
                code.write("CHKERR;\n");
                return;
            case glGetFloatv:
            case glGetFramebufferAttachmentParameteriv:
            case glGetIntegerv:
            case glGetProgramiv:
            case glGetProgramInfoLog:
            case glGetRenderbufferParameteriv:
            case glGetShaderiv:
            case glGetShaderInfoLog:
            case glGetShaderPrecisionFormat:
            case glGetShaderSource:
            case glGetString:
            case glGetTexParameterfv:
            case glGetTexParameteriv:
            case glGetUniformfv:
            case glGetUniformiv:
                return;
            case glGetUniformLocation:
                call = String.format("assert(%d == %s)", msg.getRet(), call);
                break;
            case glGetVertexAttribfv:
            case glGetVertexAttribiv:
            case glGetVertexAttribPointerv:
                return; // TODO
            case glHint:
            case glIsBuffer:
            case glIsEnabled:
            case glIsFramebuffer:
            case glIsProgram:
            case glIsRenderbuffer:
            case glIsShader:
            case glIsTexture:
            case glLineWidth:
            case glLinkProgram:
            case glPixelStorei:
            case glPolygonOffset:
                break;
            case glReadPixels:
                return; // TODO
            case glReleaseShaderCompiler:
            case glRenderbufferStorage:
            case glSampleCoverage:
            case glScissor:
                break;
            case glShaderBinary:
                return; // TODO
            case glShaderSource:
                call = String.format(
                        "glShaderSource(shader_%d, 1, (const char * []){\"%s\"}, NULL)",
                        msg.getArg0(),
                        msg.getData().toStringUtf8().replace("\r", "").replace("\n", "\\n\\\n")
                                .replace("\"", "\\\"")
                                );
                break;
            case glStencilFunc:
            case glStencilFuncSeparate:
            case glStencilMask:
            case glStencilMaskSeparate:
            case glStencilOp:
            case glStencilOpSeparate:
                break;
            case glTexImage2D:
                codeGenTextureUpload(msg, false);
                return;
            case glTexParameterf:
                break;
            case glTexParameterfv:
                return; // TODO
            case glTexParameteri:
                break;
            case glTexParameteriv:
                return; // TODO
            case glTexSubImage2D:
                codeGenTextureUpload(msg, false);
                return;
            case glUniform1f:
            case glUniform1fv:
            case glUniform1i:
            case glUniform1iv:
            case glUniform2f:
            case glUniform2fv:
            case glUniform2i:
            case glUniform2iv:
            case glUniform3f:
            case glUniform3fv:
            case glUniform3i:
            case glUniform3iv:
            case glUniform4f:
            case glUniform4fv:
            case glUniform4i:
            case glUniform4iv:
            case glUniformMatrix2fv:
            case glUniformMatrix3fv:
            case glUniformMatrix4fv:
            case glUseProgram:
            case glValidateProgram:
            case glVertexAttrib1f:
            case glVertexAttrib1fv:
            case glVertexAttrib2f:
            case glVertexAttrib2fv:
            case glVertexAttrib3f:
            case glVertexAttrib3fv:
            case glVertexAttrib4f:
            case glVertexAttrib4fv:
                break;
            case glVertexAttribPointer:
                // if it's user pointer, then CodeGenDrawArrays/Elements will
                // replace it with loaded data just before the draw
                call = call.replace("arg5", "(const void *)0x" +
                        Integer.toHexString(msg.getArg5()));
                break;
            case glViewport:
                break;
            case eglSwapBuffers:
                return;
            default:
                assert false;
                return;
        }
        if (call.indexOf("glEnable(/*cap*/ GL_TEXTURE_2D)") >= 0)
            return;
        else if (call.indexOf("glDisable(/*cap*/ GL_TEXTURE_2D)") >= 0)
            return;
        else if (call.indexOf("glActiveTexture(/*texture*/ GL_TEXTURE_2D)") >= 0)
            return;
        code.write(call + ";CHKERR;\n");
    }

    private void codeGenSetup(final Context ctx) {
        try {
            codeFile = new FileWriter("frame_setup.cpp", false);
            code = new PrintWriter(codeFile);
            dataOut = new FileOutputStream("frame_data.bin", false);
            namesHeaderFile = new FileWriter("frame_names.h", false);
            namesHeader = new PrintWriter(namesHeaderFile);
            namesSourceFile = new FileWriter("frame_names.cpp", false);
            namesSource = new PrintWriter(namesSourceFile);
        } catch (IOException e) {
            e.printStackTrace();
            assert false;
        }
        bufferNames = new SparseIntArray();
        framebufferNames = new SparseIntArray();
        programNames = new SparseIntArray();
        textureNames = new SparseIntArray();
        shaderNames = new SparseIntArray();
        renderbufferNames = new SparseIntArray();

        namesHeader.write("#include <stdlib.h>\n");
        namesHeader.write("#include <stdio.h>\n");
        namesHeader.write("#include <assert.h>\n");
        namesHeader.write("#include <GLES2/gl2.h>\n");
        namesHeader.write("#include <GLES2/gl2ext.h>\n");
        namesHeader.write("#define CHKERR assert(GL_NO_ERROR == glGetError());/**/\n");
        namesHeader.write("void FrameSetup();\n");
        namesHeader.write("extern const unsigned int FrameCount;\n");
        namesHeader.write("extern const GLuint program_0;\n");

        namesSource.write("/*\n" +
        " * Copyright (C) 2011 The Android Open Source Project\n" +
        " *\n" +
        " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
        " * you may not use this file except in compliance with the License.\n" +
        " * You may obtain a copy of the License at\n" +
        " *\n" +
        " *      http://www.apache.org/licenses/LICENSE-2.0\n" +
        " *\n" +
        " * Unless required by applicable law or agreed to in writing, software\n" +
        " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
        " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
        " * See the License for the specific language governing permissions and\n" +
        " * limitations under the License.\n" +
        " */\n" +
        "\n" +
        "#include <stdlib.h>\n" +
        "#include <stdio.h>\n" +
        "\n" +
        "#include <EGL/egl.h>\n" +
        "#include <GLES2/gl2.h>\n" +
        "#include <GLES2/gl2ext.h>\n" +
        "\n" +
        "#include <ui/FramebufferNativeWindow.h>\n" +
        "#include <ui/EGLUtils.h>\n" +
        "\n" +
        "#include <private/ui/android_natives_priv.h>\n" +
        "\n" +
        "#include <surfaceflinger/Surface.h>\n" +
        "#include <surfaceflinger/ISurface.h>\n" +
        "#include <surfaceflinger/SurfaceComposerClient.h>\n" +
        "\n" +
        "using namespace android;\n" +
        "\n" +
        "static void checkEglError(const char* op, EGLBoolean returnVal = EGL_TRUE)\n" +
        "{\n" +
        "    if (returnVal != EGL_TRUE) {\n" +
        "        fprintf(stderr, \"%s() returned %d\\n\", op, returnVal);\n" +
        "    }\n" +
        "\n" +
        "    for (EGLint error = eglGetError(); error != EGL_SUCCESS; error\n" +
        "            = eglGetError()) {\n" +
        "        fprintf(stderr, \"after %s() eglError %s (0x%x)\\n\", op, EGLUtils::strerror(error),\n" +
        "                error);\n" +
        "    }\n" +
        "}\n" +
        "\n" +
        "static EGLDisplay dpy;\n" +
        "static EGLSurface surface;\n" +
        "\n" +
        "#include \"frame_names.h\"\n" +
        "const GLuint program_0 = 0;\n" +
        "int main(int argc, char** argv)\n" +
        "{\n" +
        "    EGLBoolean returnValue;\n" +
        "    EGLConfig myConfig = {0};\n" +
        "\n" +
        "    EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };\n" +
        "    EGLint majorVersion;\n" +
        "    EGLint minorVersion;\n" +
        "    EGLContext context;\n" +
        "    EGLint w, h;\n" +
        "\n" +
        "\n" +
        "    checkEglError(\"<init>\");\n" +
        "    dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n" +
        "    checkEglError(\"eglGetDisplay\");\n" +
        "    if (dpy == EGL_NO_DISPLAY) {\n" +
        "        printf(\"eglGetDisplay returned EGL_NO_DISPLAY.\\n\");\n" +
        "        return 0;\n" +
        "    }\n" +
        "\n" +
        "    returnValue = eglInitialize(dpy, &majorVersion, &minorVersion);\n" +
        "    checkEglError(\"eglInitialize\", returnValue);\n" +
        "    if (returnValue != EGL_TRUE) {\n" +
        "        printf(\"eglInitialize failed\\n\");\n" +
        "        return 0;\n" +
        "    }\n" +
        "\n" +
        "    sp<SurfaceComposerClient> spClient;\n" +
        "    sp<SurfaceControl> spControl;\n" +
        "    sp<Surface> spSurface;\n" +
        "\n" +
        "    // create a client to surfaceflinger\n" +
        "    spClient = new SurfaceComposerClient();\n" +
        "\n" +
        "    spControl = spClient->createSurface(getpid(), 0, 1280, 752, PIXEL_FORMAT_RGBX_8888);\n" +
        "    spClient->openTransaction();\n" +
        "    spControl->setLayer(350000);\n" +
        "    spControl->show();\n" +
        "    spClient->closeTransaction();\n" +
        "\n" +
        "    spSurface = spControl->getSurface();\n" +
        "    EGLNativeWindowType window = spSurface.get();\n" +
        "\n" +
        "    printf(\"window=%p\\n\", window);\n" +
        "    EGLint attrib_list[] = {\n" +
        "        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n" +
        "        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n" +
        "        EGL_BUFFER_SIZE, 32,\n" +
        "        EGL_RED_SIZE, 8,\n" +
        "        EGL_GREEN_SIZE, 8,\n" +
        "        EGL_BLUE_SIZE, 8,\n" +
        "        EGL_NONE\n" +
        "    };\n" +
        "\n" +
        "    EGLConfig configs[12] = {0};\n" +
        "    int num_config = -1;\n" +
        "    eglChooseConfig(dpy, attrib_list, configs, sizeof(configs) / sizeof(*configs), &num_config);\n" +
        "    printf(\"eglChooseConfig %d \\n\", num_config);\n" +
        "\n" +
        "    surface = eglCreateWindowSurface(dpy, configs[0], window, NULL);\n" +
        "    checkEglError(\"eglCreateWindowSurface\");\n" +
        "    if (surface == EGL_NO_SURFACE) {\n" +
        "        printf(\"gelCreateWindowSurface failed.\\n\");\n" +
        "        return 0;\n" +
        "    }\n" +
        "\n" +
        "    context = eglCreateContext(dpy, configs[0], EGL_NO_CONTEXT, context_attribs);\n" +
        "    checkEglError(\"eglCreateContext\");\n" +
        "    if (context == EGL_NO_CONTEXT) {\n" +
        "        printf(\"eglCreateContext failed\\n\");\n" +
        "        return 0;\n" +
        "    }\n" +
        "    printf(\"context=%p \\n\", context);\n" +
        "\n" +
        "    returnValue = eglMakeCurrent(dpy, surface, surface, context);\n" +
        "    checkEglError(\"eglMakeCurrent\", returnValue);\n" +
        "    if (returnValue != EGL_TRUE) {\n" +
        "        return 0;\n" +
        "    }\n" +
        "\n" +
        "    glClearColor(1,1,1,1);\n" +
        "    glClear(GL_COLOR_BUFFER_BIT);\n" +
        "\n" +
        "    FrameSetup();\n" +
        "    while (true)\n" +
        "        for (unsigned int i = 0; i < FrameCount; i++) {\n" +
        "            Frames[i]();\n" +
        "            eglSwapBuffers(dpy, surface);\n" +
        "            printf(\"press ENTER after Frame%d \\n\", i);\n" +
        "            getchar();\n" +
        "        }\n" +
        "\n" +
        "    return 0;\n" +
        "}");

        code.write("#include \"frame_names.h\"\n");
        code.write("void FrameSetup(){\n");

        codeGenServerState(ctx.serverState);
        codeGenServerShader(ctx.serverShader);
        codeGenServerTexture(ctx.serverTexture, true);
        codeGenServerVertex(ctx.serverVertex);

        code.write("}\n");

        try {
            codeFile.close();
            makeFile = new FileWriter("Android.mk", false);
            make = new PrintWriter(makeFile);
            make.write("LOCAL_PATH:= $(call my-dir)\n" +
                    "include $(CLEAR_VARS)\n" +
                    "LOCAL_SRC_FILES := \\\n");
        } catch (IOException e) {
            e.printStackTrace();
            assert false;
        }
    }

    private void codeGenCleanup() {
        make.write("    frame_setup.cpp \\\n");
        make.write("    frame_names.cpp \\\n");
        make.write("#\n");
        make.write(
                "LOCAL_SHARED_LIBRARIES := \\\n" +
                "    libcutils \\\n" +
                "    libutils \\\n" +
                "    libEGL \\\n" +
                "    libGLESv2 \\\n" +
                "    libui \\\n" +
                "    libhardware \\\n" +
                "    libgui\n" +
                "\n" +
                "LOCAL_MODULE:= gles2dbg\n" +
                "\n" +
                "LOCAL_MODULE_TAGS := optional\n" +
                "\n" +
                "LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES -O0 -g -DDEBUG -UNDEBUG\n" +
                "\n" +
                "include $(BUILD_EXECUTABLE)");
        try {
            dataOut.flush();
            dataOut.close();
            codeFile.close();
            makeFile.close();
            namesHeaderFile.close();
            namesSourceFile.close();
        } catch (IOException e) {
            e.printStackTrace();
            assert false;
        }
        dataOut = null;
        code = null;
        codeFile = null;
        make = null;
        makeFile = null;

        bufferNames = null;
        framebufferNames = null;
        programNames = null;
        textureNames = null;
        shaderNames = null;
        renderbufferNames = null;
    }

    private DebugContext dbgCtx;
    private int count;
    private IProgressMonitor progress;

    public void run(IProgressMonitor monitor) {
        progress.beginTask("CodeGenFrames", count + 2);
        Context ctx = dbgCtx.getFrame(0).startContext.clone();
        codeGenSetup(ctx);
        progress.worked(1);
        for (int i = 0; i < count; i++) {
            try {
                codeFile = new FileWriter("frame" + i + ".cpp", false);
                code = new PrintWriter(codeFile);
            } catch (IOException e1) {
                e1.printStackTrace();
                assert false;
            }
            make.format("    frame%d.cpp \\\n", i);

            code.write("#include \"frame_names.h\"\n");
            code.format("void Frame%d(){\n", i);
            final Frame frame = dbgCtx.getFrame(i);
            for (int j = 0; j < frame.size(); j++) {
                final MessageData msgData = frame.get(j);
                code.format("/* frame function %d: %s %s*/\n", j, msgData.msg.getFunction(),
                        MessageFormatter.format(msgData.msg, false));
                ctx.processMessage(msgData.msg);
                try {
                    codeGenFunction(ctx, msgData);
                } catch (IOException e) {
                    e.printStackTrace();
                    assert false;
                }
            }
            code.write("}\n");
            try {
                codeFile.close();
            } catch (IOException e) {
                e.printStackTrace();
                assert false;
            }
            progress.worked(1);
        }
        for (int i = 0; i < count; i++)
            namesHeader.format("void Frame%d();\n", i);
        namesHeader.format("extern void (* Frames[%d])();\n", count);
        namesSource.format("void (* Frames[%d])() = {\n", count);
        for (int i = 0; i < count; i++) {
            namesSource.format("    Frame%d,\n", i);
        }
        namesSource.write("};\n");
        namesSource.format("const unsigned int FrameCount = %d;\n", count);
        codeGenCleanup();
        progress.worked(1);
    }

    void codeGenFrames(final DebugContext dbgCtx, int count, final Shell shell) {
        this.dbgCtx = dbgCtx;
        this.count = count;
        ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
        this.progress = dialog.getProgressMonitor();
        try {
            dialog.run(false, true, this);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            assert false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.dbgCtx = null;
        this.count = 0;
        progress = null;
    }

    void codeGenFrame(final Frame frame) {
        Context ctx = frame.startContext.clone();
        codeGenSetup(ctx);
        try {
            codeFile = new FileWriter("frame0.cpp", false);
            code = new PrintWriter(codeFile);
        } catch (IOException e1) {
            e1.printStackTrace();
            assert false;
        }
        make.format("    frame0.cpp \\\n");
        code.write("#include \"frame_names.h\"\n");
        code.format("void Frame0(){\n");
        for (int i = 0; i < frame.size(); i++) {
            final MessageData msgData = frame.get(i);
            code.format("/* frame function %d: %s %s*/\n", i, msgData.msg.getFunction(),
                    MessageFormatter.format(msgData.msg, false));
            ctx.processMessage(msgData.msg);
            try {
                codeGenFunction(ctx, msgData);
            } catch (IOException e) {
                e.printStackTrace();
                assert false;
            }
        }
        code.write("}\n");
        namesHeader.write("void Frame0();\n");
        namesHeader.write("extern void (* Frames[1])();\n");
        namesSource.write("void (* Frames[1])() = {Frame0};\n");
        namesSource.write("const unsigned int FrameCount = 1;\n");
        codeGenCleanup();
    }
}
