/**
 * 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.
 */
/*
 * Created on 24/09/2005
 */
package com.python.pydev.analysis.ctrl_1;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.ICodeCompletionASTManager;
import org.python.pydev.core.IModulesManager;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.codefolding.MarkerAnnotationAndPosition;
import org.python.pydev.editor.codefolding.PySourceViewer;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.ImageCache;
import org.python.pydev.shared_ui.UIConstants;
import org.python.pydev.shared_ui.proposals.IPyCompletionProposal;

import com.python.pydev.analysis.AnalysisPlugin;
import com.python.pydev.analysis.CtxInsensitiveImportComplProposal;
import com.python.pydev.analysis.IAnalysisPreferences;
import com.python.pydev.analysis.additionalinfo.AbstractAdditionalTokensInfo;
import com.python.pydev.analysis.additionalinfo.AdditionalProjectInterpreterInfo;
import com.python.pydev.analysis.additionalinfo.IInfo;
import com.python.pydev.analysis.builder.AnalysisParserObserver;
import com.python.pydev.analysis.builder.AnalysisRunner;
import com.python.pydev.analysis.ui.AutoImportsPreferencesPage;

/**
 * Class that'll create proposals for fixing an undefined variable found.
 *
 * @author Fabio
 */
public class UndefinedVariableFixParticipant implements IAnalysisMarkersParticipant {

    /**
     * Defines whether a reparse should be forced after applying the completion.
     */
    private boolean forceReparseOnApply;

    public UndefinedVariableFixParticipant() {
        this(true);
    }

    public UndefinedVariableFixParticipant(boolean forceReparseOnApply) {
        this.forceReparseOnApply = forceReparseOnApply;
    }

    /**
     * @see IAnalysisMarkersParticipant#addProps(MarkerAnnotation, IAnalysisPreferences, String, PySelection, int, IPythonNature, 
     * PyEdit, List)
     * 
     */
    public void addProps(MarkerAnnotationAndPosition markerAnnotation, IAnalysisPreferences analysisPreferences,
            String line, PySelection ps, int offset, IPythonNature nature, PyEdit edit, List<ICompletionProposal> props)
            throws BadLocationException, CoreException {
        IMarker marker = markerAnnotation.markerAnnotation.getMarker();
        Integer id = (Integer) marker.getAttribute(AnalysisRunner.PYDEV_ANALYSIS_TYPE);
        if (id != IAnalysisPreferences.TYPE_UNDEFINED_VARIABLE) {
            return;
        }
        if (nature == null) {
            return;
        }
        ICodeCompletionASTManager astManager = nature.getAstManager();
        if (astManager == null) {
            return;
        }

        if (markerAnnotation.position == null) {
            return;
        }
        int start = markerAnnotation.position.offset;
        int end = start + markerAnnotation.position.length;
        ps.setSelection(start, end);
        String markerContents = ps.getSelectedText();
        String fullRep = ps.getFullRepAfterSelection();

        ImageCache imageCache = PydevPlugin.getImageCache();
        Image packageImage = null;
        if (imageCache != null) { //making tests
            packageImage = imageCache.get(UIConstants.COMPLETION_PACKAGE_ICON);
        }
        IModulesManager projectModulesManager = astManager.getModulesManager();
        Set<String> allModules = projectModulesManager.getAllModuleNames(true, markerContents.toLowerCase());

        //when an undefined variable is found, we can:
        // - add an auto import (if it is a class or a method or some global attribute)
        // - declare it as a local or global variable
        // - change its name to some other global or local (mistyped)
        // - create a method or class for it (if it is a call)

        Set<Tuple<String, String>> mods = new HashSet<Tuple<String, String>>();
        //1. check if it is some module

        //use a single buffer to create all the strings
        FastStringBuffer buffer = new FastStringBuffer();
        boolean doIgnoreImportsStartingWithUnder = AutoImportsPreferencesPage.doIgnoreImportsStartingWithUnder();

        for (String completeName : allModules) {
            FullRepIterable iterable = new FullRepIterable(completeName);

            for (String mod : iterable) {

                if (fullRep.startsWith(mod)) {

                    if (fullRep.length() == mod.length() //it does not only start with, but it is equal to it.
                            || (fullRep.length() > mod.length() && fullRep.charAt(mod.length()) == '.')) {
                        buffer.clear();
                        String realImportRep = buffer.append("import ").append(mod).toString();
                        buffer.clear();
                        String displayString = buffer.append("Import ").append(mod).toString();
                        addProp(props, realImportRep, displayString, packageImage, offset, mods);
                    }
                }

                String[] strings = FullRepIterable.headAndTail(mod);
                String packageName = strings[0];
                String importRep = strings[1];

                if (importRep.equals(markerContents)) {
                    if (packageName.length() > 0) {
                        buffer.clear();
                        String realImportRep = buffer.append("from ").append(packageName).append(" ").append("import ")
                                .append(strings[1]).toString();
                        buffer.clear();
                        String displayString = buffer.append("Import ").append(importRep).append(" (")
                                .append(packageName).append(")").toString();
                        addProp(props, realImportRep, displayString, packageImage, offset, mods);

                    } else {
                        buffer.clear();
                        String realImportRep = buffer.append("import ").append(strings[1]).toString();
                        buffer.clear();
                        String displayString = buffer.append("Import ").append(importRep).toString();
                        addProp(props, realImportRep, displayString, packageImage, offset, mods);
                    }
                }
            }
        }

        //2. check if it is some global class or method
        List<AbstractAdditionalTokensInfo> additionalInfo;
        try {
            additionalInfo = AdditionalProjectInterpreterInfo.getAdditionalInfo(nature);
        } catch (MisconfigurationException e) {
            return;
        }
        FastStringBuffer tempBuf = new FastStringBuffer();
        for (AbstractAdditionalTokensInfo info : additionalInfo) {
            Collection<IInfo> tokensEqualTo = info.getTokensEqualTo(markerContents,
                    AbstractAdditionalTokensInfo.TOP_LEVEL);
            for (IInfo found : tokensEqualTo) {
                //there always is a declaring module
                String name = found.getName();
                String declPackage = found.getDeclaringModuleName();
                String declPackageWithoutInit = declPackage;
                if (declPackageWithoutInit.endsWith(".__init__")) {
                    declPackageWithoutInit = declPackageWithoutInit.substring(0, declPackageWithoutInit.length() - 9);
                }

                declPackageWithoutInit = AutoImportsPreferencesPage.removeImportsStartingWithUnderIfNeeded(
                        declPackageWithoutInit, tempBuf, doIgnoreImportsStartingWithUnder);

                buffer.clear();
                String importDeclaration = buffer.append("from ").append(declPackageWithoutInit).append(" import ")
                        .append(name).toString();

                buffer.clear();
                String displayImport = buffer.append("Import ").append(name).append(" (").append(declPackage)
                        .append(")").toString();

                addProp(props, importDeclaration, displayImport, AnalysisPlugin.getImageForAutoImportTypeInfo(found),
                        offset, mods);
            }
        }
    }

    private void addProp(List<ICompletionProposal> props, String importDeclaration, String displayImport,
            Image importImage, int offset, Set<Tuple<String, String>> mods) {
        Tuple<String, String> tuple = new Tuple<String, String>(importDeclaration, displayImport);
        if (mods.contains(tuple)) {
            return;
        }

        mods.add(tuple);

        String tooltip = importDeclaration + "\n\nNote: Hold Ctrl on apply to do local import.";
        props.add(new CtxInsensitiveImportComplProposal("", offset, 0, 0, importImage, displayImport, null, tooltip,
                IPyCompletionProposal.PRIORITY_LOCALS, importDeclaration) {

            @Override
            public void selected(ITextViewer viewer, boolean smartToggle) {
                //Overridden to do nothing (i.e.: don't leave yellow when ctrl is pressed).
            }

            @Override
            public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
                if ((stateMask & SWT.CTRL) != 0) {
                    this.setAddLocalImport(true);
                }
                super.apply(viewer, trigger, stateMask, offset);
                if (forceReparseOnApply) {
                    //and after applying it, let's request a reanalysis
                    if (viewer instanceof PySourceViewer) {
                        PySourceViewer sourceViewer = (PySourceViewer) viewer;
                        PyEdit edit = sourceViewer.getEdit();
                        if (edit != null) {
                            edit.getParser().forceReparse(
                                    new Tuple<String, Boolean>(AnalysisParserObserver.ANALYSIS_PARSER_OBSERVER_FORCE,
                                            true));
                        }
                    }
                }
            }

        });
    }

}
