/**
 * 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.codecompletion.revisited.javaintegration;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.ui.text.java.CompletionProposalCollector;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.editor.codecompletion.revisited.modules.EmptyModuleForZip;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;

/**
 * This is the module wrapper around java files or java packages.
 * 
 * Don't know how to make the completions for java correctly... check: 
 * http://www.eclipse.org/newsportal/article.php?id=68521&group=eclipse.platform#68521
 * 
 * @author Fabio
 */
public class JavaZipModule extends AbstractJavaClassModule {

    public static final boolean DEBUG_JARS = false;

    private File file;

    /**
     * If true, this represents a .class file in a zip, otherwise, it's a module representation.
     */
    private boolean isFileInZip;

    private final IPythonNature nature;

    @Override
    public IPythonNature getNature() {
        return nature;
    }

    @Override
    public File getFile() {
        return file;
    }

    /**
     * If it's not a file in a zip, it's a folder (in which case it's a package).
     */
    @Override
    public boolean isPackage() {
        return !this.isFileInZip;
    }

    private static HashMap<String, IClasspathEntry[]> classpathEntries = new HashMap<String, IClasspathEntry[]>();

    /**
     * @return the classpath entries that should be used to make the code-completion for this class.
     */
    protected synchronized IClasspathEntry[] getClasspathEntries() {
        return getClasspathEntries(this.file.getAbsolutePath());
    }

    /**
     * @return the classpath entries used in this class
     */
    private static synchronized IClasspathEntry[] getClasspathEntries(String path) {
        IClasspathEntry[] entry = classpathEntries.get(path);
        if (entry == null) {
            entry = new IClasspathEntry[] { JavaCore.newLibraryEntry(Path.fromOSString(path), null, null, true) };
            classpathEntries.put(path, entry);
        }
        return entry;
    }

    /**
     * Creates a java class module from a .class in a jar.
     * @param nature 
     */
    public JavaZipModule(EmptyModuleForZip emptyModuleForZip, IPythonNature nature) {
        super(emptyModuleForZip.getName());
        this.file = emptyModuleForZip.f;
        this.isFileInZip = emptyModuleForZip.isFile;
        this.nature = nature;

        if (DEBUG_JARS) {
            System.out.println("Created JavaZipClassModule: " + name);
        }
        if (isFileInZip) {
            //we only have tokens for a class 
            this.tokens = createTokens(name);
        } else {
            //otherwise, it's a folder (which is treated as a module without any tokens -- as an empty __init__.py file)
            this.tokens = EMPTY_ITOKEN;
        }

    }

    /**
     * @return the java element corresponding to the passed module.
     */
    @Override
    protected IJavaElement findJavaElement(String javaClassModuleName) throws Exception {
        String contents = "import %s.;";
        contents = StringUtils.format(contents, FullRepIterable.getWithoutLastPart(javaClassModuleName));
        final String lookingForClass = FullRepIterable.getLastPart(javaClassModuleName);
        List<Tuple<IJavaElement, CompletionProposal>> javaCompletionProposals = getJavaCompletionProposals(contents,
                contents.length() - 1, lookingForClass);
        if (javaCompletionProposals.size() > 0) {
            return javaCompletionProposals.get(0).o1;
        }
        return null;
    }

    /**
     * Gets tuples with the java element and the corresponding completion proposal for that element.
     * 
     * @param completeClassDesc the name of the class from where we should get the tokens. E.g. java.lang.Class, javax.swing.JFrame
     * @param filterCompletionName if specified, only return matches from elements that have the name passed (otherwise it should be null)
     * @return a list of tuples corresponding to the element and the proposal for the gotten elements
     * @throws JavaModelException
     */
    @Override
    protected List<Tuple<IJavaElement, CompletionProposal>> getJavaCompletionProposals(String completeClassDesc,
            final String filterCompletionName) throws JavaModelException {
        String contents;
        if (filterCompletionName != null) {
            //pre-filter it a bit if we already know the completion name
            contents = "class CompletionClass {void main(){new %s().%s}}";
            contents = StringUtils.format(contents, completeClassDesc, filterCompletionName);

        } else {
            contents = "class CompletionClass {void main(){new %s().}}";
            contents = StringUtils.format(contents, completeClassDesc);
        }

        List<Tuple<IJavaElement, CompletionProposal>> javaCompletionProposals = getJavaCompletionProposals(contents,
                contents.length() - 2, filterCompletionName);
        if (javaCompletionProposals.size() == 0) {
            //Handle static access (notice that we don't create an instance.)
            if (filterCompletionName != null) {
                //pre-filter it a bit if we already know the completion name
                contents = "class CompletionClass {void main(){%s.%s}}";
                contents = StringUtils.format(contents, completeClassDesc, filterCompletionName);

            } else {
                contents = "class CompletionClass {void main(){%s.}}";
                contents = StringUtils.format(contents, completeClassDesc);
            }
            javaCompletionProposals = getJavaCompletionProposals(contents, contents.length() - 2, filterCompletionName);

        }
        return javaCompletionProposals;
    }

    /**
     * Gets tuples with the java element and the corresponding completion proposal for that element.
     * 
     * @param contents the contents that should be set for doing the code-completion
     * @param completionOffset the offset where the code completion should be requested
     * @param filterCompletionName if specified, only return matches from elements that have the name passed (otherwise it should be null)
     * @return a list of tuples corresponding to the element and the proposal for the gotten elements
     * @throws JavaModelException
     */
    @Override
    protected List<Tuple<IJavaElement, CompletionProposal>> getJavaCompletionProposals(String contents,
            int completionOffset, final String filterCompletionName) throws JavaModelException {

        final List<Tuple<IJavaElement, CompletionProposal>> ret = new ArrayList<Tuple<IJavaElement, CompletionProposal>>();

        IClasspathEntry entries[] = getClasspathEntries();
        //Using old version for compatibility with eclipse 3.2
        ICompilationUnit unit = new WorkingCopyOwner() {
        }.newWorkingCopy(name, entries, null, new NullProgressMonitor());
        unit.getBuffer().setContents(contents);
        CompletionProposalCollector collector = createCollector(filterCompletionName, ret, unit);

        unit.codeComplete(completionOffset, collector); //fill the completions while searching it
        return ret;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof JavaZipModule)) {
            return false;
        }
        JavaZipModule m = (JavaZipModule) obj;

        if (name == null || m.name == null) {
            if (name != m.name) {
                return false;
            }
            //both null at this point
        } else if (!name.equals(m.name)) {
            return false;
        }

        if (file == null || m.file == null) {
            if (file != m.file) {
                return false;
            }
            //both null at this point
        } else if (!file.equals(m.file)) {
            return false;
        }

        if (isFileInZip != m.isFileInZip) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int hash = 23;
        if (file != null) {
            hash += file.hashCode();
        }
        if (name != null) {
            hash += name.hashCode();
        }
        if (isFileInZip) {
            hash *= 2;
        }
        return hash;
    }
}
