package org.python.pydev.debug.referrers;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewSite;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.model.AbstractDebugTarget;
import org.python.pydev.debug.model.IVariableLocator;
import org.python.pydev.debug.model.XMLUtils;
import org.python.pydev.debug.model.XMLUtils.XMLToReferrersInfo;
import org.python.pydev.debug.model.remote.AbstractDebuggerCommand;
import org.python.pydev.debug.model.remote.ICommandResponseListener;
import org.python.pydev.debug.model.remote.RunCustomOperationCommand;
import org.python.pydev.debug.views.BaseDebugView;
import org.python.pydev.debug.views.ILaunchAndDebugListener;
import org.python.pydev.shared_ui.utils.UIUtils;

public class ReferrersView extends BaseDebugView {

    private static final String REFERRERS_VIEW_ID = "org.python.pydev.views.ReferrersView";

    /**
     * May only be called in the UI thread. If the view is not visible, shows it if the
     * preference to do that is set to true.
     *
     * Note that it may return null if the preference to show it is false and the view is not currently shown.
     */
    public static ReferrersView getView(boolean forceVisible) {
        return (ReferrersView) UIUtils.getView(REFERRERS_VIEW_ID, forceVisible);
    }

    @Override
    protected ITreeContentProvider createContentProvider() {
        return new ReferrersViewContentProvider();
    }

    private static final class ReferrerCommandResponseListener implements ICommandResponseListener {
        private final IVariableLocator locator;
        private final AbstractDebugTarget debugTarget;
        private ReferrersView referrersView;

        private ReferrerCommandResponseListener(ReferrersView referrersView, IVariableLocator locator,
                AbstractDebugTarget debugTarget) {
            this.locator = locator;
            this.debugTarget = debugTarget;
            this.referrersView = referrersView;
        }

        @Override
        public void commandComplete(AbstractDebuggerCommand cmd) {
            try {
                if (cmd instanceof RunCustomOperationCommand) {
                    RunCustomOperationCommand c = (RunCustomOperationCommand) cmd;
                    String responsePayload = c.getResponsePayload();
                    if (responsePayload != null) {
                        XMLToReferrersInfo xmlToReferrers = XMLUtils.XMLToReferrers(debugTarget, locator,
                                responsePayload);
                        if (xmlToReferrers != null) {
                            referrersView.addReferrersInfo(xmlToReferrers);
                        }
                    } else {
                        Log.log("Command to get referrers did not return proper value.");
                    }
                }
            } finally {
                this.finish();
            }
        }

        private void finish() {
            boolean removedNow;
            synchronized (referrersView.listenersLock) {
                removedNow = referrersView.listeners.remove(this);
            }

            if (removedNow) {
                referrersView.endProgress();
            }
        }
    }

    @Override
    protected ILaunchAndDebugListener createListener() {
        return new ILaunchAndDebugListener() {

            @Override
            public void launchRemoved(ILaunch launch) {
                IDebugTarget debugTarget = launch.getDebugTarget();
                if (debugTarget instanceof AbstractDebugTarget) {
                    remove((AbstractDebugTarget) debugTarget);
                }
            }

            @Override
            public void launchChanged(ILaunch launch) {
                if (launch.isTerminated()) {
                    this.launchRemoved(launch);
                }
            }

            @Override
            public void launchAdded(ILaunch launch) {
            }

            @Override
            public void handleDebugEvents(DebugEvent[] events) {
                for (DebugEvent debugEvent : events) {
                    if (debugEvent.getSource() instanceof AbstractDebugTarget) {
                        if (debugEvent.getKind() == DebugEvent.TERMINATE) {
                            AbstractDebugTarget debugTarget = (AbstractDebugTarget) debugEvent.getSource();
                            remove(debugTarget);
                        }
                    }
                }

            }

            private void remove(AbstractDebugTarget debugTarget) {
                if (debugTarget.isTerminated()) {
                    synchronized (xmlToReferrersLock) {
                        Iterator<XMLToReferrersInfo> iterator = xmlToReferrers.iterator();
                        while (iterator.hasNext()) {
                            XMLToReferrersInfo next = iterator.next();
                            if (next.target == debugTarget) {
                                iterator.remove();
                            }
                        }
                    }
                    updateTreeJob.schedule();
                }
            }
        };
    }

    public ReferrersView() {
    }

    @Override
    protected void configureToolBar(IViewSite viewSite) {
        IActionBars actionBars = viewSite.getActionBars();
        IToolBarManager toolBar = actionBars.getToolBarManager();
        //IMenuManager menuManager = actionBars.getMenuManager(); -- not adding anything to the menu for now.

        toolBar.add(new ClearCurrentReferrers(this));

    }

    private final Set<ReferrerCommandResponseListener> listeners = new HashSet<>();
    protected final Object listenersLock = new Object();

    @Override
    public void clear() {
        super.clear();
        //Any registered pending command should be stopped now!
        synchronized (listenersLock) {
            for (ReferrerCommandResponseListener referrerCommandResponseListener : listeners) {
                referrerCommandResponseListener.finish();
            }
            listeners.clear();
            synchronized (xmlToReferrersLock) {
                this.xmlToReferrers.clear();
            }
        }
    }

    public void showReferrersFor(final AbstractDebugTarget debugTarget, final IVariableLocator locator) {
        RunCustomOperationCommand cmd = new RunCustomOperationCommand(debugTarget, locator,
                "from _pydevd_bundle.pydevd_referrers import get_referrer_info",
                "get_referrer_info");

        ReferrerCommandResponseListener listener = new ReferrerCommandResponseListener(this, locator, debugTarget);

        synchronized (listenersLock) {
            startProgress();
            listeners.add(listener);
        }
        cmd.setCompletionListener(listener);

        debugTarget.postCommand(cmd);
    }

    // Information to add to the tree and updating it ------------------------------------------------------------------

    protected final List<XMLToReferrersInfo> xmlToReferrers = new ArrayList<>();
    protected final Object xmlToReferrersLock = new Object();

    @Override
    protected void onSetTreeInput() {
        XMLToReferrersInfo[] array;
        int size = xmlToReferrers.size();
        synchronized (xmlToReferrersLock) {
            array = xmlToReferrers.toArray(new XMLToReferrersInfo[size]);
        }
        viewer.setInput(array);
    }

    protected void addReferrersInfo(XMLToReferrersInfo xmlToReferrers) {
        synchronized (xmlToReferrersLock) {
            this.xmlToReferrers.add(xmlToReferrers);
        }
        updateTreeJob.schedule();
    }

    @Override
    protected void makeLastVisibleInTree(Object input) {
        if (input instanceof XMLToReferrersInfo[]) {
            XMLToReferrersInfo[] xmlToReferrersInfos = (XMLToReferrersInfo[]) input;
            if (xmlToReferrersInfos.length > 0) {
                //i.e.: scroll to the last added element.
                XMLToReferrersInfo element = xmlToReferrersInfos[xmlToReferrersInfos.length - 1];
                if (element.forVar != null) {
                    viewer.reveal(element.forVar);
                }
            }
        }
    }

}
