/**
 * 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.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.jface.text.IDocument;
import org.python.pydev.builder.pycremover.PycHandlerBuilderVisitor;
import org.python.pydev.builder.pylint.PyLintVisitor;
import org.python.pydev.builder.syntaxchecker.PySyntaxChecker;
import org.python.pydev.builder.todo.PyTodoVisitor;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.FileUtilsFileBuffer;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IPythonPathNature;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.codecompletion.revisited.PyCodeCompletionVisitor;
import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.shared_core.callbacks.ICallback0;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.utils.PyFileListing;

/**
 * This builder only passes through python files
 *
 * @author Fabio Zadrozny
 */
public class PyDevBuilder extends IncrementalProjectBuilder {

    private static final boolean DEBUG = false;

    /**
     *
     * @return a list of visitors for building the application.
     */
    public List<PyDevBuilderVisitor> getVisitors() {
        List<PyDevBuilderVisitor> list = new ArrayList<PyDevBuilderVisitor>();
        list.add(new PyTodoVisitor());
        list.add(new PyLintVisitor());
        list.add(new PyCodeCompletionVisitor());
        list.add(new PycHandlerBuilderVisitor());
        list.add(new PySyntaxChecker());

        list.addAll(ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_BUILDER));
        return list;
    }

    /**
     * Marking that no locking should be done during the build.
     */
    public ISchedulingRule getRule() {
        return null;
    }

    @Override
    public ISchedulingRule getRule(int kind, Map<String, String> args) {
        return null;
    }

    /**
     * Builds the project.
     *
     * @see org.eclipse.core.internal.events InternalBuilder#build(int, java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException {

        if (PyDevBuilderPrefPage.usePydevBuilders() == false) {
            return null;
        }

        if (kind == IncrementalProjectBuilder.FULL_BUILD || kind == IncrementalProjectBuilder.CLEAN_BUILD) {
            // Do a Full Build: Use a ResourceVisitor to process the tree.
            //Timer timer = new Timer();
            performFullBuild(monitor);
            //timer.printDiff("Total time for analysis of: " + getProject());

        } else {
            // Build it with a delta
            IResourceDelta delta = getDelta(getProject());

            if (delta == null) {
                //no delta (unspecified changes?... let's do a full build...)
                performFullBuild(monitor);

            } else {
                VisitorMemo memo = new VisitorMemo();
                memo.put(PyDevBuilderVisitor.IS_FULL_BUILD, false); //mark it as delta build

                // ok, we have a delta
                // first step is just counting them
                PyDevDeltaCounter counterVisitor = new PyDevDeltaCounter();
                counterVisitor.memo = memo;
                delta.accept(counterVisitor);

                List<PyDevBuilderVisitor> visitors = getVisitors();

                //sort by priority
                Collections.sort(visitors);

                PydevGrouperVisitor grouperVisitor = new PydevGrouperVisitor(visitors, monitor,
                        counterVisitor.getNVisited());
                grouperVisitor.memo = memo;

                try (AutoCloseable closeable = withStartEndVisitingNotifications(visitors, monitor, false, null)) {
                    try {
                        delta.accept(grouperVisitor);
                    } catch (Exception e) {
                        Log.log(e);
                    }
                    try {
                        grouperVisitor.finishDelayedVisits();
                    } catch (Exception e) {
                        Log.log(e);
                    }
                } catch (Exception e1) {
                    Log.log(e1);
                }
            }
        }
        return null;
    }

    /**
     * Processes all python files.
     *
     * @param monitor
     */
    private void performFullBuild(IProgressMonitor monitor) throws CoreException {
        IProject project = getProject();

        //we need the project...
        if (project != null) {
            IPythonNature nature = PythonNature.getPythonNature(project);

            //and the nature...
            if (nature != null && nature.startRequests()) {
                nature.updateMtime();

                try {
                    IPythonPathNature pythonPathNature = nature.getPythonPathNature();
                    pythonPathNature.getProjectSourcePath(false); //this is just to update the paths (in case the project name has just changed)

                    List<IFile> resourcesToParse = new ArrayList<IFile>();

                    List<PyDevBuilderVisitor> visitors = getVisitors();
                    try (AutoCloseable closable = withStartEndVisitingNotifications(visitors, monitor, true, nature)) {
                        monitor.beginTask("Building...", (visitors.size() * 100) + 30);

                        IResource[] members = project.members();

                        if (members != null) {
                            // get all the python files to get information.
                            int len = members.length;
                            for (int i = 0; i < len; i++) {
                                try {
                                    IResource member = members[i];
                                    if (member == null) {
                                        continue;
                                    }

                                    if (member.getType() == IResource.FILE) {
                                        addToResourcesToParse(resourcesToParse, (IFile) member, nature);

                                    } else if (member.getType() == IResource.FOLDER) {
                                        //if it is a folder, let's get all python files that are beneath it
                                        //the heuristics to know if we have to analyze them are the same we have
                                        //for a single file
                                        List<IFile> files = PyFileListing.getAllIFilesBelow((IFolder) member);

                                        for (IFile file : files) {
                                            if (file != null) {
                                                addToResourcesToParse(resourcesToParse, file, nature);
                                            }
                                        }
                                    } else {
                                        if (DEBUG) {
                                            System.out.println("Unknown type: " + member.getType());
                                        }
                                    }
                                } catch (Exception e) {
                                    // that's ok...
                                }
                            }
                            monitor.worked(30);
                            buildResources(resourcesToParse, monitor, visitors);
                        }
                    } catch (Exception e1) {
                        Log.log(e1);
                    }

                } finally {
                    nature.endRequests();
                }
            }
        }
        monitor.done();

    }

    private AutoCloseable withStartEndVisitingNotifications(final List<PyDevBuilderVisitor> visitors,
            final IProgressMonitor monitor,
            boolean isFullBuild, IPythonNature nature) {
        for (PyDevBuilderVisitor visitor : visitors) {
            try {
                visitor.visitingWillStart(monitor, isFullBuild, nature);
            } catch (Throwable e) {
                Log.log(e);
            }
        }
        return new AutoCloseable() {
            @Override
            public void close() throws Exception {
                for (PyDevBuilderVisitor visitor : visitors) {
                    try {
                        visitor.visitingEnded(monitor);
                    } catch (Throwable e) {
                        Log.log(e);
                    }
                }
            }
        };
    }

    /**
     * @param resourcesToParse the list where the resource may be added
     * @param member the resource we are adding
     * @param nature the nature associated to the resource
     */
    private void addToResourcesToParse(List<IFile> resourcesToParse, IFile member, IPythonNature nature) {
        //analyze it only if it is a valid source file
        String fileExtension = member.getFileExtension();
        if (DEBUG) {
            System.out.println("Checking name:'" + member.getName() + "' projPath:'" + member.getProjectRelativePath()
                    + "' ext:'" + fileExtension + "'");
            System.out.println("loc:'" + member.getLocation() + "' rawLoc:'" + member.getRawLocation() + "'");

        }
        if (fileExtension != null && PythonPathHelper.isValidSourceFile("." + fileExtension)) {
            if (DEBUG) {
                System.out.println("Adding resource to parse:" + member.getProjectRelativePath());
            }
            resourcesToParse.add(member);
        }
    }

    /**
     * Default implementation. Visits each resource once at a time. May be overridden if a better implementation is needed.
     *
     * @param resourcesToParse list of resources from project that are python files.
     * @param monitor
     * @param visitors
     */
    public void buildResources(List<IFile> resourcesToParse, IProgressMonitor monitor,
            List<PyDevBuilderVisitor> visitors) {

        // we have 100 units here
        double inc = (visitors.size() * 100) / (double) resourcesToParse.size();

        double total = 0;
        int totalResources = resourcesToParse.size();
        int i = 0;

        FastStringBuffer bufferToCreateString = new FastStringBuffer();

        boolean loggedMisconfiguration = false;
        long lastProgressTime = 0;

        Object memoSharedProjectState = null;
        for (Iterator<IFile> iter = resourcesToParse.iterator(); iter.hasNext() && monitor.isCanceled() == false;) {
            i += 1;
            total += inc;
            IFile r = iter.next();

            PythonPathHelper.markAsPyDevFileIfDetected(r);

            IPythonNature nature = PythonNature.getPythonNature(r);
            if (nature == null) {
                continue;
            }
            if (!nature.startRequests()) {
                continue;
            }
            try {
                String moduleName;
                try {
                    //we visit external because we must index them
                    moduleName = nature.resolveModuleOnlyInProjectSources(r, true);
                    if (moduleName == null) {
                        continue; // we only analyze resources that are in the pythonpath
                    }
                } catch (Exception e1) {
                    if (!loggedMisconfiguration) {
                        loggedMisconfiguration = true; //No point in logging it over and over again.
                        Log.log(e1);
                    }
                    continue;
                }

                //create new memo for each resource
                VisitorMemo memo = new VisitorMemo();
                memo.setSharedProjectState(memoSharedProjectState);
                memo.put(PyDevBuilderVisitor.IS_FULL_BUILD, true); //mark it as full build

                ICallback0<IDocument> doc = FileUtilsFileBuffer.getDocOnCallbackFromResource(r);
                memo.put(PyDevBuilderVisitor.DOCUMENT_TIME, System.currentTimeMillis());

                PyDevBuilderVisitor.setModuleNameInCache(memo, r, moduleName);

                for (Iterator<PyDevBuilderVisitor> it = visitors.iterator(); it.hasNext()
                        && monitor.isCanceled() == false;) {

                    try {
                        PyDevBuilderVisitor visitor = it.next();
                        visitor.memo = memo; //setting the memo must be the first thing.

                        long currentTimeMillis = System.currentTimeMillis();
                        if (currentTimeMillis - lastProgressTime > 300) {
                            communicateProgress(monitor, totalResources, i, r, visitor, bufferToCreateString);
                            lastProgressTime = currentTimeMillis;
                        }

                        //on a full build, all visits are as some add...
                        visitor.visitAddedResource(r, doc, monitor);
                    } catch (Exception e) {
                        Log.log(e);
                    }
                }

                if (total > 1) {
                    monitor.worked((int) total);
                    total -= (int) total;
                }
                memoSharedProjectState = memo.getSharedProjectState();
            } finally {
                nature.endRequests();
            }
        }
    }

    /**
     * Used so that we can communicate the progress to the user
     *
     * @param bufferToCreateString: this is a buffer that's emptied and used to create the string to be shown to the
     * user with the progress.
     */
    public static void communicateProgress(IProgressMonitor monitor, int totalResources, int i, IResource r,
            PyDevBuilderVisitor visitor, FastStringBuffer bufferToCreateString) {
        if (monitor != null) {
            bufferToCreateString.clear();
            bufferToCreateString.append("PyDev: Analyzing ");
            bufferToCreateString.append(i);
            bufferToCreateString.append(" of ");
            bufferToCreateString.append(totalResources);
            bufferToCreateString.append(" (");
            bufferToCreateString.append(r.getName());
            bufferToCreateString.append(")");

            //in this case the visitor does not have the progress and therefore does not communicate the progress
            String name = bufferToCreateString.toString();
            monitor.subTask(name);
        }
    }

}
