/******************************************************************************
* Copyright (C) 2011-2013  Fabio Zadrozny and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     Fabio Zadrozny <fabiofz@gmail.com>       - initial API and implementation
*     Andrew Ferrazzutti <aferrazz@redhat.com> - ongoing maintenance
******************************************************************************/
package org.python.pydev.shared_ui.actions;

import java.util.Iterator;
import java.util.Map;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextSelection;
import org.python.pydev.shared_core.log.Log;
import org.python.pydev.shared_core.parsing.IScopesParser;
import org.python.pydev.shared_core.parsing.Scopes;
import org.python.pydev.shared_core.string.ICharacterPairMatcher2;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.string.TextSelectionUtils;
import org.python.pydev.shared_core.structure.FastStack;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.editor.BaseEditor;

public class ScopeSelectionAction {

    public static final String SELECTION_SCOPE_CACHE = "_SELECTION_SCOPE_CACHE_";

    public void perform(IDocument doc, ITextSelection selection, BaseEditor editor) {
        FastStack<IRegion> cache = getCache(editor);
        Region initialRegion = new Region(selection.getOffset(), selection.getLength());
        if (cache.size() > 0) {
            IRegion peek = cache.peek();
            if (!peek.equals(initialRegion)) {
                cache.clear();
            }
        }
        if (cache.size() == 0) {
            cache.push(initialRegion);
        }
        ITextSelection newSelection = getNewSelection(doc, selection, editor);
        if (initialRegion.equals(new Region(newSelection.getOffset(), newSelection.getLength()))) {
            return;
        }

        editor.setSelection(newSelection.getOffset(), newSelection.getLength());
        cache.push(new Region(newSelection.getOffset(), newSelection.getLength()));
    }

    public ITextSelection getNewSelection(IDocument doc, ITextSelection selection, BaseEditor editor) {
        return this.getNewSelection(doc, selection, editor.getPairMatcher(), editor.createScopesParser());
    }

    public ITextSelection getNewSelection(IDocument doc, ITextSelection selection, ICharacterPairMatcher2 pairMatcher,
            IScopesParser scopesParser) {
        try {
            TextSelectionUtils ps = new TextSelectionUtils(doc, selection);
            String selectedText = ps.getSelectedText();
            if (selectedText.length() == 0) {
                //Select the current word
                Tuple<String, Integer> currToken = ps.getCurrToken();
                if (currToken.o1.length() > 0) {
                    return new TextSelection(currToken.o2, currToken.o1.length());
                } else {
                    char c = '\0';
                    try {
                        c = ps.getCharAtCurrentOffset();
                    } catch (BadLocationException e) {
                        //Ignore (end of document is selected).
                    }
                    if (StringUtils.isClosingPeer(c)) {
                        int openingOffset = pairMatcher.searchForOpeningPeer(ps.getAbsoluteCursorOffset(),
                                StringUtils.getPeer(c), c, doc);
                        if (openingOffset >= 0) {
                            return new TextSelection(openingOffset, ps.getAbsoluteCursorOffset() - openingOffset + 1);
                        }
                    }
                }
            } else {
                //There's already some text selected
                boolean tryMatchWithQualifier = true;
                boolean hasDotSelected = false; //value only valid if tryMatchWithQualifier == true!
                for (int i = 0; i < selectedText.length(); i++) {
                    char c = selectedText.charAt(i);
                    if (c == '.') {
                        hasDotSelected = true;
                        continue;
                    }
                    if (!Character.isJavaIdentifierPart(c)) {
                        tryMatchWithQualifier = false;
                        break;
                    }
                }
                if (tryMatchWithQualifier) {
                    Tuple<String, Integer> currToken = ps.getCurrToken();
                    if (!hasDotSelected && !currToken.o1.equals(selectedText)) {
                        return new TextSelection(currToken.o2, currToken.o1.length());
                    } else {
                        //The selected text is not equal to the current token, let's see if we have to select a full dotted word
                        Tuple<String, Integer> currDottedToken = ps.getCurrDottedStatement(pairMatcher);
                        if (!currDottedToken.o1.equals(selectedText)) {
                            return new TextSelection(currDottedToken.o2, currDottedToken.o1.length());
                        }
                    }
                }
            }
            Scopes scopes = scopesParser.createScopes(doc);
            //            System.out.println(scopes.debugString(doc));
            IRegion scope = scopes.getScopeForSelection(selection.getOffset(), selection.getLength());
            if (scope != null) {
                return new TextSelection(scope.getOffset(), scope.getLength());
            }

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

        return selection;
    }

    private static String getCurrentSelectionCacheKey(BaseEditor pyEdit) {
        IDocument doc = pyEdit.getDocument();

        int length = doc.getLength();
        String key = Integer.toString(length);
        if (doc instanceof IDocumentExtension4) {
            IDocumentExtension4 document = (IDocumentExtension4) doc;
            long modificationStamp = document.getModificationStamp();
            key += " - " + modificationStamp;
        }
        return key;
    }

    @SuppressWarnings("unchecked")
    public static FastStack<IRegion> getCache(BaseEditor pyEdit) {
        Map<String, Object> cache = pyEdit.getCache();
        String key = getCurrentSelectionCacheKey(pyEdit);
        try {
            Tuple<String, FastStack<IRegion>> object = (Tuple<String, FastStack<IRegion>>) cache
                    .get(SELECTION_SCOPE_CACHE);

            if (object != null) {
                if (key.equals(object.o1)) {
                    return object.o2;
                }
            }
        } catch (Exception e) {
            Log.log(e);
        }

        FastStack<IRegion> stack = new FastStack<IRegion>(20);
        cache.put(SELECTION_SCOPE_CACHE, new Tuple<String, FastStack<IRegion>>(key, stack));
        return stack;
    }

    public void deselect(BaseEditor editor) {
        FastStack<IRegion> stack = ScopeSelectionAction.getCache(editor);

        ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection();
        Region region = new Region(selection.getOffset(), selection.getLength());
        Iterator<IRegion> it = stack.topDownIterator();
        while (it.hasNext()) {
            IRegion iRegion = it.next();
            stack.pop(); //After getting the latest, pop it.

            if (iRegion.equals(region)) {
                if (stack.size() > 0) {
                    IRegion peek = stack.peek();
                    editor.setSelection(peek.getOffset(), peek.getLength());
                }
                break;
            }
        }
    }
}
