/**
 * 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 Oct 25, 2004
 *
 * @author Fabio Zadrozny
 */
package org.python.pydev.builder;

import java.io.File;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.IDocument;
import org.python.pydev.core.ICodeCompletionASTManager;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IModulesManager;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.editor.codecompletion.revisited.ProjectModulesManager;
import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper;
import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.shared_core.callbacks.ICallback0;

/**
 * Visitors within pydev should be subclasses of this class.
 *
 * They should be prepared for being reused to, as they are instantiated and reused for visiting many resources.
 *
 * @author Fabio Zadrozny
 */
public abstract class PyDevBuilderVisitor implements Comparable<PyDevBuilderVisitor> {

    public static final int MAX_TO_VISIT_INFINITE = -1;

    /**
     * identifies the key for the module in the cache
     */
    private static final String MODULE_CACHE = "MODULE_CACHE"; //$NON-NLS-1$

    /**
     * identifies the key for the module name in the cache
     */
    private static final String MODULE_NAME_CACHE = "MODULE_NAME"; //$NON-NLS-1$

    /*default*/static final String MODULE_IN_PROJECT_PYTHONPATH = "MODULE_IN_PROJECT_PYTHONPATH"; //$NON-NLS-1$

    /**
     * The default priority is 5.
     *
     * Higher priorities are minor numbers (and vice-versa).
     */
    public static final int PRIORITY_DEFAULT = 5;

    /**
     * Maximum priority is 0
     */
    public static final int PRIORITY_MAX = 0;

    /**
     * Minimum priority is 10
     */
    public static final int PRIORITY_MIN = 10;

    /**
     * Compares them by priority (they are ordered before visiting by priority, so, this can
     * be useful if some visitor needs to run only after some other visitor was executed).
     */
    @Override
    public int compareTo(PyDevBuilderVisitor o) {
        int priority = getPriority();
        int otherPriority = o.getPriority();
        if (priority < otherPriority) {
            return -1;
        }
        if (otherPriority < priority) {
            return 1;
        }
        return 0; //equal
    }

    /**
     * @return the priority of this visitor (visitors with higher priority --
     * lower numbers -- are visited before)
     */
    protected int getPriority() {
        return PRIORITY_DEFAULT;
    }

    /**
     * This field acts like a memory.
     *
     * It is set before a given resource is visited, and is maintained
     * for each visitor while the same resource is being visited.
     *
     * In this way, we can keep from having to recreate some info (such as the ast) each time over and over
     * for each visitor.
     */
    public VisitorMemo memo;

    /**
     * Constant indicating value in memory to represent a full build.
     */
    public static final String IS_FULL_BUILD = "IS_FULL_BUILD"; //$NON-NLS-1$

    /**
     * Constant indicating value in memory to represent the creation time of the document in memory that the visitor
     * is getting.
     */
    public static final String DOCUMENT_TIME = "DOCUMENT_TIME"; //$NON-NLS-1$

    /**
     * @return whether we are doing a full build right now.
     */
    protected boolean isFullBuild() {
        Boolean b = (Boolean) memo.get(IS_FULL_BUILD);
        if (b == null) {
            return false; // we surely will have it set when it is a full build. (the other way around may not be true).
        }
        return b.booleanValue();
    }

    /**
     * @return The time of the document creation used for this visitor (in current time millis)
     * or -1 if the document creation time is not available.
     */
    protected long getDocumentTime() {
        Long b = (Long) memo.get(DOCUMENT_TIME);
        if (b == null) {
            return -1;
        }
        return b.longValue();
    }

    /**
     * This method returns the module that is created from the given resource.
     *
     * It also uses the cache, to see if the module is already available for that.
     *
     * @param resource the resource we are analyzing
     * @param document the document with the resource contents
     * @return the module that is created by the given resource
     * @throws MisconfigurationException
     */
    protected SourceModule getSourceModule(IResource resource, IDocument document, IPythonNature nature)
            throws MisconfigurationException {
        SourceModule module = (SourceModule) memo.get(MODULE_CACHE + resource.getModificationStamp());
        if (module == null) {
            module = createSoureModule(resource, document, getModuleName(resource, nature));
            setModuleInCache(resource, module);
        }
        return module;
    }

    /**
     * @param module this is the module to set in the cache
     */
    protected void setModuleInCache(IResource resource, IModule module) {
        memo.put(MODULE_CACHE + resource.getModificationStamp(), module);
    }

    /**
     * @param resource
     * @param document
     * @return
     * @throws MisconfigurationException
     */
    protected SourceModule createSoureModule(IResource resource, IDocument document, String moduleName)
            throws MisconfigurationException {
        SourceModule module;
        PythonNature nature = PythonNature.getPythonNature(resource.getProject());
        IFile f = (IFile) resource;
        String file = f.getRawLocation().toOSString();
        module = AbstractModule.createModuleFromDoc(moduleName, new File(file), document, nature, true);
        return module;
    }

    /**
     * @param resource the resource we are analyzing
     * @return the nature associated to the project where the resource is contained
     */
    protected PythonNature getPythonNature(IResource resource) {
        PythonNature pythonNature = PythonNature.getPythonNature(resource);
        return pythonNature;
    }

    /**
     * @param resource must be the resource we are analyzing because it will go to the cache without the resource (only as MODULE_NAME_CACHE)
     * @return the name of the module we are analyzing (given tho resource)
     * @throws MisconfigurationException
     */
    public String getModuleName(IResource resource, IPythonNature nature) throws MisconfigurationException {
        String moduleName = (String) memo.get(getModuleNameCacheKey(resource));
        if (moduleName == null) {
            moduleName = nature.resolveModule(resource);
            if (moduleName != null) {
                setModuleNameInCache(memo, resource, moduleName);
            } else {
                throw new RuntimeException("Unable to resolve module for:" + resource); //$NON-NLS-1$
            }
        }
        return moduleName;
    }

    private static String getModuleNameCacheKey(IResource resource) {
        return MODULE_NAME_CACHE + resource.getModificationStamp();
    }

    /**
     * @param moduleName the module name to set in the cache
     */
    public static void setModuleNameInCache(Map<String, Object> memo, IResource resource, String moduleName) {
        memo.put(getModuleNameCacheKey(resource), moduleName);
    }

    public boolean isResourceInPythonpathProjectSources(IResource resource, IPythonNature nature, boolean addExternal)
            throws CoreException, MisconfigurationException {
        Boolean isInProjectPythonpath = (Boolean) memo.get(MODULE_IN_PROJECT_PYTHONPATH + addExternal);
        if (isInProjectPythonpath == null) {

            //This was simply: String moduleName = nature.resolveModuleOnlyInProjectSources(resource, addExternal);
            //Inlined with the code below because nature.getPythonPathNature().getOnlyProjectPythonPathStr was one of
            //the slowest things when doing a full build.

            List<String> onlyProjectPythonPathLst = memo.getOnlyProjectPythonPathStr(nature, addExternal);

            String resourceOSString = PydevPlugin.getIResourceOSString(resource);
            String moduleName = null;
            if (resourceOSString != null) {
                ICodeCompletionASTManager astManager = nature.getAstManager();
                if (astManager != null) {
                    IModulesManager modulesManager = astManager.getModulesManager();
                    if (modulesManager instanceof ProjectModulesManager) {
                        PythonPathHelper pythonPathHelper = ((ProjectModulesManager) modulesManager)
                                .getPythonPathHelper();
                        moduleName = pythonPathHelper.resolveModule(
                                resourceOSString, false, onlyProjectPythonPathLst, nature.getProject());
                    }
                }
            }

            isInProjectPythonpath = (moduleName != null);
            if (isInProjectPythonpath) {
                setModuleNameInCache(memo, resource, moduleName);
            }

        }
        return isInProjectPythonpath;
    }

    /**
     * @param resource the resource we want to know about
     * @return true if it is in the pythonpath
     */
    public static boolean isInPythonPath(IResource resource) {
        if (resource == null) {
            return false;
        }
        IProject project = resource.getProject();
        PythonNature nature = PythonNature.getPythonNature(project);
        if (project != null && nature != null) {
            ICodeCompletionASTManager astManager = nature.getAstManager();
            if (astManager != null) {
                IModulesManager modulesManager = astManager.getModulesManager();
                return modulesManager.isInPythonPath(resource, project);
            }
        }

        return false;
    }

    /**
     *
     * @return the maximun number of resources that it is allowed to visit (if this
     * number is higher than the number of resources changed, this visitor is not called).
     */
    public int maxResourcesToVisit() {
        return MAX_TO_VISIT_INFINITE;
    }

    /**
     * Called when a resource is changed
     *
     * @param resource to be visited.
     */
    public abstract void visitChangedResource(IResource resource, ICallback0<IDocument> document,
            IProgressMonitor monitor);

    /**
     * Called when a resource is added. Default implementation calls the same method
     * used for change.
     *
     * @param resource to be visited.
     */
    public void visitAddedResource(IResource resource, ICallback0<IDocument> document, IProgressMonitor monitor) {
        visitChangedResource(resource, document, monitor);
    }

    /**
     * Called when a resource is removed
     *
     * @param resource to be visited.
     */
    public abstract void visitRemovedResource(IResource resource, ICallback0<IDocument> document,
            IProgressMonitor monitor);

    /**
     * This function is called right before a visiting session starts for a delta (end will
     * only be called when the whole delta is processed).
     * @param monitor this is the monitor that will be used in the visit
     * @param nature
     */
    public void visitingWillStart(IProgressMonitor monitor, boolean isFullBuild, IPythonNature nature) {

    }

    /**
     * This function is called when we finish visiting some delta (which may be the whole project or
     * just some files).
     *
     * A use-case is: It may be overridden if we need to store info in a persisting location
     * @param monitor this is the monitor used in the visit
     */
    public void visitingEnded(IProgressMonitor monitor) {

    }
}
