/*
 ** 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.ide.eclipse.gldebugger.DebuggerMessage.Message.Prop;
import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message.Type;

import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;

import java.io.IOException;

public class BreakpointOption extends ScrolledComposite implements SelectionListener,
        ProcessMessage {

    GLFramesView mGLFramesView;
    Button[] buttonsBreak = new Button[Function.values().length];
    /** cache of buttonsBreak[Function.getNumber()].getSelection */
    boolean[] breakpoints = new boolean[Function.values().length];

    BreakpointOption(GLFramesView view, Composite parent) {
        super(parent, SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_SCROLL);
        mGLFramesView = view;

        Composite composite = new Composite(this, 0);
        GridLayout layout = new GridLayout();
        layout.numColumns = 4;
        composite.setLayout(layout);
        this.setLayout(new FillLayout());

        for (int i = 0; i < Function.values().length; i++) {
            Group group = new Group(composite, 0);
            group.setLayout(new RowLayout());
            group.setText(Function.values()[i].toString());
            Button btn = new Button(group, SWT.CHECK);
            btn.addSelectionListener(this);
            btn.setText("Break");
            btn.setSelection(false);
            breakpoints[Function.values()[i].getNumber()] = btn.getSelection();
            buttonsBreak[Function.values()[i].getNumber()] = btn;
        }

        Point size = composite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        composite.setSize(size);
        this.setContent(composite);
        this.setExpandHorizontal(true);
        this.setExpandVertical(true);
        this.setMinSize(size);
        this.layout();
    }

    void setBreakpoint(final int contextId, final Function function, final boolean enabled) {
        Message.Builder builder = Message.newBuilder();
        builder.setContextId(contextId);
        builder.setType(Type.Response);
        builder.setExpectResponse(false);
        builder.setFunction(Function.SETPROP);
        builder.setProp(Prop.ExpectResponse);
        builder.setArg0(function.getNumber());
        builder.setArg1(enabled ? 1 : 0);
        mGLFramesView.messageQueue.addCommand(builder.build());
        breakpoints[function.getNumber()] = enabled;
    }

    public void widgetSelected(SelectionEvent e) {
        Button btn = (Button) e.widget;
        Group group = (Group) btn.getParent();
        int contextId = 0;
        if (mGLFramesView.current != null)
            contextId = mGLFramesView.current.contextId;
        setBreakpoint(contextId, Function.valueOf(group.getText()), btn.getSelection());
    }

    public void widgetDefaultSelected(SelectionEvent e) {
    }

    private Function lastFunction = Function.NEG;

    public boolean processMessage(final MessageQueue queue, final Message msg) throws IOException {
        if (!breakpoints[msg.getFunction().getNumber()])
            return false;
        // use DefaultProcessMessage just to register the GL call
        // but do not send response
        final int contextId = msg.getContextId();
        if (msg.getType() == Type.BeforeCall || msg.getType() == Type.AfterCall)
            queue.defaultProcessMessage(msg, true, false);
        final Message.Builder builder = Message.newBuilder();
        builder.setContextId(contextId);
        builder.setType(Type.Response);
        builder.setExpectResponse(true);
        final Shell shell = mGLFramesView.getViewSite().getShell();
        final boolean send[] = new boolean[1];
        shell.getDisplay().syncExec(new Runnable() {
            public void run() {
                String call = MessageFormatter.format(msg, false);
                call = call.substring(0, call.indexOf("(")) + ' ' +
                        msg.getFunction() + call.substring(call.indexOf("("));
                if (msg.hasData() && msg.getFunction() == Function.glShaderSource)
                {
                    int index = call.indexOf("string=") + 7;
                    String ptr = call.substring(index, call.indexOf(',', index));
                    call = call.replace(ptr, '"' + msg.getData().toStringUtf8() + '"');
                }
                if (msg.getType() == Type.AfterCall)
                {
                    call = "skip " + call;
                    builder.setFunction(Function.SKIP);
                }
                else if (msg.getType() == Type.BeforeCall)
                {
                    call = "continue " + call;
                    builder.setFunction(Function.CONTINUE);
                }
                else
                {
                    assert msg.getType() == Type.AfterGeneratedCall;
                    assert msg.getFunction() == lastFunction;
                    call = "skip " + call;
                    builder.setFunction(Function.SKIP);
                }
                InputDialog inputDialog = new InputDialog(shell,
                            msg.getFunction().toString() + " " + msg.getType().toString(),
                        "(s)kip, (c)continue, (r)emove bp or glFunction(...)",
                            call, null);
                if (Window.OK == inputDialog.open())
                {
                    String s = inputDialog.getValue().substring(0, 1).toLowerCase();
                    if (s.startsWith("s"))
                    {
                        builder.setFunction(Function.SKIP);
                        // AfterCall is skipped, so push BeforeCall to complete
                        if (queue.getPartialMessage(contextId) != null)
                            queue.completePartialMessage(contextId);
                    }
                    else if (s.startsWith("c"))
                        builder.setFunction(Function.CONTINUE);
                    else if (s.startsWith("r"))
                    {
                        Button btn = buttonsBreak[msg.getFunction().getNumber()];
                        btn.setSelection(false);
                        setBreakpoint(msg.getContextId(), msg.getFunction(), false);
                        builder.setExpectResponse(false);
                    }
                    else
                    {
                        MessageParserEx.instance.parse(builder, inputDialog.getValue());
                        lastFunction = builder.getFunction();
                        builder.setExpectResponse(true);
                        // AfterCall is skipped, so push BeforeCall to complete
                        if (queue.getPartialMessage(contextId) != null)
                            queue.completePartialMessage(contextId);
                    }
                }
                // else defaults to continue BeforeCall and skip AfterCall
            }
        });
        queue.sendMessage(builder.build());
        return true;
    }
}
