/**
 * 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.
 */
package org.python.pydev.editor.refactoring;

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

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.ModulesKey;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.structure.DecoratableObject;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.structure.Tuple;

/**
 * This class encapsulates all the info needed in order to do a refactoring
 * As we'd have a getter/setter without any side-effects, let's leave them all public...
 *
 * It is a Decoratable Object, so that clients can add additional information to this
 * object at runtime.
 */
public class RefactoringRequest extends DecoratableObject {

    public static final String FIND_REFERENCES_ONLY_IN_LOCAL_SCOPE = "findReferencesOnlyOnLocalScope";

    public static final String FIND_DEFINITION_IN_ADDITIONAL_INFO = "findDefinitionInAdditionalInfo";

    /**
     * Flag used when renaming modules. If set and True, we won't do a refactoring rename and will
     * only really change the resource name.
     */
    public static final String SIMPLE_RESOURCE_RENAME = "simpleResourceRename";

    /**
     * The file associated with the editor where the refactoring is being requested
     */
    public final File file;

    /**
     * Only available on module renames.
     */
    private IFile iFileResource;

    public IFile getIFileResource() {
        return iFileResource;
    }

    /**
     * The current selection when the refactoring was requested
     */
    public PySelection ps;

    /**
     * The progress monitor to give feedback to the user (may be checked in another thread)
     * May be null
     *
     * Note that this is the monitor for the initial request, but, clients may use it in othe
     */
    private final Stack<IProgressMonitor> monitors = new Stack<IProgressMonitor>();

    /**
     * The nature used
     */
    public IPythonNature nature;

    /**
     * The python editor. May be null (especially on tests)
     */
    public final PyEdit pyEdit;

    /**
     * The module for the passed document. Has a getter that caches the result here.
     */
    private IModule module;

    /**
     * The module name (may be null)
     */
    public String moduleName;

    /**
     * The new name in a refactoring (may be null if not applicable)
     */
    public String inputName;

    /**
     * The initial representation of the selected name
     */
    public String initialName;

    /**
     * If the file is passed, we also set the document automatically
     * @param file the file correspondent to this request
     */
    public RefactoringRequest(File file, PySelection selection, IPythonNature nature) {
        this(file, selection, null, nature, null);
    }

    /**
     * If the file is passed, we also set the document automatically
     * @param file the file correspondent to this request
     * @throws MisconfigurationException
     */
    public RefactoringRequest(PyEdit pyEdit, PySelection ps) throws MisconfigurationException {
        this(pyEdit.getEditorFile(), ps, null, pyEdit.getPythonNature(), pyEdit);
    }

    /**
     * Assigns parameters to attributes (tries to resolve the module name and create a SystemPythonNature if the
     * nature is not specified)
     */
    public RefactoringRequest(File file, PySelection ps, IProgressMonitor monitor, IPythonNature nature,
            PyEdit pyEdit) {
        this.file = file;
        this.ps = ps;
        this.pushMonitor(monitor);

        if (nature == null) {
            Tuple<IPythonNature, String> infoForFile = PydevPlugin.getInfoForFile(file);
            if (infoForFile != null) {
                this.nature = infoForFile.o1;
                this.moduleName = infoForFile.o2;
            } else {
                this.nature = null;
            }
        } else {
            this.nature = nature;
            if (file != null) {
                this.moduleName = resolveModule();
            }
        }

        this.pyEdit = pyEdit;
    }

    public File getFile() {
        return file;
    }

    /**
     * Used to make the work communication (also checks to see if it has been cancelled)
     * @param desc Some string to be shown in the progress
     */
    public synchronized void communicateWork(String desc) throws OperationCanceledException {
        IProgressMonitor monitor = getMonitor();
        if (monitor != null) {
            monitor.setTaskName(desc);
            monitor.worked(1);
            checkCancelled();
        }
    }

    /**
     * Checks if the process was cancelled (throws CancelledException in this case)
     */
    public void checkCancelled() throws OperationCanceledException {
        if (getMonitor().isCanceled()) {
            throw new OperationCanceledException();
        }
    }

    /**
     * @return the module name or null if it is not possible to determine the module name
     */
    public String resolveModule() {
        if (moduleName == null) {
            if (file != null && nature != null) {
                try {
                    moduleName = nature.resolveModule(file);
                } catch (MisconfigurationException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return moduleName;
    }

    // Some shortcuts to the PySelection
    /**
     * @return the initial column selected (starting at 0)
     */
    public int getBeginCol() {
        return ps.getAbsoluteCursorOffset() - ps.getStartLine().getOffset();
    }

    /**
     * @return the final column selected (starting at 0)
     */
    public int getEndCol() {
        return ps.getAbsoluteCursorOffset() + ps.getSelLength() - ps.getEndLine().getOffset();
    }

    /**
     * @return the last line selected (starting at 1)
     */
    public int getEndLine() {
        return ps.getEndLineIndex() + 1;
    }

    /**
     * @return the initial line selected (starting at 1)
     */
    public int getBeginLine() {
        return ps.getStartLineIndex() + 1;
    }

    /**
     * @return the module for the document (may return the ast from the pyedit if it is available).
     */
    public IModule getModule() {
        if (module == null) {
            if (pyEdit != null) {
                SimpleNode ast = pyEdit.getAST();
                if (ast != null) {
                    IDocument doc = ps.getDoc();
                    long astModificationTimeStamp = pyEdit.getAstModificationTimeStamp();
                    if (astModificationTimeStamp != -1
                            && astModificationTimeStamp == (((IDocumentExtension4) doc).getModificationStamp())) {
                        //Matched time stamp -- so, we can use the ast without fear of being unsynched.
                        module = AbstractModule.createModule(ast, file, resolveModule(), nature);
                    } else {
                        //Did not match time stamp!! We'll reparse the document later on to get a synched version.
                    }
                }
            }

            if (module == null) {
                try {
                    module = AbstractModule.createModuleFromDoc(resolveModule(), file, ps.getDoc(), nature, false);
                } catch (MisconfigurationException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return module;
    }

    /**
     * @return the ast for the current module
     */
    public SimpleNode getAST() {
        IModule mod = getModule();
        if (mod instanceof SourceModule) {
            return ((SourceModule) mod).getAst();
        }
        return null;
    }

    /**
     * Fills the initial name and initial offset from the PySelection
     */
    public void fillInitialNameAndOffset() {
        try {
            Tuple<String, Integer> currToken = ps.getCurrToken();
            initialName = currToken.o1;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @return the current document where this refactoring request was asked
     */
    public IDocument getDoc() {
        return ps.getDoc();
    }

    /**
     * Sets the monitor to be used (because it may change depending on the current step we're in)
     * @param monitor the monitor to be used
     */
    public void pushMonitor(IProgressMonitor monitor) {
        if (monitor == null) {
            monitor = new NullProgressMonitor();
        }
        this.monitors.push(monitor);
    }

    /**
     * Removes and returns the current top-most progress monitor
     */
    public IProgressMonitor popMonitor() {
        return this.monitors.pop();
    }

    /**
     * Clients using the RefactoringRequest are expected to receive it as a parameter, then:
     *
     * getMonitor().beginTask("my task", total)
     *
     *
     * try{
     *     //Calling another function
     *     req.pushMonitor(new SubProgressMonitor(monitor, 10));
     *     callIt(req);
     * finally{
     *     req.popMonitor().done();
     * }
     *
     * try{
     *     //Calling another function
     *     req.pushMonitor(new SubProgressMonitor(monitor, 90));
     *     callIt(req);
     * finally{
     *     req.popMonitor().done();
     * }
     *
     *
     * getMonitor().done();
     *
     *
     *
     *
     *
     * @return the current progress monitor
     */
    public IProgressMonitor getMonitor() {
        return this.monitors.peek();
    }

    public IFile getIFile() {
        if (this.pyEdit == null) {
            return null;
        }
        return this.pyEdit.getIFile();
    }

    public boolean isModuleRenameRefactoringRequest() {
        return false;
    }

    public IPythonNature getTargetNature() {
        return this.nature;
    }

    private Map<String, List<Tuple<List<ModulesKey>, IPythonNature>>> tokenToLastReferences = new HashMap<>();

    public List<Tuple<List<ModulesKey>, IPythonNature>> getPossibleReferences(String initialName) {
        return tokenToLastReferences.get(initialName);
    }

    public void setPossibleReferences(String initialName, List<Tuple<List<ModulesKey>, IPythonNature>> ret) {
        tokenToLastReferences.put(initialName, ret);
    }

    public void setUpdateReferences(boolean updateReferences) {
        setAdditionalInfo(FIND_REFERENCES_ONLY_IN_LOCAL_SCOPE, !updateReferences);
    }

    public void setSimpleResourceRename(boolean simpleResourceRename) {
        setAdditionalInfo(SIMPLE_RESOURCE_RENAME, simpleResourceRename);
    }

    public boolean getSimpleResourceRename() {
        return (boolean) getAdditionalInfo(SIMPLE_RESOURCE_RENAME, false);
    }

    public void setFileResource(IFile file2) {
        this.iFileResource = file2;

    }

}