/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.drools.eclipse.debug.core;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.drools.eclipse.DroolsEclipsePlugin;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.jdi.TimeoutException;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.internal.debug.core.IJDIEventListener;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugModelMessages;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDIObjectValue;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;

import com.sun.jdi.ClassType;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ThreadDeathEvent;
import com.sun.jdi.event.ThreadStartEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;

public class DroolsDebugTarget extends JDIDebugTarget {

    private ArrayList<JDIThread>          fThreads;
    private ThreadStartHandler fThreadStartHandler = null;
    private boolean            fSuspended          = true;

    public DroolsDebugTarget(ILaunch launch,
                             VirtualMachine jvm,
                             String name,
                             boolean supportTerminate,
                             boolean supportDisconnect,
                             IProcess process,
                             boolean resume) {
        super( launch,
               jvm,
               name,
               supportTerminate,
               supportDisconnect,
               process,
               resume );
    }

    public void breakpointAdded(IBreakpoint breakpoint) {

        try {
            if ( breakpoint instanceof DroolsLineBreakpoint ) {
                ((DroolsLineBreakpoint) breakpoint).setJavaBreakpointProperties();

                final DroolsLineBreakpoint d = (DroolsLineBreakpoint) breakpoint;

                if ( d.getDialectName().equals( "mvel" ) ) {
                    //getBreakpoints().add( breakpoint );
                    //super.breakpointAdded(breakpoint);

                    Iterator<ReferenceType> handleriter = getVM().classesByName( "org.drools.core.base.mvel.MVELDebugHandler" ).iterator();
                    if ( !handleriter.hasNext() ) {
                        // Create class prepare request to add breakpoint after MVELDebugHanlder is loaded
                        ClassPrepareRequest req = getEventRequestManager().createClassPrepareRequest();
                        req.addClassFilter( "org.drools.core.base.mvel.MVELDebugHandler" );
                        req.setSuspendPolicy( EventRequest.SUSPEND_ALL );

                        addJDIEventListener(new IJDIEventListener() {
                            public boolean handleEvent(Event event,    JDIDebugTarget target) {
                                addRemoteBreakpoint(d);
                                return true;
                            }

                            public void wonSuspendVote(Event event, JDIDebugTarget target) {
                            }

                            public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
                                wonSuspendVote(event, target);
                            }

                            public boolean handleEvent(Event event,    JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
                                return handleEvent(event, target);
                            }

                        }, req);

                        req.enable();
                        return;
                    }

                    addRemoteBreakpoint( d );
                } else {
                    // only add breakpoint if setting Java properties of DRL
                    // breakpoint does not generate an error
                    super.breakpointAdded( breakpoint );
                }
            } else {
                super.breakpointAdded( breakpoint );
            }
        } catch ( Throwable t ) {
            // Exception will be thrown when trying to use breakpoint
            // on drl that is incorrect (cannot be parsed or compiled)
            DroolsEclipsePlugin.log( t );
        }
    }

    protected synchronized void initialize() {
        setThreadList( new ArrayList<JDIThread>( 5 ) );
        super.initialize();
    }

    protected JDIThread createThread(ThreadReference thread) {
        JDIThread jdiThread = null;
        try {
            jdiThread = new DroolsThread( this,
                                          thread );
        } catch ( ObjectCollectedException exception ) {
            // ObjectCollectionException can be thrown if the thread has already
            // completed (exited) in the VM.
            return null;
        }
        if ( isDisconnected() ) {
            return null;
        }
        synchronized ( fThreads ) {
            fThreads.add( jdiThread );
        }
        jdiThread.fireCreationEvent();
        return jdiThread;
    }

    @SuppressWarnings("unchecked")
    private Iterator<JDIThread> getThreadIterator() {
        List<JDIThread> threadList;
        synchronized ( fThreads ) {
            threadList = (List<JDIThread>) fThreads.clone();
        }
        return threadList.iterator();
    }

    private boolean hasSuspendedThreads() {
        Iterator<JDIThread> it = getThreadIterator();
        while ( it.hasNext() ) {
            IThread thread = it.next();
            if ( thread.isSuspended() ) return true;
        }
        return false;
    }

    public boolean canResume() {
        return (isSuspended() || hasSuspendedThreads()) && isAvailable() && !isPerformingHotCodeReplace();
    }

    protected void resume(boolean fireNotification) throws DebugException {
        if ( (!isSuspended() && !hasSuspendedThreads()) || !isAvailable() ) {
            return;
        }
        try {
            setSuspended( false );
            resumeThreads();
            VirtualMachine vm = getVM();
            if ( vm != null ) {
                vm.resume();
            }
            if ( fireNotification ) {
                fireResumeEvent( DebugEvent.CLIENT_REQUEST );
            }
        } catch ( VMDisconnectedException e ) {
            disconnected();
            return;
        } catch ( RuntimeException e ) {
            setSuspended( true );
            fireSuspendEvent( DebugEvent.CLIENT_REQUEST );
            targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_resume, e.toString()), e);
        }
    }

    private void setSuspended(boolean suspended) {
        fSuspended = suspended;
    }

    public boolean isSuspended() {
        return fSuspended;
    }

    private void setThreadList(ArrayList<JDIThread> threads) {
        fThreads = threads;
    }

    public IThread[] getThreads() {
        synchronized ( fThreads ) {
            return fThreads.toArray( new IThread[0] );
        }
    }

    protected void removeAllThreads() {
        Iterator<JDIThread> itr = getThreadIterator();
        while ( itr.hasNext() ) {
            DroolsThread child = (DroolsThread) itr.next();
            child.terminated();
        }
        synchronized ( fThreads ) {
            fThreads.clear();
        }
    }

    protected void initializeRequests() {
        setThreadStartHandler( new ThreadStartHandler() );
        new ThreadDeathHandler();
        new MVELTraceHandler();
    }

    class ThreadDeathHandler
        implements
        IJDIEventListener {

        protected ThreadDeathHandler() {
            createRequest();
        }

        /**
         * Creates and registers a request to listen to thread
         * death events.
         */
        protected void createRequest() {
            EventRequestManager manager = getEventRequestManager();
            if ( manager != null ) {
                try {
                    EventRequest req = manager.createThreadDeathRequest();
                    req.setSuspendPolicy( EventRequest.SUSPEND_NONE );
                    req.enable();
                    addJDIEventListener( this,
                                         req );
                } catch ( RuntimeException e ) {
                    logError( e );
                }
            }
        }

        /**
         * Locates the model thread associated with the underlying JDI thread
         * that has terminated, and removes it from the collection of
         * threads belonging to this debug target. A terminate event is
         * fired for the model thread.
         *
         * @param event a thread death event
         * @param target the target in which the thread died
         * @return true - the thread should be resumed
         */
        public boolean handleEvent(Event event,
                                   JDIDebugTarget target) {
            ThreadReference ref = ((ThreadDeathEvent) event).thread();
            DroolsThread thread = (DroolsThread) findThread( ref );
            if ( thread != null ) {
                synchronized ( fThreads ) {
                    fThreads.remove( thread );
                }
                thread.terminated();
            }
            return true;
        }

        public void wonSuspendVote(Event event, JDIDebugTarget target) {
            // do nothing
        }

        public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
            wonSuspendVote(event, target);
        }

        public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
            return handleEvent(event, target);
        }

    }

    class ThreadStartHandler
        implements
        IJDIEventListener {

        protected EventRequest fRequest;

        protected ThreadStartHandler() {
            createRequest();
        }

        /**
         * Creates and registers a request to handle all thread start
         * events
         */
        protected void createRequest() {
            EventRequestManager manager = getEventRequestManager();
            if ( manager != null ) {
                try {
                    EventRequest req = manager.createThreadStartRequest();
                    req.setSuspendPolicy( EventRequest.SUSPEND_NONE );
                    req.enable();
                    addJDIEventListener( this,
                                         req );
                    setRequest( req );
                } catch ( RuntimeException e ) {
                    logError( e );
                }
            }
        }

        /**
         * Creates a model thread for the underlying JDI thread
         * and adds it to the collection of threads for this
         * debug target. As a side effect of creating the thread,
         * a create event is fired for the model thread.
         * The event is ignored if the underlying thread is already
         * marked as collected.
         *
         * @param event a thread start event
         * @param target the target in which the thread started
         * @return true - the thread should be resumed
         */
        public boolean handleEvent(Event event,
                                   JDIDebugTarget target) {
            ThreadReference thread = ((ThreadStartEvent) event).thread();
            try {
                if ( thread.isCollected() ) {
                    return false;
                }
            } catch ( VMDisconnectedException exception ) {
                return false;
            } catch ( ObjectCollectedException e ) {
                return false;
            } catch ( TimeoutException e ) {
                // continue - attempt to create the thread
            }
            DroolsThread jdiThread = (DroolsThread) findThread( thread );
            if ( jdiThread == null ) {
                jdiThread = (DroolsThread) createThread( thread );
                if ( jdiThread == null ) {
                    return false;
                }
            } else {
                jdiThread.disposeStackFrames();
                jdiThread.fireChangeEvent( DebugEvent.CONTENT );
            }
            return !jdiThread.isSuspended();
        }

        /* (non-Javadoc)
         * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
         */
        public void wonSuspendVote(Event event,
                                   JDIDebugTarget target) {
            // do nothing
        }

        /**
         * Deregisters this event listener.
         */
        protected void deleteRequest() {
            if ( getRequest() != null ) {
                removeJDIEventListener( this,
                                        getRequest() );
                setRequest( null );
            }
        }

        protected EventRequest getRequest() {
            return fRequest;
        }

        protected void setRequest(EventRequest request) {
            fRequest = request;
        }

        public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
            wonSuspendVote(event, target);

        }

        public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
            return handleEvent(event, target);
        }
    }

    private void disposeThreadHandler() {
        ThreadStartHandler handler = getThreadStartHandler2();
        if ( handler != null ) {
            handler.deleteRequest();
        }
    }

    public boolean hasThreads() {
        return fThreads.size() > 0;
    }

    protected ThreadStartHandler getThreadStartHandler2() {
        return fThreadStartHandler;
    }

    protected void setThreadStartHandler(ThreadStartHandler threadStartHandler) {
        fThreadStartHandler = threadStartHandler;
    }

    public boolean isOutOfSynch() throws DebugException {
        Iterator<JDIThread> threads = getThreadIterator();
        while ( threads.hasNext() ) {
            JDIThread thread = threads.next();
            if ( thread.isOutOfSynch() ) {
                return true;
            }
        }
        return false;
    }

    public boolean mayBeOutOfSynch() {
        Iterator<JDIThread> threads = getThreadIterator();
        while ( threads.hasNext() ) {
            JDIThread thread = threads.next();
            if ( thread.mayBeOutOfSynch() ) {
                return true;
            }
        }
        return false;
    }

    public JDIThread findThread(ThreadReference tr) {
        Iterator<JDIThread> iter = getThreadIterator();
        while ( iter.hasNext() ) {
            JDIThread thread = iter.next();
            if ( thread.getUnderlyingThread().equals( tr ) ) return thread;
        }
        return null;
    }

    public void breakpointRemoved(IBreakpoint breakpoint,
                                  IMarkerDelta delta) {
        if ( !isAvailable() ) {
            return;
        }
        if ( supportsBreakpoint( breakpoint ) ) {
            try {

                if ( breakpoint instanceof DroolsLineBreakpoint ) {
                    ((DroolsLineBreakpoint) breakpoint).setJavaBreakpointProperties();

                    final DroolsLineBreakpoint d = (DroolsLineBreakpoint) breakpoint;

                    if ( d.getDialectName().equals( "mvel" ) ) {
                        removeRemoteBreakpoint( (DroolsLineBreakpoint) breakpoint,
                                                delta );
                    }
                }

                ((JavaBreakpoint) breakpoint).removeFromTarget( this );
                getBreakpoints().remove( breakpoint );
                Iterator<JDIThread> threads = getThreadIterator();
                while ( threads.hasNext() ) {
                    ((DroolsThread) threads.next()).removeCurrentBreakpoint( breakpoint );
                }
            } catch ( CoreException e ) {
                logError( e );
            }
        }
    }

    protected void suspendThreads() {
        Iterator<JDIThread> threads = getThreadIterator();
        while ( threads.hasNext() ) {
            ((DroolsThread) threads.next()).suspendedByVM();
        }
    }

    protected void resumeThreads() throws DebugException {
        Iterator<JDIThread> threads = getThreadIterator();
        while ( threads.hasNext() ) {
            ((DroolsThread) threads.next()).resumedByVM();
        }
    }

    public void disconnect() throws DebugException {

        if ( !isAvailable() ) {
            // already done
            return;
        }

        if ( !canDisconnect() ) {
            notSupported( JDIDebugModelMessages.JDIDebugTarget_does_not_support_disconnect );
        }

        try {
            disposeThreadHandler();
            VirtualMachine vm = getVM();
            if ( vm != null ) {
                vm.dispose();
            }
        } catch ( VMDisconnectedException e ) {
            // if the VM disconnects while disconnecting, perform
            // normal disconnect handling
            disconnected();
        } catch ( RuntimeException e ) {
            targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_disconnecting, e.toString()), e);
        }

    }

    public void terminate() throws DebugException {
        if ( !isAvailable() ) {
            return;
        }
        if ( !supportsTerminate() ) {
            notSupported( JDIDebugModelMessages.JDIDebugTarget_does_not_support_termination );
        }
        try {
            setTerminating( true );
            disposeThreadHandler();
            VirtualMachine vm = getVM();
            if ( vm != null ) {
                vm.exit( 1 );
            }
            IProcess process = getProcess();
            if ( process != null ) {
                process.terminate();
            }
        } catch ( VMDisconnectedException e ) {
            // if the VM disconnects while exiting, perform
            // normal termination processing
            terminated();
        } catch ( TimeoutException exception ) {
            // if there is a timeout see if the associated process is terminated
            IProcess process = getProcess();
            if ( process != null && process.isTerminated() ) {
                terminated();
            } else {
                // All we can do is disconnect
                disconnected();
            }
        } catch ( RuntimeException e ) {
            targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_terminating, e.toString()), e);
        }
    }

    public void handleVMStart(VMStartEvent event) {
        if ( isResumeOnStartup() ) {
            try {
                setSuspended( true );
                resume();
            } catch ( DebugException e ) {
                logError( e );
            }
        }
        // If any threads have resumed since thread collection was initialized,
        // update their status (avoid concurrent modification - use #getThreads())
        IThread[] threads = getThreads();
        for ( int i = 0; i < threads.length; i++ ) {
            DroolsThread thread = (DroolsThread) threads[i];
            if ( thread.isSuspended() ) {
                try {
                    boolean suspended = thread.getUnderlyingThread().isSuspended();
                    if ( !suspended ) {
                        thread.setRunning( true );
                        thread.fireResumeEvent( DebugEvent.CLIENT_REQUEST );
                    }
                } catch ( VMDisconnectedException e ) {
                } catch ( ObjectCollectedException e ) {
                } catch ( RuntimeException e ) {
                    logError( e );
                }
            }
        }
    }

    protected void initializeState() {

        List<ThreadReference> threads = null;
        VirtualMachine vm = getVM();
        if ( vm != null ) {
            try {
                threads = vm.allThreads();
            } catch ( RuntimeException e ) {
                internalError( e );
            }
            if ( threads != null ) {
                Iterator<ThreadReference> initialThreads = threads.iterator();
                while ( initialThreads.hasNext() ) {
                    createThread(initialThreads.next());
                }
            }
        }

        if ( isResumeOnStartup() ) {
            setSuspended( false );
        }
    }

    public void suspend() throws DebugException {
        if ( isSuspended() ) {
            return;
        }
        try {
            VirtualMachine vm = getVM();
            if ( vm != null ) {
                vm.suspend();
            }
            suspendThreads();
            setSuspended( true );
            fireSuspendEvent( DebugEvent.CLIENT_REQUEST );
        } catch ( RuntimeException e ) {
            setSuspended( false );
            fireResumeEvent( DebugEvent.CLIENT_REQUEST );
            targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_suspend, e.toString()), e);
        }
    }

    public void prepareToSuspendByBreakpoint(JavaBreakpoint breakpoint) {
        setSuspended( true );
        suspendThreads();
    }

    protected void cancelSuspendByBreakpoint(JavaBreakpoint breakpoint) throws DebugException {
        setSuspended( false );
        resumeThreads();
    }

    class MVELTraceHandler
        implements
        IJDIEventListener {

        protected MVELTraceHandler() {
            createRequest();
        }

        protected void createRequest() {
            EventRequestManager manager = getEventRequestManager();
            if ( manager != null ) {
                try {
                    ClassPrepareRequest req = manager.createClassPrepareRequest();
                    req.addClassFilter( "org.drools.core.base.mvel.MVELDebugHandler" );
                    req.setSuspendPolicy( EventRequest.SUSPEND_ALL );
                    addJDIEventListener( MVELTraceHandler.this,
                                         req );
                    req.enable();

                } catch ( RuntimeException e ) {
                    logError( e );
                }
            }
        }

        /**
         * Locates the model thread associated with the underlying JDI thread
         * that has terminated, and removes it from the collection of
         * threads belonging to this debug target. A terminate event is
         * fired for the model thread.
         *
         * @param event a thread death event
         * @param target the target in which the thread died
         * @return true - the thread should be resumed
         */
        public boolean handleEvent(Event event,
                                   JDIDebugTarget target) {
            ClassType classType = ( ClassType ) ((ClassPrepareEvent) event).referenceType();
            String name = classType.name();

            // change this to create a breakpoint, as the method enter was too slow
            BreakpointRequest req = null;
            List<Method> list = classType.methodsByName( "onBreak" );
            if (  list.size() == 0 ) {
                throw new IllegalStateException( "MVELDebugHandler.onBreak cannot be found by JDI" );
            }

            Method method = list.get( 0 );
            if (method != null && !method.isNative()) {
                Location location = method.location();
                if (location != null && location.codeIndex() != -1) {
                    req = getEventRequestManager().createBreakpointRequest(location);
                    req.addThreadFilter( ((ClassPrepareEvent) event).thread() );
                    req.setSuspendPolicy( EventRequest.SUSPEND_ALL  );
                } else {
                    throw new IllegalStateException( "MVELDebugHandler.onBreak location cannot be found by JDI" );
                }
            }    else {
                throw new IllegalStateException( "MVELDebugHandler.onBreak cannot be found by JDI" );
            }

            //breakpointCatched

            /*field= type.fieldByName(getFieldName());
             Field field;
             EventRequest req= manager.createModificationWatchpointRequest(field);
             */
            //req.setSuspendPolicy( EventRequest.SUSPEND_EVENT_THREAD );
            addJDIEventListener(new IJDIEventListener() {

                public boolean handleEvent(Event event, JDIDebugTarget target) {
                    BreakpointEvent entryEvent = (BreakpointEvent) event;

                    //System.out.println( entryEvent + ":" + entryEvent.location() );

                    try {
                        IThread[] tharr = getThreads();
                        ThreadReference t = null;
                        DroolsThread t2 = null;
                        for (int i = 0; i < tharr.length; i++) {
                            DroolsThread th2 = (DroolsThread) tharr[i];
                            ThreadReference th2real = ((DroolsThread) tharr[i]).getUnderlyingThread();
                            if (th2real.suspendCount() == 1    && th2.getName().equals("main")) {
                                t = th2real;
                                t2 = (DroolsThread) th2;
                                th2real.suspend();
                                th2.setRunning(false);
                                th2.fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
                                return true;
                            }
                        }
                    } catch (Exception t) {
                        logError(t);
                    }
                    return true;
                }

                public void wonSuspendVote(Event event, JDIDebugTarget target) {
                    // do nothing
                }

                public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
                    wonSuspendVote(event, target);
                }

                public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
                    return handleEvent(event, target);
                }

            }, req);

            req.enable();

            // now remove the ClassPrepareEvent
            removeJDIEventListener(MVELTraceHandler.this, ((ClassPrepareEvent) event).request() );

            return true;
        }

        /* (non-Javadoc)
         * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
         */
        public void wonSuspendVote(Event event,
                                   JDIDebugTarget target) {
            // do nothing
        }

        public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
            wonSuspendVote(event, target);
        }

        public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
            return handleEvent(event, target);
        }

    }

    /**
     * Tries to find a match for the provided breakpoint information from the list of registered breakpoints.
     * For stepping and possibly other purposes it returns also a breakpoint for cases where exactly the same line was not found.
     *
     * If breakpoint is not found for line at source then it takes the first line that is above the
     * specified line at the same file.
     *
     * @param source
     * @param line
     * @return
     */
    public DroolsLineBreakpoint getDroolsBreakpoint(String source) {

        if ( source == null ) {
            return null;
        }

        Iterator<IBreakpoint> iterator = getBreakpoints().iterator();
        while ( iterator.hasNext() ) {
            IJavaBreakpoint element = (IJavaBreakpoint) iterator.next();
            if ( element instanceof DroolsLineBreakpoint && ((DroolsLineBreakpoint) element).getDialectName().equals( "mvel" ) ) {
                DroolsLineBreakpoint l = (DroolsLineBreakpoint) element;
                try {

                    int matchLine = l.getLineNumber();
                    String matchSource = l.getRuleName();

                    if ( source.equals( matchSource ) || l.getFileRuleMappings().containsKey( source ) ) {
                        return l;
                    }

                } catch ( CoreException e ) {
                    logError( e );
                }
            }
        }

        return null;
    }

    private void addRemoteBreakpoint(DroolsLineBreakpoint d) {

        try {
            if ( !d.isEnabled() ) {
                return; // No need to install disabled breakpoints
            }
        } catch ( CoreException e2 ) {
            logError( e2 );
            return; // No need to install breakpoints that are this much broken
        }

        Iterator<ReferenceType> handleriter = getVM().classesByName( "org.drools.core.base.mvel.MVELDebugHandler" ).iterator();
        ReferenceType debugHandlerClass = handleriter.next();

        int line;
        String sourceName;

        try {
            line = d.getLineNumber();
            sourceName = d.getTypeName();
        } catch ( CoreException e1 ) {
            logError( e1 );
            return;
        }

        ReferenceType refType = debugHandlerClass;
        Method m = (Method) refType.methodsByName( "registerBreakpoint" ).iterator().next();
        List<Value> args = new ArrayList<Value>();
        IntegerValue lineVal = getVM().mirrorOf( line );
        StringReference nameVal = getVM().mirrorOf( sourceName );
        JDIObjectValue val = (JDIObjectValue) newValue( sourceName );
        ObjectReference realVal = val.getUnderlyingObject();
        args.add( nameVal );
        args.add( lineVal );

        try {
            ClassType tt = (ClassType) debugHandlerClass;
            IThread[] tharr = getThreads();
            ThreadReference t = null;
            DroolsThread t2 = null;

            for ( int i = 0; i < tharr.length; i++ ) {
                IThread th2 = tharr[i];
                ThreadReference th2real = ((DroolsThread) tharr[i]).getUnderlyingThread();

                if ( th2real.suspendCount() == 1 && th2.getName().equals( "main" ) ) {
                    t = th2real;
                    t2 = (DroolsThread) th2;
                }
            }

            tt.invokeMethod( t,
                             m,
                             args,
                             ObjectReference.INVOKE_SINGLE_THREADED );
            //t2.computeNewStackFrames();

            super.breakpointAdded( d );

        } catch ( Exception e ) {
            logError( e );
        }

    }

    private void removeRemoteBreakpoint(DroolsLineBreakpoint d,
                                        IMarkerDelta delta) {
        Iterator<ReferenceType> handleriter = getVM().classesByName( "org.drools.core.base.mvel.MVELDebugHandler" ).iterator();
        ReferenceType debugHandlerClass = handleriter.next();

        int line;
        String sourceName;

        try {
            line = d.getLineNumber();
            sourceName = d.getTypeName();
        } catch ( CoreException e1 ) {
            logError( e1 );
            return;
        }

        ReferenceType refType = debugHandlerClass;
        Method m = (Method) refType.methodsByName( "removeBreakpoint" ).iterator().next();
        List<Value> args = new ArrayList<Value>();
        IntegerValue lineVal = getVM().mirrorOf( line );
        StringReference nameVal = getVM().mirrorOf( sourceName );
        JDIObjectValue val = (JDIObjectValue) newValue( sourceName );
        ObjectReference realVal = val.getUnderlyingObject();
        args.add( nameVal );
        args.add( lineVal );

        try {
            ClassType tt = (ClassType) debugHandlerClass;
            IThread[] tharr = getThreads();
            ThreadReference t = null;
            DroolsThread t2 = null;

            for ( int i = 0; i < tharr.length; i++ ) {
                IThread th2 = tharr[i];
                ThreadReference th2real = ((DroolsThread) tharr[i]).getUnderlyingThread();

                if ( th2real.suspendCount() == 1 && th2.getName().equals( "main" ) ) {
                    t = th2real;
                    t2 = (DroolsThread) th2;
                }
            }

            tt.invokeMethod( t,
                             m,
                             args,
                             ObjectReference.INVOKE_SINGLE_THREADED );

        } catch ( Exception e ) {
            logError( e );
        }

    }

}


/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.guvnor.tools.actions;

import java.net.URL;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.guvnor.tools.Activator;
import org.guvnor.tools.views.model.TreeObject;

/**
 * Opens the Guvnor web console in a browser instance.
 * Defaults to an internal Eclipse web browser if available.
 */
public class OpenGuvnorWebConsoleAction implements IObjectActionDelegate {

    private TreeObject selectedNode;

    public OpenGuvnorWebConsoleAction() {
        super();
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.IObjectActionDelegate#setActivePart(org.eclipse.jface.action.IAction, org.eclipse.ui.IWorkbenchPart)
     */
    public void setActivePart(IAction action, IWorkbenchPart targetPart) { }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
     */
    public void run(IAction action) {
        if (selectedNode == null) {
            return;
        }
        IWorkbenchBrowserSupport browserSupport =
            Activator.getDefault().getWorkbench().getBrowserSupport();
        try {
            URL consoleURL = new URL(
                    extractGuvnorConsoleUrl(selectedNode.getGuvnorRepository().getLocation()));
            if (browserSupport.isInternalWebBrowserAvailable()) {
                browserSupport.createBrowser(null).openURL(consoleURL);
            } else {
                browserSupport.getExternalBrowser().openURL(consoleURL);
            }
        } catch (Exception e) {
            Activator.getDefault().displayError(IStatus.ERROR, e.getMessage(), e, true);
        }
    }

    private String extractGuvnorConsoleUrl(String guvnorLoc) {
        String id = "/webdav"; //$NON-NLS-1$
        int pos = guvnorLoc.indexOf(id);
        if (pos == -1) {
             return guvnorLoc + "/Guvnor.html"; //$NON-NLS-1$;
        }
        return guvnorLoc.substring(0, pos) + "/Guvnor.html"; //$NON-NLS-1$
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection)
     */
    public void selectionChanged(IAction action, ISelection selection) {
        action.setEnabled(false);

        if (!(selection instanceof IStructuredSelection)) {
            return;
        }

        IStructuredSelection sel = (IStructuredSelection)selection;
        if (sel.size() != 1) {
            return;
        }

        if (sel.getFirstElement() instanceof TreeObject) {
            if (((TreeObject)sel.getFirstElement()).getNodeType() == TreeObject.Type.REPOSITORY) {
                selectedNode = (TreeObject)sel.getFirstElement();
                action.setEnabled(true);
            }
        }
    }
}


/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.drools.eclipse.dsl.editor;

import org.drools.compiler.lang.dsl.DSLMappingEntry;
import org.drools.compiler.lang.dsl.DSLMappingEntry.Section;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

/**
 * This provides an editor for mapping language mappings.
 * This is preferable to in place editing, as it fits the usage pattern of read lots,
 * edit little.
 *
 * This is a simple popup modal dialog.
 */
public class MappingEditor extends TitleAreaDialog {

    private static final int       SCOPE_KEYWORD = 0;
    private static final int       SCOPE_WHEN    = 1;
    private static final int       SCOPE_THEN    = 2;
    private static final int       SCOPE_ALL     = 3;

    private static final String    SCOPE_STR_KEYWORD = "keyword";
    private static final String    SCOPE_STR_WHEN = "condition";
    private static final String    SCOPE_STR_THEN = "consequence";
    private static final String    SCOPE_STR_ALL  = "*";

    private Text                   exprText;
    private Text                   mappingText;
    private Text                   objText;
    private Combo                  scopeCombo;
    private boolean                cancelled;

    private DSLMappingEntry model;

    protected MappingEditor(Shell parent) {
        super( parent );
    }

    /**
     * Pass in a NLMapping item for display/edits.
     * Changes will be applied to this object only if the user clicks OK.
     */
    public void setNLMappingItem(DSLMappingEntry item) {
        model = item;
        setSection( model.getSection() );
        exprText.setText( model.getMappingKey() == null ? "" : model.getMappingKey() );
        mappingText.setText( model.getMappingValue() == null ? "" : model.getMappingValue() );
        objText.setText( model.getMetaData().getMetaData() == null ? "" : model.getMetaData().getMetaData() );
    }

    private void setSection(Section section) {
        if ( section == DSLMappingEntry.CONDITION ) {
            scopeCombo.select( SCOPE_WHEN );
        } else if ( section == DSLMappingEntry.CONSEQUENCE ) {
            scopeCombo.select( SCOPE_THEN );
        } else if ( section == DSLMappingEntry.ANY ) {
            scopeCombo.select( SCOPE_ALL );
        } else if ( section == DSLMappingEntry.KEYWORD ) {
            scopeCombo.select( SCOPE_KEYWORD );
        } else {
            throw new IllegalArgumentException( "Unknown scope type: " + section );
        }
    }

    private Section getSection(String sectionStr) {
        DSLMappingEntry.Section section = DSLMappingEntry.ANY;
        if ( SCOPE_STR_KEYWORD.equals( sectionStr ) ) {
            section = DSLMappingEntry.KEYWORD;
        } else if ( SCOPE_STR_WHEN.equals( sectionStr ) ) {
            section = DSLMappingEntry.CONDITION;
        } else if ( SCOPE_STR_THEN.equals( sectionStr ) ) {
            section = DSLMappingEntry.CONSEQUENCE;
        }
        return section;
    }

    protected void cancelPressed() {
        this.cancelled = true;
        super.cancelPressed();
    }

    protected void okPressed() {
        this.cancelled = false;
        this.model.setMappingKey( this.exprText.getText() );
        this.model.setMappingValue( this.mappingText.getText() );
        this.model.setSection( this.getSection( this.scopeCombo.getText() ) );
        this.model.setMetaData( new DSLMappingEntry.DefaultDSLEntryMetaData( this.objText.getText() ) );
        super.okPressed();
    }

    /** This will tell if the user cancelled the edit */
    public boolean isCancelled() {
        return cancelled;
    }

    protected Control createDialogArea(Composite parent) {

        //set the overall layout
        GridLayout gridLayout = new GridLayout();
        gridLayout.marginHeight = 10;
        gridLayout.verticalSpacing = 10;
        gridLayout.marginWidth = 10;
        gridLayout.numColumns = 2;
        parent.setLayout( gridLayout );

        //setup fields
        createExpressionField( parent );
        createMappingField( parent );
        createObjectField( parent );
        createScopeField( parent );

        // create the top level composite wrapper
        Composite composite = new Composite( parent,
                                             SWT.NONE );
        GridLayout layout = new GridLayout();
        layout.marginHeight = 10;
        layout.marginWidth = 10;
        layout.verticalSpacing = 10;
        composite.setLayout( layout );
        composite.setLayoutData( new GridData( GridData.FILL_BOTH ) );
        composite.setFont( parent.getFont() );

        return composite;
    }

    private void createMappingField(Composite parent) {
        Label mappingLbl = new Label( parent,
                                      SWT.NONE );
        mappingLbl.setText( "Rule mapping:" );
        mappingLbl.setFont( parent.getFont() );
        mappingLbl.setLayoutData( new GridData( GridData.HORIZONTAL_ALIGN_END ) );

        mappingText = new Text( parent,
                                SWT.BORDER );
        GridData data = new GridData();
        data.widthHint = 450;
        data.horizontalAlignment = GridData.FILL;
        data.grabExcessHorizontalSpace = true;
        mappingText.setLayoutData( data );

        mappingText.setToolTipText( "Enter the rule language mapping that the \nlanguage item will be translated to." + " Use the named variables (holes) \nthat you specify in the language expression above." );

    }

    private void createExpressionField(Composite parent) {
        Label exprLbl = new Label( parent,
                                   SWT.NONE );
        exprLbl.setText( "Language expression:" );
        exprLbl.setFont( parent.getFont() );
        exprLbl.setLayoutData( new GridData( GridData.HORIZONTAL_ALIGN_END ) );

        exprText = new Text( parent,
                             SWT.BORDER );
        GridData data = new GridData();
        data.widthHint = 450;
        data.horizontalAlignment = GridData.FILL;
        data.grabExcessHorizontalSpace = true;
        exprText.setLayoutData( data );
        exprText.setToolTipText( "Enter the language expression that you want to use in a rule.\n" + "Use curly brackets to mark 'holes' where the values will be extracted\n" + "from in the rule source. " + "Such as: Person has a name of {name} \n"
                                 + "This will then parse the rule source to extract the data out of \n" + "the place where {name} would appear." );
    }

    private void createObjectField(Composite parent) {
        Label objectLbl = new Label( parent,
                                     SWT.NONE );
        objectLbl.setText( "Object:" );
        objectLbl.setFont( parent.getFont() );
        objectLbl.setLayoutData( new GridData( GridData.HORIZONTAL_ALIGN_END ) );

        objText = new Text( parent,
                            SWT.BORDER );
        GridData data = new GridData();
        data.widthHint = 450;
        data.horizontalAlignment = GridData.FILL;
        data.grabExcessHorizontalSpace = true;
        objText.setLayoutData( data );

        objText.setToolTipText( "Enter the name of the object." );

    }

    private void createScopeField(Composite parent) {

        //type
        Label scopeLbl = new Label( parent,
                                    SWT.NONE );
        scopeLbl.setText( "Scope:" );
        scopeLbl.setFont( parent.getFont() );
        scopeLbl.setLayoutData( new GridData( GridData.HORIZONTAL_ALIGN_END ) );

        scopeCombo = new Combo( parent,
                                SWT.READ_ONLY );

        scopeCombo.add( SCOPE_STR_KEYWORD,
                        SCOPE_KEYWORD );
        scopeCombo.add( SCOPE_STR_WHEN,
                        SCOPE_WHEN );
        scopeCombo.add( SCOPE_STR_THEN,
                        SCOPE_THEN );
        scopeCombo.add( SCOPE_STR_ALL,
                        SCOPE_ALL );

        scopeCombo.select( SCOPE_ALL ); //the default

        scopeCombo.setLayoutData( new GridData( GridData.HORIZONTAL_ALIGN_BEGINNING ) );
        scopeCombo.setFont( parent.getFont() );
        scopeCombo.setToolTipText( "This specifies what part of the rule the expression applies. Indicating '*' means global." );

    }

}


/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      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.eclipse.webdav.dom;

import org.eclipse.webdav.Policy;
import org.w3c.dom.Element;

/**
 * An element editor for the WebDAV baseline control element. See
 * the latest Delta-V protocol document for the element's definition.
 *

 * Note: This class/interface is part of an interim API that is still under
 * development and expected to change significantly before reaching stability.
 * It is being made available at this early stage to solicit feedback from pioneering
 * adopters on the understanding that any code that uses this API will almost
 * certainly be broken (repeatedly) as the API evolves.
 *

 */
public class BaselineControl extends ElementEditor {
    /**
     * An ordered collection of the element names of the resourceid
     * element's children.
     */
    protected static final String[] childNames = new String[] {"href"}; //$NON-NLS-1$

    /**
     * Creates a new editor on the given WebDAV baseline control element. The
     * element is assumed to be well formed.
     *
     * @param root a baseline control element
     * @throws        MalformedElementException if there is reason to
     *                believe that the element is not well formed
     */
    public BaselineControl(Element root) throws MalformedElementException {
        super(root, "baseline-control"); //$NON-NLS-1$
    }

    /**
     * Answer the href passed in the body of the baseline control element.
     */
    public String getHref() throws MalformedElementException {
        String href = getChildText(root, "href", true); //$NON-NLS-1$
        ensureNotNull(Policy.bind("ensure.missingHrefElmt"), href); //$NON-NLS-1$
        return decodeHref(href);
    }

    /**
     * Sets this baseline control elment description to the given href.
     *
     * @param href the href of the baseline.
     */
    public void setHref(String href) {
        Assert.isNotNull(href);
        setChild(root, "href", encodeHref(href), childNames, true); //$NON-NLS-1$
    }
}