/**
 * 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.console;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.internal.ui.views.console.ProcessConsole;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsolePageParticipant;
import org.eclipse.ui.console.TextConsoleViewer;
import org.eclipse.ui.internal.console.IOConsolePage;
import org.eclipse.ui.internal.console.IOConsolePartition;
import org.eclipse.ui.part.IPageBookViewPage;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.core.Constants;
import org.python.pydev.debug.model.AbstractDebugTarget;
import org.python.pydev.debug.model.PyStackFrame;
import org.python.pydev.debug.model.XMLUtils;
import org.python.pydev.debug.model.remote.AbstractDebuggerCommand;
import org.python.pydev.debug.model.remote.GetCompletionsCommand;
import org.python.pydev.debug.model.remote.ICommandResponseListener;
import org.python.pydev.debug.newconsole.CurrentPyStackFrameForConsole;
import org.python.pydev.debug.newconsole.PydevConsoleCommunication;
import org.python.pydev.debug.newconsole.PydevConsoleCompletionProcessor;
import org.python.pydev.debug.newconsole.PydevConsoleInterpreter;
import org.python.pydev.editor.codecompletion.PyCodeCompletionPreferencesPage;
import org.python.pydev.editor.codecompletion.PyContentAssistant;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_interactive_console.console.IScriptConsoleCommunication;
import org.python.pydev.shared_interactive_console.console.InterpreterResponse;
import org.python.pydev.shared_ui.bindings.KeyBindingHelper;

/**
 * Will provide code-completion in debug sessions.
 */
@SuppressWarnings("restriction")
public class ConsoleCompletionsPageParticipant implements IConsolePageParticipant {

    /**
     * Class to get the completions in debug mode in a suspended frame.
     */
    public static class GetCompletionsInDebug implements IScriptConsoleCommunication, ICommandResponseListener {

        private static final ICompletionProposal[] EMPTY_COMPLETION_PROPOSALS = new ICompletionProposal[0];
        private String actTok;
        private String text;
        private int offset;
        private volatile List<Object[]> receivedXmlCompletions;
        private CurrentPyStackFrameForConsole currentPyStackFrameForConsole;

        public GetCompletionsInDebug(CurrentPyStackFrameForConsole currentPyStackFrameForConsole) {
            this.currentPyStackFrameForConsole = currentPyStackFrameForConsole;
        }

        @Override
        public String getDescription(String text) throws Exception {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public boolean isConnected() {
            return this.currentPyStackFrameForConsole.getLastSelectedFrame() != null;
        }

        /**
         * Gets the completions at the passed offset.
         */
        @Override
        public ICompletionProposal[] getCompletions(String text, String actTok, int offset,
                boolean showForTabCompletion)
                throws Exception {
            this.text = text;
            this.actTok = actTok;
            this.offset = offset;
            PyStackFrame stackFrame = currentPyStackFrameForConsole.getLastSelectedFrame();

            if (stackFrame != null) {
                AbstractDebugTarget target = (AbstractDebugTarget) stackFrame.getAdapter(IDebugTarget.class);
                if (target != null) {
                    GetCompletionsCommand cmd = new GetCompletionsCommand(target, actTok, stackFrame.getLocalsLocator()
                            .getPyDBLocation());
                    cmd.setCompletionListener(this);
                    target.postCommand(cmd);
                }
                return waitForCommand();
            }
            return EMPTY_COMPLETION_PROPOSALS;
        }

        /**
         * Keeps in a loop for 3 seconds or until the completions are found. If no completions are found in that time,
         * returns an empty array.
         */
        private ICompletionProposal[] waitForCommand() {
            int i = 300; //wait up to 3 seconds
            while (--i > 0 && receivedXmlCompletions == null) {
                try {
                    Thread.sleep(10); //10 millis
                } catch (InterruptedException e) {
                    //ignore
                }
            }

            List<Object[]> fromServer = receivedXmlCompletions;
            receivedXmlCompletions = null;
            if (fromServer == null) {
                Log.logInfo("Timeout for waiting for debug completions elapsed (3 seconds).");
                return EMPTY_COMPLETION_PROPOSALS;
            }
            List<ICompletionProposal> ret = new ArrayList<ICompletionProposal>(fromServer.size());
            PydevConsoleCommunication.convertConsoleCompletionsToICompletions(text, actTok, offset, fromServer, ret,
                    false);
            return ret.toArray(new ICompletionProposal[0]);
        }

        @Override
        public void execInterpreter(String command, ICallback<Object, InterpreterResponse> onResponseReceived) {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public void interrupt() {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public void setOnContentsReceivedCallback(ICallback<Object, Tuple<String, String>> onContentsReceived) {
            throw new RuntimeException("Not implemented");

        }

        @Override
        public void close() throws Exception {
            throw new RuntimeException("Not implemented");
        }

        /**
         * Received when the completions command receives a response (ICommandResponseListener)
         * Converts the xml to completions.
         */
        @Override
        public void commandComplete(AbstractDebuggerCommand cmd) {
            GetCompletionsCommand compCmd = (GetCompletionsCommand) cmd;
            try {
                String response = compCmd.getResponse();
                List<Object[]> fromServer = XMLUtils.convertXMLcompletionsFromConsole(response);
                receivedXmlCompletions = fromServer;
            } catch (CoreException e) {
                receivedXmlCompletions = new ArrayList<>(0);
                Log.log(e);
            }

        }

        @Override
        public void linkWithDebugSelection(boolean isLinkedWithDebug) {
            throw new RuntimeException("Not implemented");
        }

    }

    /**
     * The content assistant added to the console.
     */
    private PyContentAssistant contentAssist;

    @Override
    public <T> T getAdapter(Class<T> adapter) {

        return null;
    }

    /**
     * When a console page is initialized,
     */
    @Override
    public void init(IPageBookViewPage page, final IConsole console) {
        if (!(console instanceof ProcessConsole)) {
            return;
        }
        ProcessConsole processConsole = (ProcessConsole) console;
        IProcess process = processConsole.getProcess();
        if (process == null) {
            return;
        }
        if (!PyCodeCompletionPreferencesPage.useCodeCompletion()
                || !PyCodeCompletionPreferencesPage.useCodeCompletionOnDebug()) {
            return;
        }
        String attribute = process.getAttribute(Constants.PYDEV_DEBUG_IPROCESS_ATTR);
        if (!Constants.PYDEV_DEBUG_IPROCESS_ATTR_TRUE.equals(attribute)) {
            //Only provide code-completion for pydev debug processes.
            return;
        }
        Control control = page.getControl();
        if (page instanceof IOConsolePage) {

            //Note that completions on "all letters and '_'" are already activated just by installing
            //the content assist, but the completions on the default keybinding is not, so, we have to
            //call it ourselves here.
            control.addKeyListener(new KeyListener() {
                @Override
                public void keyPressed(KeyEvent e) {

                    if (KeyBindingHelper.matchesContentAssistKeybinding(e)) {
                        contentAssist.showPossibleCompletions();
                    }
                }

                @Override
                public void keyReleased(KeyEvent e) {
                }
            });

            final CurrentPyStackFrameForConsole currentPyStackFrameForConsole = new CurrentPyStackFrameForConsole(
                    console);
            IOConsolePage consolePage = (IOConsolePage) page;
            TextConsoleViewer viewer = consolePage.getViewer();

            PydevConsoleInterpreter interpreter = new PydevConsoleInterpreter();
            interpreter.setLaunchAndRelatedInfo(process.getLaunch());
            interpreter.setConsoleCommunication(new GetCompletionsInDebug(currentPyStackFrameForConsole));

            contentAssist = new PyContentAssistant() {
                @Override
                public String showPossibleCompletions() {
                    //Only show completions if we're in a suspended console.
                    if (currentPyStackFrameForConsole.getLastSelectedFrame() == null) {
                        return null;
                    }
                    return super.showPossibleCompletions();
                };
            };
            contentAssist.setInformationControlCreator(PyContentAssistant.createInformationControlCreator(viewer));
            contentAssist.install(new ScriptConsoleViewerWrapper(viewer, interpreter.getInterpreterInfo()));

            IContentAssistProcessor processor = new PydevConsoleCompletionProcessor(interpreter, contentAssist);
            contentAssist.setContentAssistProcessor(processor, IOConsolePartition.INPUT_PARTITION_TYPE);
            contentAssist.setContentAssistProcessor(processor, IOConsolePartition.OUTPUT_PARTITION_TYPE);

            contentAssist.enableAutoActivation(true);
            contentAssist.enableAutoInsert(false);
            contentAssist.setAutoActivationDelay(PyCodeCompletionPreferencesPage.getAutocompleteDelay());
        }
    }

    @Override
    public void dispose() {

    }

    @Override
    public void activated() {

    }

    @Override
    public void deactivated() {

    }

}
