/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.netbeans.modules.debugger.jpda.actions;

import com.sun.jdi.ThreadReference;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.InvalidRequestStateException;
import com.sun.jdi.request.StepRequest;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.netbeans.api.debugger.ActionsManager;
import org.netbeans.api.debugger.jpda.JPDADebugger;
import org.netbeans.api.debugger.jpda.JPDAThread;

import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
//import org.netbeans.modules.debugger.jpda.JPDAStepImpl.SingleThreadedStepWatch;
import org.netbeans.modules.debugger.jpda.jdi.InternalExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.InvalidRequestStateExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.VMDisconnectedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.VirtualMachineWrapper;
import org.netbeans.modules.debugger.jpda.jdi.request.EventRequestManagerWrapper;
import org.netbeans.modules.debugger.jpda.jdi.request.StepRequestWrapper;
import org.netbeans.modules.debugger.jpda.models.JPDAThreadImpl;
import org.netbeans.spi.debugger.ActionsProviderSupport;

import org.openide.util.WeakSet;


/**
* Representation of a debugging session.
*
* @author   Jan Jancura
* @author  Marian Petras
*/
public abstract class JPDADebuggerActionProvider extends ActionsProviderSupport 
implements PropertyChangeListener {
    
    protected JPDADebuggerImpl debugger;
    
    private static final Set<JPDADebuggerActionProvider> providersToDisableOnLazyActions = new WeakSet<JPDADebuggerActionProvider>();
    
    private volatile boolean disabled;
    
    protected JPDADebuggerActionProvider (JPDADebuggerImpl debugger) {
        this.debugger = debugger;
        debugger.addPropertyChangeListener (JPDADebugger.PROP_STATE, this);
    }
    
    @Override
    public void propertyChange (PropertyChangeEvent evt) {
        if ("methodInvoke".equals(evt.getPropagationId())) {
            return ; // Ignore events associated with method invocations
        }
        checkEnabled (debugger.getState ());
    }
    
    protected abstract void checkEnabled (int debuggerState);
    
    @Override
    public boolean isEnabled (Object action) {
        if (!disabled) {
            checkEnabled (debugger.getState ());
        }
        return super.isEnabled (action);
    }
    
    protected JPDADebuggerImpl getDebuggerImpl () {
        return debugger;
    }
    
    protected void removeStepRequests (ThreadReference tr) {
        removeStepRequests(getDebuggerImpl(), tr);
    }

    static void removeStepRequests(JPDADebuggerImpl debugger, ThreadReference tr) {
        //S ystem.out.println ("removeStepRequests");
        try {
            VirtualMachine vm = debugger.getVirtualMachine ();
            if (vm == null) return;
            EventRequestManager erm = VirtualMachineWrapper.eventRequestManager (vm);
            List<StepRequest> l = EventRequestManagerWrapper.stepRequests (erm);
            Iterator<StepRequest> it = l.iterator ();
            while (it.hasNext ()) {
                StepRequest stepRequest = it.next ();
                if (StepRequestWrapper.thread(stepRequest).equals (tr)) {
                    try {
                        //S ystem.out.println("  remove request " + stepRequest);
                        EventRequestManagerWrapper.deleteEventRequest(erm, stepRequest);
                    } catch (InvalidRequestStateExceptionWrapper ex) {}
                    //SingleThreadedStepWatch.stepRequestDeleted(stepRequest);
                    debugger.getOperator().unregister(stepRequest);
                    break;
                }
                //S ystem.out.println("  do not remove " + stepRequest + " : " + stepRequest.thread ());
            }
        } catch (VMDisconnectedExceptionWrapper e) {
        } catch (InternalExceptionWrapper e) {
        } catch (IllegalThreadStateException e) {
        } catch (InvalidRequestStateException e) {
        }
    }
    
    /**
     * Mark the provided action provider to be disabled when a lazy action is to be performed.
     */
    final void setProviderToDisableOnLazyAction(JPDADebuggerActionProvider provider) {
        synchronized (JPDADebuggerActionProvider.class) {
            providersToDisableOnLazyActions.add(provider);
        }
    }
    
    /**
     * Do the action lazily in a RequestProcessor.
     * @param run The action to perform.
     */
    protected final void doLazyAction(final Object action, final Runnable run) {
        //System.err.println("doLazyAction() in "+this);
        //Logger.getLogger(JPDADebuggerActionProvider.class.getName()).fine("doLazyAction() in "+this);
        //final long start = System.nanoTime();
        final ActionsSynchronizer as = ActionsSynchronizer.get(debugger);
        as.actionScheduled(action);
        final JPDAThreadImpl threadWithActionsPending;
        JPDAThread ct = debugger.getCurrentThread();
        if (ct instanceof JPDAThreadImpl && action != ActionsManager.ACTION_PAUSE) {
            threadWithActionsPending = (JPDAThreadImpl) ct;
            threadWithActionsPending.setPendingAction(action);
        } else {
            threadWithActionsPending = null;
        }
        final Set<JPDADebuggerActionProvider> disabledActions;
        synchronized (JPDADebuggerActionProvider.class) {
            disabledActions = new HashSet<JPDADebuggerActionProvider>(providersToDisableOnLazyActions);
        }
        for (Iterator<JPDADebuggerActionProvider> it = disabledActions.iterator(); it.hasNext(); ) {
            JPDADebuggerActionProvider ap = it.next();
            Set actions = ap.getActions();
            ap.disabled = true;
            for (Iterator ait = actions.iterator(); ait.hasNext(); ) {
                Object a = ait.next();
                ap.setEnabled (a, false);
                //System.out.println(ap+".setEnabled("+action+", "+false+")");
            }
        }
        debugger.getRequestProcessor().post(new Runnable() {
            @Override
            public void run() {
                try {
                    //long end = System.nanoTime();
                    //System.err.println("  run in RP after "+(end - start)+" ns ("+((end - start)/1000000)+" ms) in "+this);
                    //Logger.getLogger(JPDADebuggerActionProvider.class.getName()).fine("  run in RP after "+(end - start)+" ns ("+((end - start)/1000000)+" ms) in "+this);
                    as.actionStarts(action);
                    run.run();
                    if (threadWithActionsPending != null) {
                        threadWithActionsPending.setPendingAction(null);
                    }
                    for (Iterator<JPDADebuggerActionProvider> it = disabledActions.iterator(); it.hasNext(); ) {
                        JPDADebuggerActionProvider ap = it.next();
                        Set actions = ap.getActions();
                        ap.disabled = false;
                        ap.checkEnabled (debugger.getState ());
                    }
                } catch (com.sun.jdi.VMDisconnectedException e) {
                    // Causes kill action when something is being evaluated
                } finally {
                    as.actionEnds(action);
                }
            }
        });
    }
}
