/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Copyright (c) 2013 by Syapse, 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 25/09/2005
 */
package org.python.pydev.editor.actions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.Position;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.python.pydev.core.IMiscConstants;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.codefolding.MarkerAnnotationAndPosition;
import org.python.pydev.parser.PyParser;
import org.python.pydev.parser.PyParser.IPostParserListener;
import org.python.pydev.shared_core.model.ErrorDescription;
import org.python.pydev.shared_core.structure.Tuple;

/**
 *
 * @author Fabio, Jeremy J. Carroll
 */
class OrganizeImportsFixesUnused {

    public boolean beforePerformArrangeImports(PySelection ps, PyEdit edit, IFile f) {
        int oldSelection = ps.getRegion().getOffset();

        IDocumentExtension4 doc = (IDocumentExtension4) ps.getDoc();
        if (edit != null) {
            if (!ensureParsed(edit)) {
                return true;
            }
            //Check that the editor time is actually the same as the document time.
            long docTime = doc.getModificationStamp();

            if (docTime != edit.getAstModificationTimeStamp()) {
                return true;
            }
            ErrorDescription errorDescription = edit.getErrorDescription();
            if (errorDescription != null) {
                return true; //Don't remove unused imports if we have syntax errors.
            }
        }

        try {
            findAndDeleteUnusedImports(ps, edit, doc, f);
        } catch (Exception e) {
            Log.log(e);
        }
        ps.setSelection(oldSelection, oldSelection);
        return true;

    }

    private void findAndDeleteUnusedImports(PySelection ps, PyEdit edit, IDocumentExtension4 doc, IFile f)
            throws Exception {
        Iterator<MarkerAnnotationAndPosition> it;
        if (edit != null) {
            it = edit.getPySourceViewer().getMarkerIterator();
        } else {

            IMarker markers[] = f.findMarkers(IMiscConstants.PYDEV_ANALYSIS_PROBLEM_MARKER, true, IResource.DEPTH_ZERO);
            MarkerAnnotationAndPosition maap[] = new MarkerAnnotationAndPosition[markers.length];
            int ix = 0;
            for (IMarker m : markers) {
                int start = (Integer) m.getAttribute(IMarker.CHAR_START);
                int end = (Integer) m.getAttribute(IMarker.CHAR_END);
                maap[ix++] =
                        new MarkerAnnotationAndPosition(new MarkerAnnotation(m), new Position(start, end - start));
            }
            it = Arrays.asList(maap).iterator();
        }
        ArrayList<MarkerAnnotationAndPosition> unusedImportsMarkers = getUnusedImports(it);
        sortInReverseDocumentOrder(unusedImportsMarkers);
        deleteImports(doc, ps, unusedImportsMarkers);

    }

    private ArrayList<MarkerAnnotationAndPosition> getUnusedImports(Iterator<MarkerAnnotationAndPosition> it)
            throws CoreException {
        ArrayList<MarkerAnnotationAndPosition> unusedImportsMarkers = new ArrayList<MarkerAnnotationAndPosition>();
        while (it.hasNext()) {
            MarkerAnnotationAndPosition marker = it.next();
            String type = marker.markerAnnotation.getMarker().getType();
            if (type != null && type.equals(IMiscConstants.PYDEV_ANALYSIS_PROBLEM_MARKER)) {
                Integer attribute = marker.markerAnnotation.getMarker().getAttribute(
                        IMiscConstants.PYDEV_ANALYSIS_TYPE, -1);
                if (attribute != null) {
                    if (attribute.equals(IMiscConstants.TYPE_UNUSED_IMPORT)) {
                        unusedImportsMarkers.add(marker);
                    }
                }

            }
        }
        return unusedImportsMarkers;
    }

    private void sortInReverseDocumentOrder(ArrayList<MarkerAnnotationAndPosition> unusedImportsMarkers) {
        Collections.sort(unusedImportsMarkers, new Comparator<MarkerAnnotationAndPosition>() {

            @Override
            public int compare(MarkerAnnotationAndPosition arg0, MarkerAnnotationAndPosition arg1) {
                try {
                    return getCharStart(arg1) - getCharStart(arg0);
                } catch (CoreException e) {
                    Log.log(e);
                    return 0;
                }
            }

            private int getCharStart(MarkerAnnotationAndPosition arg0) throws CoreException {
                IMarker marker = arg0.markerAnnotation.getMarker();
                int i = (Integer) marker.getAttribute(IMarker.CHAR_START);
                return i;
            }
        });
    }

    private void deleteImports(IDocumentExtension4 doc, PySelection ps, Iterable<MarkerAnnotationAndPosition> markers)
            throws BadLocationException, CoreException {
        for (MarkerAnnotationAndPosition m : markers) {
            deleteImport(ps, m);
        }
    }

    private boolean ensureParsed(PyEdit edit) {
        //Ok, we have a little problem here: we have to ensure not that only a regular ast parse took place, but
        //that the analysis of the related document is updated (so that the markers are in-place).
        //To do that, we ask for a reparse asking to analyze the results in that same thread (without scheduling it).
        //
        //Maybe better would be having some extension that allowed to call the analysis of the document directly
        //(so we don't have to rely on markers in place?)
        final PyParser parser = edit.getParser();
        final boolean[] notified = new boolean[] { false };
        final Object sentinel = new Object();

        parser.addPostParseListener(new IPostParserListener() {

            @Override
            public void participantsNotified(Object... argsToReparse) {
                synchronized (OrganizeImportsFixesUnused.this) {
                    parser.removePostParseListener(this);
                    if (argsToReparse.length == 2 && argsToReparse[1] == sentinel) {
                        notified[0] = true;
                        OrganizeImportsFixesUnused.this.notify();
                    }
                }
            }
        });

        long initial = System.currentTimeMillis();
        while ((System.currentTimeMillis() - initial) < 5000) {

            if (parser.forceReparse(new Tuple<String, Boolean>(
                    IMiscConstants.ANALYSIS_PARSER_OBSERVER_FORCE_IN_THIS_THREAD, true), sentinel)) {
                //ok, we were able to schedule it with our parameters, let's wait for its completion...
                synchronized (this) {
                    try {
                        wait(5000);
                    } catch (InterruptedException e) {
                    }
                }
                break;
            } else {
                synchronized (this) {
                    try {
                        wait(200);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }

        //Commented out: in the worse case, we already waited 5 seconds, if because of a racing condition we couldn't decide 
        //that it worked, let's just keep on going hoping that the markers are in place...
        //if (!notified[0]) {
        //    return false;
        //}
        return true;
    }

    /**
     * 
     * This leaves a bit of a mess e.g. "import " or "from " for some later process to clean up.
     * 
     */
    private void deleteImport(PySelection ps, MarkerAnnotationAndPosition markerInfo)
            throws BadLocationException, CoreException {
        IMarker marker = markerInfo.markerAnnotation.getMarker();

        Integer start = (Integer) marker.getAttribute(IMarker.CHAR_START);
        Integer end = (Integer) marker.getAttribute(IMarker.CHAR_END);
        IDocument doc = ps.getDoc();
        while (start > 0) {
            char c;
            try {
                c = doc.getChar(start - 1);
            } catch (Exception e) {
                break;
            }
            if (c == '\r' || c == '\n') {
                break;
            }
            if (Character.isWhitespace(c) || c == ',') {
                start--;
                continue;
            }
            break;
        }
        ps.setSelection(start, end);
        ps.deleteSelection();
    }

}
