/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.debug.model;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchListener;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.internal.ui.views.console.ProcessConsole;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.internal.console.IOConsolePartition;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.tasklist.ITaskListResourceAdapter;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.core.IConsoleInputListener;
import org.python.pydev.debug.core.PydevDebugPlugin;
import org.python.pydev.debug.core.PydevDebugPreferencesInitializer;
import org.python.pydev.debug.curr_exception.CurrentExceptionView;
import org.python.pydev.debug.model.XMLUtils.StoppedStack;
import org.python.pydev.debug.model.remote.AbstractDebuggerCommand;
import org.python.pydev.debug.model.remote.AbstractRemoteDebugger;
import org.python.pydev.debug.model.remote.AddIgnoreThrownExceptionIn;
import org.python.pydev.debug.model.remote.RemoveBreakpointCommand;
import org.python.pydev.debug.model.remote.RunCommand;
import org.python.pydev.debug.model.remote.SendPyExceptionCommand;
import org.python.pydev.debug.model.remote.SetBreakpointCommand;
import org.python.pydev.debug.model.remote.SetDjangoExceptionBreakpointCommand;
import org.python.pydev.debug.model.remote.SetDontTraceEnabledCommand;
import org.python.pydev.debug.model.remote.SetPropertyTraceCommand;
import org.python.pydev.debug.model.remote.ThreadListCommand;
import org.python.pydev.debug.model.remote.VersionCommand;
import org.python.pydev.debug.ui.launching.PythonRunnerConfig;
import org.python.pydev.editor.preferences.PydevEditorPrefs;
import org.python.pydev.editorinput.PySourceLocatorBase;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.utils.RunInUiThread;

/**
 * This is the target for the debug (
 *
 * @author Fabio
 */
@SuppressWarnings("restriction")
public abstract class AbstractDebugTarget extends AbstractDebugTargetWithTransmission implements IDebugTarget,
        ILaunchListener, IExceptionsBreakpointListener, IPropertyTraceListener {

    private static final boolean DEBUG = false;

    /**
     * Path pointing to the file that started the debug (e.g.: file with __name__ == '__main__')
     */
    protected IPath[] file;

    /**
     * The threads found in the debugger.
     */
    protected PyThread[] threads;

    /**
     * Indicates whether we've already disconnected from the debugger.
     */
    protected boolean disconnected = false;

    /**
     * This is the instance used to pass messages to the debugger.
     */
    protected AbstractRemoteDebugger debugger;

    /**
     * Launch that triggered the debug session.
     */
    protected ILaunch launch;

    /**
     * Class used to check for modifications in the values already found.
     */
    private ValueModificationChecker modificationChecker;

    private PyRunToLineTarget runToLineTarget;

    public AbstractDebugTarget() {
        modificationChecker = new ValueModificationChecker();
    }

    public ValueModificationChecker getModificationChecker() {
        return modificationChecker;
    }

    @Override
    public abstract boolean canTerminate();

    @Override
    public abstract boolean isTerminated();

    @Override
    public void terminate() {
        PydevPlugin plugin = PydevPlugin.getDefault();
        if (plugin != null) {
            IPreferenceStore preferenceStore = plugin.getPreferenceStore();
            if (preferenceStore != null) {
                preferenceStore.removePropertyChangeListener(listener);
            }
        }
        if (socket != null) {
            try {
                socket.shutdownInput(); // trying to make my pydevd notice that the socket is gone
            } catch (Exception e) {
                // ok, ignore
            }
            try {
                socket.shutdownOutput();
            } catch (Exception e) {
                // ok, ignore
            }
            try {
                socket.close();
            } catch (Exception e) {
                // ok, ignore
            }
        }
        socket = null;
        disconnected = true;

        if (writer != null) {
            writer.done();
            writer = null;
        }
        if (reader != null) {
            reader.done();
            reader = null;
        }

        if (DEBUG) {
            System.out.println("TERMINATE");
        }

        threads = new PyThread[0];
        fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));

    }

    public AbstractRemoteDebugger getDebugger() {
        return debugger;
    }

    @Override
    public void launchAdded(ILaunch launch) {
        // noop
    }

    @Override
    public void launchChanged(ILaunch launch) {
        // noop
    }

    // From IDebugElement
    @Override
    public String getModelIdentifier() {
        return PyDebugModelPresentation.PY_DEBUG_MODEL_ID;
    }

    // From IDebugElement
    @Override
    public IDebugTarget getDebugTarget() {
        return this;
    }

    @Override
    public String getName() throws DebugException {
        if (file != null) {
            return PythonRunnerConfig.getRunningName(file);
        } else {
            return "unknown"; //TODO: SHOW PROPER PROCESS ID!
        }
    }

    @Override
    public boolean canResume() {
        for (int i = 0; i < threads.length; i++) {
            if (threads[i].canResume()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean canSuspend() {
        for (int i = 0; i < threads.length; i++) {
            if (threads[i].canSuspend()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean isSuspended() {
        return false;
    }

    @Override
    public void resume() throws DebugException {
        for (int i = 0; i < threads.length; i++) {
            threads[i].resume();
        }
    }

    @Override
    public void suspend() throws DebugException {
        for (int i = 0; i < threads.length; i++) {
            threads[i].suspend();
        }
    }

    @Override
    public IThread[] getThreads() throws DebugException {
        if (debugger == null) {
            return null;
        }

        if (threads == null) {
            ThreadListCommand cmd = new ThreadListCommand(this);
            this.postCommand(cmd);
            try {
                cmd.waitUntilDone(1000);
                threads = cmd.getThreads();
            } catch (InterruptedException e) {
                threads = new PyThread[0];
            }
        }
        return threads;
    }

    @Override
    public boolean hasThreads() throws DebugException {
        return true;
    }

    //Breakpoints ------------------------------------------------------------------------------------------------------

    /* (non-Javadoc)
     * @see org.python.pydev.debug.model.IExceptionsBreakpointListener#onSetConfiguredExceptions()
     */
    @Override
    public void onSetConfiguredExceptions() {
        // Sending python exceptions to the debugger
        SendPyExceptionCommand sendCmd = new SendPyExceptionCommand(this);
        this.postCommand(sendCmd);
    }

    /**
     * Same as onAddIgnoreThrownExceptionIn, but bulk-created with all available.
     */
    @Override
    public void onUpdateIgnoreThrownExceptions() {
        AddIgnoreThrownExceptionIn cmd = new AddIgnoreThrownExceptionIn(this);
        this.postCommand(cmd);
    }

    @Override
    public void onAddIgnoreThrownExceptionIn(File file, int lineNumber) {
        AddIgnoreThrownExceptionIn cmd = new AddIgnoreThrownExceptionIn(this, file, lineNumber);
        this.postCommand(cmd);
    }

    /*
     * (non-Javadoc)
     * @see org.python.pydev.debug.model.IPropertyTraceListener#onSetPropertyTraceConfiguration()
     */
    @Override
    public void onSetPropertyTraceConfiguration() {
        // Sending whether to trace python property
        SetPropertyTraceCommand sendCmd = new SetPropertyTraceCommand(this);
        this.postCommand(sendCmd);
    }

    /**
     * @return true if the given breakpoint is supported by this target
     */
    @Override
    public boolean supportsBreakpoint(IBreakpoint breakpoint) {
        return breakpoint instanceof PyBreakpoint;
    }

    /**
     * @return true if all the breakpoints should be skipped. Patch from bug:
     * http://sourceforge.net/tracker/index.php?func=detail&aid=1960983&group_id=85796&atid=577329
     */
    private boolean shouldSkipBreakpoints() {
        DebugPlugin manager = DebugPlugin.getDefault();
        return manager != null && !manager.getBreakpointManager().isEnabled();
    }

    /**
     * Adds a breakpoint if it's enabled.
     */
    @Override
    public void breakpointAdded(IBreakpoint breakpoint) {
        try {
            if (breakpoint instanceof PyBreakpoint) {
                PyBreakpoint b = (PyBreakpoint) breakpoint;
                if (b.isEnabled() && !shouldSkipBreakpoints()) {
                    String condition = null;
                    if (b.isConditionEnabled()) {
                        condition = b.getCondition();
                        if (condition != null) {
                            condition = StringUtils.replaceAll(condition, "\n",
                                    "@_@NEW_LINE_CHAR@_@");
                            condition = StringUtils.replaceAll(condition, "\t",
                                    "@_@TAB_CHAR@_@");
                        }
                    }
                    String file2 = b.getFile();
                    Object line = b.getLine();
                    if (file2 == null || line == null) {
                        Log.log("Trying to add breakpoint with invalid file: " + file2 + " or line: " + line);
                    } else {
                        SetBreakpointCommand cmd = new SetBreakpointCommand(this, b.breakpointId, file2, line,
                                condition, b.getFunctionName(), b.getType());
                        this.postCommand(cmd);
                    }
                }
            }
        } catch (CoreException e) {
            Log.log(e);
        }
    }

    /**
     * Removes an existing breakpoint from the debug target.
     */
    @Override
    public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
        if (breakpoint instanceof PyBreakpoint) {
            PyBreakpoint b = (PyBreakpoint) breakpoint;
            RemoveBreakpointCommand cmd = new RemoveBreakpointCommand(this, b.breakpointId, b.getFile(), b.getType());
            this.postCommand(cmd);
        }
    }

    /**
     * Called when a breakpoint is changed.
     * E.g.:
     *  - When line numbers change in the file
     *  - When the manager decides to enable/disable all existing markers
     *  - When the breakpoint properties (hit condition) are edited
     */
    @Override
    public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
        if (breakpoint instanceof PyBreakpoint) {
            breakpointRemoved(breakpoint, null);
            breakpointAdded(breakpoint);
        }
    }

    //End Breakpoints --------------------------------------------------------------------------------------------------

    // Storage retrieval is not supported
    @Override
    public boolean supportsStorageRetrieval() {
        return false;
    }

    @Override
    public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException {
        return null;
    }

    /**
     * When a command that originates from daemon is received,
     * this routine processes it.
     * The responses to commands originating from here
     * are processed by commands themselves
     */
    public void processCommand(String sCmdCode, String sSeqCode, String payload) {
        if (DEBUG) {
            System.out.println("process command:" + sCmdCode +
                    "\tseq:" + sSeqCode +
                    "\tpayload:" + payload +
                    "\n\n");
        }
        try {
            int cmdCode = Integer.parseInt(sCmdCode);

            if (cmdCode == AbstractDebuggerCommand.CMD_THREAD_CREATED) {
                processThreadCreated(payload);

            } else if (cmdCode == AbstractDebuggerCommand.CMD_THREAD_KILL) {
                processThreadKilled(payload);

            } else if (cmdCode == AbstractDebuggerCommand.CMD_THREAD_SUSPEND) {
                processThreadSuspended(payload);

            } else if (cmdCode == AbstractDebuggerCommand.CMD_THREAD_RUN) {
                processThreadRun(payload);

            } else if (cmdCode == AbstractDebuggerCommand.CMD_GET_BREAKPOINT_EXCEPTION) {
                processBreakpointException(payload);

            } else if (cmdCode == AbstractDebuggerCommand.CMD_SEND_CURR_EXCEPTION_TRACE) {
                processCaughtExceptionTraceSent(payload);

            } else if (cmdCode == AbstractDebuggerCommand.CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED) {
                processCaughtExceptionTraceProceededSent(payload);

            } else {
                PydevDebugPlugin.log(IStatus.WARNING, "Unexpected debugger command:" + sCmdCode +
                        "\nseq:" + sSeqCode
                        +
                        "\npayload:" + payload, null);
            }
        } catch (Exception e) {
            PydevDebugPlugin.log(IStatus.ERROR, "Error processing: " + sCmdCode +
                    "\npayload: " + payload, e);
        }
    }

    public void fireEvent(DebugEvent event) {
        DebugPlugin manager = DebugPlugin.getDefault();
        if (manager != null) {
            manager.fireDebugEventSet(new DebugEvent[] { event });
        }
    }

    /**
     * @return an existing thread with a given id (null if none)
     */
    protected PyThread findThreadByID(String thread_id) {
        for (IThread thread : threads) {
            if (thread_id.equals(((PyThread) thread).getId())) {
                return (PyThread) thread;
            }
        }
        return null;
    }

    /**
     * Add it to the list of threads
     */
    private void processThreadCreated(String payload) {

        PyThread[] newThreads;
        try {
            newThreads = XMLUtils.ThreadsFromXML(this, payload);
        } catch (CoreException e) {
            PydevDebugPlugin.errorDialog("Error in processThreadCreated", e);
            return;
        }

        // Hide Pydevd threads if requested
        if (PydevDebugPlugin.getDefault().getPreferenceStore()
                .getBoolean(PydevDebugPreferencesInitializer.HIDE_PYDEVD_THREADS)) {
            int removeThisMany = 0;

            for (int i = 0; i < newThreads.length; i++) {
                if (newThreads[i].isPydevThread()) {
                    removeThisMany++;
                }
            }

            if (removeThisMany > 0) {
                int newSize = newThreads.length - removeThisMany;

                if (newSize == 0) { // no threads to add
                    return;

                } else {

                    PyThread[] newnewThreads = new PyThread[newSize];
                    int i = 0;

                    for (PyThread newThread : newThreads) {
                        if (!newThread.isPydevThread()) {
                            newnewThreads[i] = newThread;
                            i += 1;
                        }
                    }

                    newThreads = newnewThreads;

                }
            }
        }

        // add threads to the thread list, and fire event
        if (threads == null) {
            threads = newThreads;

        } else {
            PyThread[] combined = new PyThread[threads.length + newThreads.length];
            int i = 0;
            for (i = 0; i < threads.length; i++) {
                combined[i] = threads[i];
            }

            for (int j = 0; j < newThreads.length; i++, j++) {
                combined[i] = newThreads[j];
            }
            threads = combined;
        }
        // Now notify debugger that new threads were added
        for (int i = 0; i < newThreads.length; i++) {
            fireEvent(new DebugEvent(newThreads[i], DebugEvent.CREATE));
        }
    }

    // Remote this from our thread list
    private void processThreadKilled(String thread_id) {
        PyThread threadToDelete = findThreadByID(thread_id);
        if (threadToDelete != null) {
            int j = 0;
            PyThread[] newThreads = new PyThread[threads.length - 1];
            for (int i = 0; i < threads.length; i++) {
                if (threads[i] != threadToDelete) {
                    newThreads[j++] = threads[i];
                }
            }
            threads = newThreads;
            fireEvent(new DebugEvent(threadToDelete, DebugEvent.TERMINATE));
        }
    }

    private void processThreadSuspended(String payload) {
        StoppedStack threadNstack;
        try {
            threadNstack = XMLUtils.XMLToStack(this, payload);
        } catch (CoreException e) {
            PydevDebugPlugin.errorDialog("Error reading ThreadSuspended", e);
            return;
        }

        PyThread t = threadNstack.thread;
        int reason = DebugEvent.UNSPECIFIED;
        String stopReason = threadNstack.stopReason;

        if (stopReason != null) {
            int stopReason_i = Integer.parseInt(stopReason);

            if (stopReason_i == AbstractDebuggerCommand.CMD_STEP_OVER
                    || stopReason_i == AbstractDebuggerCommand.CMD_STEP_INTO
                    || stopReason_i == AbstractDebuggerCommand.CMD_STEP_CAUGHT_EXCEPTION
                    || stopReason_i == AbstractDebuggerCommand.CMD_STEP_RETURN
                    || stopReason_i == AbstractDebuggerCommand.CMD_RUN_TO_LINE
                    || stopReason_i == AbstractDebuggerCommand.CMD_SET_NEXT_STATEMENT) {

                //Code which could be used to know where a caught exception broke the debugger.
                //if (stopReason_i == AbstractDebuggerCommand.CMD_STEP_CAUGHT_EXCEPTION) {
                //    System.out.println("Stopped: caught exception");
                //    IStackFrame stackFrame[] = (IStackFrame[]) threadNstack[2];
                //    if (stackFrame.length > 0) {
                //        IStackFrame currStack = stackFrame[0];
                //        if (currStack instanceof PyStackFrame) {
                //            PyStackFrame pyStackFrame = (PyStackFrame) currStack;
                //            try {
                //                System.out.println(pyStackFrame.getPath() + " " + pyStackFrame.getLineNumber());
                //            } catch (DebugException e) {
                //                Log.log(e);
                //            }
                //        }
                //    }
                //
                //}
                reason = DebugEvent.STEP_END;

            } else if (stopReason_i == AbstractDebuggerCommand.CMD_THREAD_SUSPEND) {
                reason = DebugEvent.CLIENT_REQUEST;

            } else if (stopReason_i == AbstractDebuggerCommand.CMD_SET_BREAK) {
                reason = DebugEvent.BREAKPOINT;

            } else {
                PydevDebugPlugin.log(IStatus.ERROR, "Unexpected reason for suspension", null);
                reason = DebugEvent.UNSPECIFIED;
            }
        }
        if (t != null) {
            modificationChecker.onlyLeaveThreads(this.threads);

            IStackFrame stackFrame[] = threadNstack.stack;
            t.setSuspended(true, stackFrame);
            fireEvent(new DebugEvent(t, DebugEvent.SUSPEND, reason));
        }
    }

    /**
     * @param payload a string in the format: thread_id\tresume_reason
     * E.g.: pid3720_zad_seq1\t108
     *
     * @return a tuple with the thread id and the reason it stopped.
     * @throws CoreException
     */
    public static Tuple<String, String> getThreadIdAndReason(String payload) throws CoreException {
        List<String> split = StringUtils.split(payload.trim(), '\t');
        if (split.size() != 2) {
            String msg = "Unexpected threadRun payload " + payload +
                    "(unable to match)";
            throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, msg, new RuntimeException(msg)));
        }
        return new Tuple<String, String>(split.get(0), split.get(1));
    }

    /**
     * ThreadRun event processing
     */
    private void processThreadRun(String payload) {
        try {
            Tuple<String, String> threadIdAndReason = getThreadIdAndReason(payload);
            int resumeReason = DebugEvent.UNSPECIFIED;
            try {
                int raw_reason = Integer.parseInt(threadIdAndReason.o2);
                if (raw_reason == AbstractDebuggerCommand.CMD_STEP_OVER) {
                    resumeReason = DebugEvent.STEP_OVER;
                } else if (raw_reason == AbstractDebuggerCommand.CMD_STEP_RETURN) {
                    resumeReason = DebugEvent.STEP_RETURN;
                } else if (raw_reason == AbstractDebuggerCommand.CMD_STEP_INTO
                        || raw_reason == AbstractDebuggerCommand.CMD_STEP_CAUGHT_EXCEPTION) {
                    resumeReason = DebugEvent.STEP_INTO;
                } else if (raw_reason == AbstractDebuggerCommand.CMD_RUN_TO_LINE) {
                    resumeReason = DebugEvent.UNSPECIFIED;
                } else if (raw_reason == AbstractDebuggerCommand.CMD_SET_NEXT_STATEMENT) {
                    resumeReason = DebugEvent.UNSPECIFIED;
                } else if (raw_reason == AbstractDebuggerCommand.CMD_THREAD_RUN) {
                    resumeReason = DebugEvent.CLIENT_REQUEST;
                } else {
                    PydevDebugPlugin.log(IStatus.ERROR, "Unexpected resume reason code", null);
                    resumeReason = DebugEvent.UNSPECIFIED;
                }
            } catch (NumberFormatException e) {
                // expected, when pydevd reports "None"
                resumeReason = DebugEvent.UNSPECIFIED;
            }

            String threadID = threadIdAndReason.o1;
            PyThread t = findThreadByID(threadID);
            if (t != null) {
                t.setSuspended(false, null);
                fireEvent(new DebugEvent(t, DebugEvent.RESUME, resumeReason));

            } else {
                FastStringBuffer buf = new FastStringBuffer();
                for (PyThread thread : threads) {
                    if (buf.length() > 0) {
                        buf.append(", ");
                    }
                    buf.append("id: " + thread.getId());
                }
                String msg = "Unable to find thread: " + threadID +
                        " available: " + buf;
                PydevDebugPlugin.log(IStatus.ERROR, msg, new RuntimeException(msg));
            }
        } catch (CoreException e1) {
            Log.log(e1);
        }

    }

    /**
     * Handle the exception received while evaluating the breakpoint condition
     *
     * @param payload
     */
    private void processBreakpointException(String payload) {
        PyConditionalBreakPointManager.getInstance().handleBreakpointException(this, payload);
    }

    private Object currExceptionsLock = new Object();
    private List<CaughtException> currExceptions = new ArrayList<>();

    public List<CaughtException> getCurrExceptions() {
        synchronized (currExceptionsLock) {
            return new ArrayList<>(currExceptions);
        }
    }

    public boolean hasCurrExceptions() {
        synchronized (currExceptionsLock) {
            return currExceptions.size() > 0;
        }
    }

    private void processCaughtExceptionTraceProceededSent(String payload) {
        synchronized (currExceptionsLock) {
            for (Iterator<CaughtException> it = currExceptions.iterator(); it.hasNext();) {
                CaughtException s = it.next();
                if (payload.equals(s.threadNstack.thread.getId())) {
                    it.remove();
                    break;
                }

            }
        }
        updateView();
    }

    /**
     * Handle the exception received while evaluating the breakpoint condition
     *
     * @param payload
     */
    private void processCaughtExceptionTraceSent(String payload) {
        List<String> split = StringUtils.split(payload, '\t', 4);
        StoppedStack threadNstack;
        try {
            threadNstack = XMLUtils.XMLToStack(this, split.get(3));
        } catch (CoreException e) {
            PydevDebugPlugin.errorDialog("Error on processCaughtExceptionTraceSent", e);
            return;
        }
        synchronized (currExceptionsLock) {
            //payload is: currentFrameId, excType, msg, xml with thread/stack
            currExceptions.add(new CaughtException(split.get(0), split.get(1), split.get(2), threadNstack));
        }

        updateView();
    }

    private void updateView() {
        RunInUiThread.async(new Runnable() {

            @Override
            public void run() {
                CurrentExceptionView view = CurrentExceptionView.getView(true);
                view.update();
            }
        });
    }

    /**
     * Listens to the (org) PydevPlugin preferences.
     */
    private final IPropertyChangeListener listener = new IPropertyChangeListener() {

        @Override
        public void propertyChange(PropertyChangeEvent event) {
            String property = event.getProperty();
            if (property.equals(PydevEditorPrefs.DONT_TRACE_ENABLED)) {
                sendDontTraceEnabledCommand();

            } else if (property.equals(PydevEditorPrefs.TRACE_DJANGO_TEMPLATE_RENDER_EXCEPTIONS)) {
                sendSetDjangoExceptionBreakpointCommand();
            }
        }
    };

    /**
     * Called after debugger has been connected.
     *
     * Here we send all the initialization commands
     * and exceptions on which pydev debugger needs to break
     */
    public void initialize() {
        // we post version command just for fun
        // it establishes the connection
        this.postCommand(new VersionCommand(this));

        // now, register all the breakpoints in all projects
        addBreakpointsFor(ResourcesPlugin.getWorkspace().getRoot());

        // Sending python exceptions and property trace state before sending run command
        this.onSetConfiguredExceptions();
        this.onSetPropertyTraceConfiguration();
        this.onUpdateIgnoreThrownExceptions();
        this.sendSetDjangoExceptionBreakpointCommand();
        this.sendDontTraceEnabledCommand();

        IPreferenceStore pyPrefsStore = PydevPlugin.getDefault().getPreferenceStore();
        pyPrefsStore.addPropertyChangeListener(listener);

        // Send the run command, and we are off
        RunCommand run = new RunCommand(this);
        this.postCommand(run);
    }

    private void sendDontTraceEnabledCommand() {
        IPreferenceStore pyPrefsStore = PydevPlugin.getDefault().getPreferenceStore();
        SetDontTraceEnabledCommand cmd = new SetDontTraceEnabledCommand(this,
                pyPrefsStore.getBoolean(PydevEditorPrefs.DONT_TRACE_ENABLED));
        this.postCommand(cmd);
    }

    private void sendSetDjangoExceptionBreakpointCommand() {
        IPreferenceStore pyPrefsStore = PydevPlugin.getDefault().getPreferenceStore();
        SetDjangoExceptionBreakpointCommand cmd = new SetDjangoExceptionBreakpointCommand(
                this, pyPrefsStore.getBoolean(PydevEditorPrefs.TRACE_DJANGO_TEMPLATE_RENDER_EXCEPTIONS));
        this.postCommand(cmd);
    }

    /**
     * Adds the breakpoints associated with a container.
     * @param container the container we're interested in (usually workspace root)
     */
    private void addBreakpointsFor(IContainer container) {
        try {
            IMarker[] markers = container.findMarkers(PyBreakpoint.PY_BREAK_MARKER, true, IResource.DEPTH_INFINITE);
            IMarker[] condMarkers = container.findMarkers(PyBreakpoint.PY_CONDITIONAL_BREAK_MARKER, true,
                    IResource.DEPTH_INFINITE);
            IMarker[] djangoMarkers = container.findMarkers(PyBreakpoint.DJANGO_BREAK_MARKER, true,
                    IResource.DEPTH_INFINITE);
            IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();

            for (IMarker marker : markers) {
                PyBreakpoint brk = (PyBreakpoint) breakpointManager.getBreakpoint(marker);
                breakpointAdded(brk);
            }

            for (IMarker marker : condMarkers) {
                PyBreakpoint brk = (PyBreakpoint) breakpointManager.getBreakpoint(marker);
                breakpointAdded(brk);
            }

            for (IMarker marker : djangoMarkers) {
                PyBreakpoint brk = (PyBreakpoint) breakpointManager.getBreakpoint(marker);
                breakpointAdded(brk);
            }
        } catch (Throwable t) {
            PydevDebugPlugin.errorDialog("Error setting breakpoints", t);
        }
    }

    /**
     * This function adds the input listener extension point, so that plugins that only care about
     * the input in the console can know about it.
     */
    @SuppressWarnings({ "unchecked" })
    public void addConsoleInputListener() {
        IConsole console = DebugUITools.getConsole(this.getProcess());
        if (console instanceof ProcessConsole) {
            final ProcessConsole c = (ProcessConsole) console;
            final List<IConsoleInputListener> participants = ExtensionHelper
                    .getParticipants(ExtensionHelper.PYDEV_DEBUG_CONSOLE_INPUT_LISTENER);
            final AbstractDebugTarget target = this;
            //let's listen the doc for the changes
            c.getDocument().addDocumentListener(new IDocumentListener() {

                @Override
                public void documentAboutToBeChanged(DocumentEvent event) {
                    //only report when we have a new line
                    if (event.fText.indexOf('\r') != -1 || event.fText.indexOf('\n') != -1) {
                        try {
                            ITypedRegion partition = event.fDocument.getPartition(event.fOffset);
                            if (partition instanceof IOConsolePartition) {
                                IOConsolePartition p = (IOConsolePartition) partition;

                                //we only communicate about inputs (because we only care about what the user writes)
                                if (p.getType().equals(IOConsolePartition.INPUT_PARTITION_TYPE)) {
                                    if (event.fText.length() <= 2) {
                                        //the user typed something
                                        final String inputFound = p.getString();
                                        for (IConsoleInputListener listener : participants) {
                                            listener.newLineReceived(inputFound, target);
                                        }
                                    }

                                }
                            }
                        } catch (Exception e) {
                            Log.log(e);
                        }
                    }

                }

                @Override
                public void documentChanged(DocumentEvent event) {
                    //only report when we have a new line
                    if (event.fText.indexOf('\r') != -1 || event.fText.indexOf('\n') != -1) {
                        try {
                            ITypedRegion partition = event.fDocument.getPartition(event.fOffset);
                            if (partition instanceof IOConsolePartition) {
                                IOConsolePartition p = (IOConsolePartition) partition;

                                //we only communicate about inputs (because we only care about what the user writes)
                                if (p.getType().equals(IOConsolePartition.INPUT_PARTITION_TYPE)) {
                                    if (event.fText.length() > 2) {
                                        //the user pasted something
                                        for (IConsoleInputListener listener : participants) {
                                            listener.pasteReceived(event.fText, target);
                                        }
                                    }

                                }
                            }
                        } catch (Exception e) {
                            Log.log(e);
                        }
                    }
                }

            });
        }
    }

    @Override
    public boolean canDisconnect() {
        return !disconnected;
    }

    @Override
    public void disconnect() throws DebugException {
        this.terminate();
        modificationChecker = null;
    }

    @Override
    public boolean isDisconnected() {
        return disconnected;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getAdapter(Class<T> adapter) {
        AdapterDebug.print(this, adapter);

        // Not really sure what to do here, but I am trying
        if (adapter.equals(ILaunch.class)) {
            return (T) launch;

        } else if (adapter.equals(IResource.class)) {
            // used by Variable ContextManager, and Project:Properties menu item
            if (file != null && file.length > 0) {
                return (T) new PySourceLocatorBase().getFileForLocation(file[0], null);
            } else {
                return null;
            }

        } else if (adapter.equals(org.eclipse.debug.ui.actions.IRunToLineTarget.class)) {
            return (T) this.getRunToLineTarget();

        } else if (adapter.equals(IPropertySource.class)) {
            return launch.getAdapter(adapter);

        } else if (adapter.equals(ITaskListResourceAdapter.class)
                || adapter.equals(org.eclipse.debug.ui.actions.IToggleBreakpointsTarget.class)) {
            return super.getAdapter(adapter);
        }

        AdapterDebug.printDontKnow(this, adapter);
        return super.getAdapter(adapter);
    }

    public PyRunToLineTarget getRunToLineTarget() {
        if (this.runToLineTarget == null) {
            this.runToLineTarget = new PyRunToLineTarget();
        }
        return this.runToLineTarget;
    }

    //From IDebugElement
    @Override
    public ILaunch getLaunch() {
        return launch;
    }

}
