/*
 ** 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.SparseArray;
import com.android.sdklib.util.SparseIntArray;

class GLStencilState implements Cloneable {
    public int ref, mask;
    public GLEnum func;
    public GLEnum sf, df, dp; // operation

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class GLServerState implements Cloneable {
    final Context context;
    public GLStencilState front = new GLStencilState(), back = new GLStencilState();
    public SparseIntArray enableDisables;

    /** integer states set via a GL function and GLEnum; keyed by GLEnum.value */
    public SparseArray<Message> integers;

    /** states set only via a GL function; keyed by Function.getNumber() */
    public SparseArray<Message> lastSetter;

    GLServerState(final Context context) {
        this.context = context;
        enableDisables = new SparseIntArray();
        enableDisables.put(GLEnum.GL_BLEND.value, 0);
        enableDisables.put(GLEnum.GL_DITHER.value, 1);
        enableDisables.put(GLEnum.GL_DEPTH_TEST.value, 0);
        enableDisables.put(GLEnum.GL_STENCIL_TEST.value, 0);
        enableDisables.put(GLEnum.GL_SCISSOR_TEST.value, 0);
        enableDisables.put(GLEnum.GL_SAMPLE_COVERAGE.value, 0);
        enableDisables.put(GLEnum.GL_SAMPLE_ALPHA_TO_COVERAGE.value, 0);
        enableDisables.put(GLEnum.GL_POLYGON_OFFSET_FILL.value, 0);
        enableDisables.put(GLEnum.GL_CULL_FACE.value, 0);
        // enableDisables.put(GLEnum.GL_TEXTURE_2D.value, 1);

        lastSetter = new SparseArray<Message>();
        lastSetter.put(Function.glBlendColor.getNumber(), null);
        // glBlendEquation overwrites glBlendEquationSeparate
        lastSetter.put(Function.glBlendEquationSeparate.getNumber(), null);
        // glBlendFunc overwrites glBlendFuncSeparate
        lastSetter.put(Function.glBlendFuncSeparate.getNumber(), null);
        lastSetter.put(Function.glClearColor.getNumber(), null);
        lastSetter.put(Function.glClearDepthf.getNumber(), null);
        lastSetter.put(Function.glClearStencil.getNumber(), null);
        lastSetter.put(Function.glColorMask.getNumber(), null);
        lastSetter.put(Function.glCullFace.getNumber(), null);
        lastSetter.put(Function.glDepthMask.getNumber(), null);
        lastSetter.put(Function.glDepthFunc.getNumber(), null);
        lastSetter.put(Function.glDepthRangef.getNumber(), null);
        lastSetter.put(Function.glFrontFace.getNumber(), null);
        lastSetter.put(Function.glLineWidth.getNumber(), null);
        lastSetter.put(Function.glPolygonOffset.getNumber(), null);
        lastSetter.put(Function.glSampleCoverage.getNumber(), null);
        lastSetter.put(Function.glScissor.getNumber(), null);
        lastSetter.put(Function.glStencilMaskSeparate.getNumber(), null);
        lastSetter.put(Function.glViewport.getNumber(), null);

        integers = new SparseArray<Message>();
        integers.put(GLEnum.GL_PACK_ALIGNMENT.value, null);
        integers.put(GLEnum.GL_UNPACK_ALIGNMENT.value, null);
    }

    /** returns true if processed */
    public boolean processMessage(final Message msg) {
        switch (msg.getFunction()) {
            case glBlendColor:
            case glBlendEquation:
            case glBlendEquationSeparate:
            case glBlendFunc:
            case glBlendFuncSeparate:
            case glClearColor:
            case glClearDepthf:
            case glClearStencil:
            case glColorMask:
            case glCullFace:
            case glDepthMask:
            case glDepthFunc:
            case glDepthRangef:
                return setter(msg);
            case glDisable:
                return enableDisable(false, msg);
            case glEnable:
                return enableDisable(true, msg);
            case glFrontFace:
            case glLineWidth:
                return setter(msg);
            case glPixelStorei:
                if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_PACK_ALIGNMENT)
                    integers.put(msg.getArg0(), msg);
                else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_UNPACK_ALIGNMENT)
                    integers.put(msg.getArg0(), msg);
                else
                    assert false;
                return true;
            case glPolygonOffset:
            case glSampleCoverage:
            case glScissor:
                return setter(msg);
            case glStencilFunc: {
                Message.Builder builder = msg.toBuilder();
                builder.setArg2(msg.getArg1());
                builder.setArg1(msg.getArg0());
                builder.setArg0(GLEnum.GL_FRONT_AND_BACK.value);
                return glStencilFuncSeparate(builder.build());
            }
            case glStencilFuncSeparate:
                return glStencilFuncSeparate(msg);
            case glStencilMask:
            case glStencilMaskSeparate:
                return setter(msg);
            case glStencilOp: {
                Message.Builder builder = msg.toBuilder();
                builder.setArg3(msg.getArg2());
                builder.setArg2(msg.getArg1());
                builder.setArg1(msg.getArg0());
                builder.setArg0(GLEnum.GL_FRONT_AND_BACK.value);
                return glStencilOpSeparate(builder.build());
            }
            case glStencilOpSeparate:
                return glStencilOpSeparate(msg);
            case glViewport:
                return setter(msg);
            default:
                return false;
        }
    }

    boolean setter(final Message msg) {
        switch (msg.getFunction()) {
            case glBlendFunc:
                lastSetter.put(Function.glBlendFuncSeparate.getNumber(), msg);
                break;
            case glBlendEquation:
                lastSetter.put(Function.glBlendEquationSeparate.getNumber(), msg);
                break;
            case glStencilMask:
                lastSetter.put(Function.glStencilMaskSeparate.getNumber(), msg);
                break;
            default:
                lastSetter.put(msg.getFunction().getNumber(), msg);
                break;
        }
        return true;
    }

    boolean enableDisable(boolean enable, final Message msg) {
        int index = enableDisables.indexOfKey(msg.getArg0());
        if (index < 0) {
            System.out.print("invalid glDisable/Enable: ");
            System.out.println(MessageFormatter.format(msg, false));
            return true;
        }
        if ((enableDisables.valueAt(index) != 0) == enable)
            return true; // TODO: redundant
        enableDisables.put(msg.getArg0(), enable ? 1 : 0);
        return true;
    }

    // void StencilFuncSeparate( enum face, enum func, int ref, uint mask )
    boolean glStencilFuncSeparate(final Message msg) {
        GLEnum ff = front.func, bf = back.func;
        int fr = front.ref, br = back.ref;
        int fm = front.mask, bm = back.mask;
        final GLEnum face = GLEnum.valueOf(msg.getArg0());
        if (face == GLEnum.GL_FRONT || face == GLEnum.GL_FRONT_AND_BACK) {
            ff = GLEnum.valueOf(msg.getArg1());
            fr = msg.getArg2();
            fm = msg.getArg3();
        }
        if (face == GLEnum.GL_BACK || face == GLEnum.GL_FRONT_AND_BACK) {
            bf = GLEnum.valueOf(msg.getArg1());
            br = msg.getArg2();
            bm = msg.getArg3();
        }
        if (ff == front.func && fr == front.ref && fm == front.mask)
            if (bf == back.func && br == back.ref && bm == back.mask)
                return true; // TODO: redundant
        front.func = ff;
        front.ref = fr;
        front.mask = fm;
        back.func = bf;
        back.ref = br;
        back.mask = bm;
        return true;
    }

    // void StencilOpSeparate( enum face, enum sfail, enum dpfail, enum dppass )
    boolean glStencilOpSeparate(final Message msg) {
        GLEnum fsf = front.sf, fdf = front.df, fdp = front.dp;
        GLEnum bsf = back.sf, bdf = back.df, bdp = back.dp;
        final GLEnum face = GLEnum.valueOf(msg.getArg0());
        if (face == GLEnum.GL_FRONT || face == GLEnum.GL_FRONT_AND_BACK) {
            fsf = GLEnum.valueOf(msg.getArg1());
            fdf = GLEnum.valueOf(msg.getArg2());
            fdp = GLEnum.valueOf(msg.getArg3());
        }
        if (face == GLEnum.GL_BACK || face == GLEnum.GL_FRONT_AND_BACK) {
            bsf = GLEnum.valueOf(msg.getArg1());
            bdf = GLEnum.valueOf(msg.getArg2());
            bdp = GLEnum.valueOf(msg.getArg3());
        }
        if (fsf == front.sf && fdf == front.df && fdp == front.dp)
            if (bsf == back.sf && bdf == back.df && bdp == back.dp)
                return true; // TODO: redundant
        front.sf = fsf;
        front.df = fdf;
        front.dp = fdp;
        back.sf = bsf;
        back.df = bdf;
        back.dp = bdp;
        return true;
    }

    /** deep copy */
    @Override
    public GLServerState clone() {
        try {
            GLServerState newState = (GLServerState) super.clone();
            newState.front = (GLStencilState) front.clone();
            newState.back = (GLStencilState) back.clone();

            newState.enableDisables = new SparseIntArray(enableDisables.size());
            for (int i = 0; i < enableDisables.size(); i++)
                newState.enableDisables.append(enableDisables.keyAt(i),
                        enableDisables.valueAt(i));

            newState.integers = new SparseArray<Message>(integers.size());
            for (int i = 0; i < integers.size(); i++)
                newState.integers.append(integers.keyAt(i), integers.valueAt(i));

            newState.lastSetter = new SparseArray<Message>(lastSetter.size());
            for (int i = 0; i < lastSetter.size(); i++)
                newState.lastSetter.append(lastSetter.keyAt(i), lastSetter.valueAt(i));

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