/*
 ** 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.util.ArrayList;

class GLShader implements Cloneable {
    public final int name;
    GLServerShader context; // the context this was created in
    public final GLEnum type;
    public boolean delete;
    public ArrayList<Integer> programs = new ArrayList<Integer>();
    public String source, originalSource;

    GLShader(final int name, final GLServerShader context, final GLEnum type) {
        this.name = name;
        this.context = context;
        this.type = type;
    }

    /** deep copy */
    public GLShader clone(final GLServerShader copyContext) {
        try {
            GLShader shader = (GLShader) super.clone();
            shader.programs = (ArrayList<Integer>) programs.clone();
            shader.context = copyContext;
            return shader;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            assert false;
            return null;
        }
    }
}

class GLProgram implements Cloneable {
    public final int name;
    GLServerShader context; // the context this was created in
    public boolean delete;
    public int vert, frag;

    GLProgram(final int name, final GLServerShader context) {
        this.name = name;
        this.context = context;
    }

    /** deep copy */
    public GLProgram clone(final GLServerShader copyContext) {
        try {
            GLProgram copy = (GLProgram) super.clone();
            copy.context = copyContext;
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            assert false;
            return null;
        }
    }
}

public class GLServerShader implements Cloneable {
    Context context;
    public SparseArray<GLShader> shaders = new SparseArray<GLShader>();
    public SparseArray<GLProgram> programs = new SparseArray<GLProgram>();
    public GLProgram current = null;
    boolean uiUpdate = false;

    GLServerShader(Context context) {
        this.context = context;
    }

    /** deep copy */
    public GLServerShader clone(final Context copyContext) {
        try {
            GLServerShader copy = (GLServerShader) super.clone();
            copy.context = copyContext;

            copy.shaders = new SparseArray<GLShader>(shaders.size());
            for (int i = 0; i < shaders.size(); i++)
                copy.shaders.append(shaders.keyAt(i), shaders.valueAt(i).clone(copy));

            copy.programs = new SparseArray<GLProgram>(programs.size());
            for (int i = 0; i < programs.size(); i++)
                copy.programs.append(programs.keyAt(i), programs.valueAt(i).clone(copy));

            if (current != null)
                copy.current = copy.programs.get(current.name);
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            assert false;
            return null;
        }
    }

    /** returns true if processed */
    public boolean processMessage(final Message msg) {
        boolean oldUiUpdate = uiUpdate;
        uiUpdate = true;
        switch (msg.getFunction()) {
            case glAttachShader:
                glAttachShader(msg);
                return true;
            case glCreateProgram:
                glCreateProgram(msg);
                return true;
            case glCreateShader:
                glCreateShader(msg);
                return true;
            case glDeleteProgram:
                glDeleteProgram(msg);
                return true;
            case glDeleteShader:
                glDeleteShader(msg);
                return true;
            case glDetachShader:
                glDetachShader(msg);
                return true;
            case glShaderSource:
                glShaderSource(msg);
                return true;
            case glUseProgram:
                glUseProgram(msg);
                return true;
            default:
                uiUpdate = oldUiUpdate;
                return false;
        }
    }

    GLShader getShader(int name) {
        if (name == 0)
            return null;
        for (Context ctx : context.shares) {
            GLShader shader = ctx.serverShader.shaders.get(name);
            if (shader != null)
                return shader;
        }
        assert false;
        return null;
    }

    GLProgram getProgram(int name) {
        if (name == 0)
            return null;
        for (Context ctx : context.shares) {
            GLProgram program = ctx.serverShader.programs.get(name);
            if (program != null)
                return program;
        }
        assert false;
        return null;
    }

    // void API_ENTRY(glAttachShader)(GLuint program, GLuint shader)
    void glAttachShader(final Message msg) {
        GLProgram program = getProgram(msg.getArg0());
        assert program != null;
        GLShader shader = getShader(msg.getArg1());
        assert program != null;
        if (GLEnum.GL_VERTEX_SHADER == shader.type)
            program.vert = shader.name;
        else
            program.frag = shader.name;
        shader.programs.add(program.name);
    }

    // GLuint API_ENTRY(glCreateProgram)(void)
    void glCreateProgram(final Message msg) {
        programs.put(msg.getRet(), new GLProgram(msg.getRet(), this));
    }

    // GLuint API_ENTRY(glCreateShader)(GLenum type)
    void glCreateShader(final Message msg) {
        shaders.put(msg.getRet(),
                new GLShader(msg.getRet(), this, GLEnum.valueOf(msg.getArg0())));
    }

    // void API_ENTRY(glDeleteProgram)
    void glDeleteProgram(final Message msg) {
        if (msg.getArg0() == 0)
            return;
        GLProgram program = getProgram(msg.getArg0());
        program.delete = true;
        for (Context ctx : context.shares)
            if (ctx.serverShader.current == program)
                return;
        glDetachShader(program, getShader(program.vert));
        glDetachShader(program, getShader(program.frag));
        programs.remove(program.name);
    }

    // void API_ENTRY(glDeleteShader)(GLuint shader)
    void glDeleteShader(final Message msg) {
        if (msg.getArg0() == 0)
            return;
        GLShader shader = getShader(msg.getArg0());
        shader.delete = true;
        if (shader.programs.size() == 0)
            shaders.remove(shader.name);
    }

    // void API_ENTRY(glDetachShader)(GLuint program, GLuint shader)
    void glDetachShader(final Message msg) {
        glDetachShader(getProgram(msg.getArg0()), getShader(msg.getArg1()));
    }

    void glDetachShader(final GLProgram program, final GLShader shader) {
        if (program == null)
            return;
        if (program.vert == shader.name)
            program.vert = 0;
        else if (program.frag == shader.name)
            program.frag = 0;
        else
            return;
        shader.programs.remove(new Integer(program.name));
        if (shader.delete && shader.programs.size() == 0)
            shaders.remove(shader.name);
    }

    // void API_ENTRY(glShaderSource)(GLuint shader, GLsizei count, const
    // GLchar** string, const GLint* length)
    void glShaderSource(final Message msg) {
        if (!msg.hasData())
            return; // TODO: distinguish between generated calls
        GLShader shader = getShader(msg.getArg0());
        shader.source = shader.originalSource = msg.getData().toStringUtf8();
    }

    // void API_ENTRY(glUseProgram)(GLuint program)
    void glUseProgram(final Message msg) {
        GLProgram oldCurrent = current;
        current = getProgram(msg.getArg0());
        if (null != oldCurrent && oldCurrent.delete && oldCurrent != current) {
            for (Context ctx : context.shares)
                if (ctx.serverShader.current == oldCurrent)
                    return;
            oldCurrent.context.programs.remove(new Integer(oldCurrent.name));
        }
    }
}
