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

class GLTexture implements Cloneable {
    public final int name;
    public final GLEnum target;
    public ArrayList<Message> contentChanges = new ArrayList<Message>();
    public GLEnum wrapS = GLEnum.GL_REPEAT, wrapT = GLEnum.GL_REPEAT;
    public GLEnum min = GLEnum.GL_NEAREST_MIPMAP_LINEAR;
    public GLEnum mag = GLEnum.GL_LINEAR;
    public GLEnum format;
    public int width, height;

    GLTexture(final int name, final GLEnum target) {
        this.name = name;
        this.target = target;
    }

    @Override
    public GLTexture clone() {
        try {
            GLTexture copy = (GLTexture) super.clone();
            copy.contentChanges = (ArrayList<Message>) contentChanges.clone();
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            assert false;
            return null;
        }
    }

    boolean processMessage(final Message msg) {
        switch (msg.getFunction()) {
            case glCompressedTexImage2D:
            case glCopyTexImage2D:
            case glTexImage2D:
                if (msg.getArg1() == 0) { // level 0
                    format = GLEnum.valueOf(msg.getArg2());
                    width = msg.getArg3();
                    height = msg.getArg4();
                }
                //$FALL-THROUGH$
            case glCompressedTexSubImage2D:
            case glCopyTexSubImage2D:
            case glTexSubImage2D:
            case glGenerateMipmap:
                contentChanges.add(msg);
                break;
            default:
                assert false;
        }
        return true;
    }

    @Override
    public String toString() {
        return String.format("%s %s %d*%d %d change(s)", target, format, width, height,
                contentChanges.size());
    }
}

public class GLServerTexture implements Cloneable {
    Context context;

    public GLEnum activeTexture = GLEnum.GL_TEXTURE0;
    public int[] tmu2D;
    public int[] tmuCube;
    public SparseArray<GLTexture> textures = new SparseArray<GLTexture>();
    public GLTexture tex2D = null, texCube = null;

    GLServerTexture(final Context context, final int MAX_COMBINED_TEXTURE_IMAGE_UNITS) {
        this.context = context;
        textures.append(0, null);
        tmu2D = new int[MAX_COMBINED_TEXTURE_IMAGE_UNITS];
        tmuCube = new int[MAX_COMBINED_TEXTURE_IMAGE_UNITS];
    }

    public GLServerTexture clone(final Context copyContext) {
        try {
            GLServerTexture copy = (GLServerTexture) super.clone();
            copy.context = copyContext;

            copy.tmu2D = tmu2D.clone();
            copy.tmuCube = tmuCube.clone();

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

            if (tex2D != null)
                copy.tex2D = copy.textures.get(tex2D.name);
            if (texCube != null)
                copy.texCube = copy.textures.get(texCube.name);

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

    public boolean processMessage(final Message msg) {
        switch (msg.getFunction()) {
            case glActiveTexture:
                activeTexture = GLEnum.valueOf(msg.getArg0());
                return true;
            case glBindTexture:
                return bindTexture(msg.getArg0(), msg.getArg1());
            case glCompressedTexImage2D:
            case glCompressedTexSubImage2D:
            case glCopyTexImage2D:
            case glCopyTexSubImage2D:
            case glTexImage2D:
            case glTexSubImage2D:
                switch (GLEnum.valueOf(msg.getArg0())) {
                    case GL_TEXTURE_2D:
                        if (tex2D != null)
                            return tex2D.processMessage(msg);
                        return true;
                    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
                    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
                    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
                        if (texCube != null)
                            return texCube.processMessage(msg);
                        return true;
                    default:
                        return true;
                }
            case glDeleteTextures: {
                final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
                names.order(GLFramesView.TARGET_BYTE_ORDER);
                for (int i = 0; i < msg.getArg0(); i++) {
                    final int name = names.getInt();
                    if (tex2D != null && tex2D.name == name)
                        bindTexture(GLEnum.GL_TEXTURE_2D.value, 0);
                    if (texCube != null && texCube.name == name)
                        bindTexture(GLEnum.GL_TEXTURE_CUBE_MAP.value, 0);
                    if (name != 0)
                        textures.remove(name);
                }
                return true;
            }
            case glGenerateMipmap:
                if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_TEXTURE_2D && tex2D != null)
                    return tex2D.processMessage(msg);
                else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_TEXTURE_CUBE_MAP
                        && texCube != null)
                    return texCube.processMessage(msg);
                return true;
            case glTexParameteri:
                return texParameter(msg.getArg0(), msg.getArg1(), msg.getArg2());
            case glTexParameterf:
                return texParameter(msg.getArg0(), msg.getArg1(),
                        (int) Float.intBitsToFloat(msg.getArg2()));
            default:
                return false;
        }
    }

    boolean bindTexture(final int target, final int name) {
        final int index = activeTexture.value - GLEnum.GL_TEXTURE0.value;
        if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_2D) {
            tex2D = textures.get(name);
            if (name != 0 && tex2D == null)
                textures.put(name, tex2D = new GLTexture(name,
                        GLEnum.GL_TEXTURE_2D));
            if (index >= 0 && index < tmu2D.length)
                tmu2D[index] = name;
        } else if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_CUBE_MAP) {
            texCube = textures.get(name);
            if (name != 0 && texCube == null)
                textures.put(name, texCube = new GLTexture(name,
                        GLEnum.GL_TEXTURE_CUBE_MAP));
            if (index >= 0 && index < tmu2D.length)
                tmu2D[index] = name;
        } else
            assert false;
        return true;
    }

    boolean texParameter(final int target, final int pname, final int param) {
        GLTexture tex = null;
        if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_2D)
            tex = tex2D;
        else if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_CUBE_MAP)
            tex = texCube;
        if (tex == null)
            return true;
        final GLEnum p = GLEnum.valueOf(param);
        switch (GLEnum.valueOf(pname)) {
            case GL_TEXTURE_WRAP_S:
                tex.wrapS = p;
                return true;
            case GL_TEXTURE_WRAP_T:
                tex.wrapT = p;
                return true;
            case GL_TEXTURE_MIN_FILTER:
                tex.min = p;
                return true;
            case GL_TEXTURE_MAG_FILTER:
                tex.mag = p;
                return true;
            default:
                return true;
        }
    }
}
