/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.java.source.ui;

import java.awt.Toolkit;
import java.lang.ref.Reference;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.lang.model.element.ElementKind;
import javax.swing.Icon;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.java.ui.Icons;
import org.netbeans.modules.parsing.lucene.support.Queries;
import org.netbeans.spi.jumpto.support.NameMatcherFactory;
import org.netbeans.spi.jumpto.type.TypeDescriptor;
import org.netbeans.spi.jumpto.type.TypeProvider;
import org.openide.cookies.EditCookie;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;

/**
 * Fast & dirty type provider, which fakes Java types from filenames.
 * The provider is used just after project loads, and before the source roots
 * are indexed by a proper Java indexer.
 *
 * <p/>
 * The TypeProvider uses {@link OpenProjectFastIndex} as data source.
 *
 * @author sdedic
 */
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.spi.jumpto.type.TypeProvider.class)
public final class FastTypeProvider implements TypeProvider {
    private static final Logger LOG = Logger.getLogger(FastTypeProvider.class.getName());

    /**
     * Cancel flag, set by the infrastructure.
     */
    private AtomicBoolean cancel = new AtomicBoolean();

    /**
     * Fast index instance
     */
    private OpenProjectFastIndex fastIndex;

    /**
     * Cached class icon for results; we do not use other icons :)
     */
    private Icon classIcon;

    public FastTypeProvider() {
        this(OpenProjectFastIndex.getDefault());
    }

    // used from unit tests
    FastTypeProvider(OpenProjectFastIndex fastIndex) {
        this.fastIndex = fastIndex;
    }

    private Icon getClassIcon() {
        if (classIcon == null) {
            classIcon = Icons.getElementIcon (ElementKind.CLASS, null);
        }
        return classIcon;
    }

    @Override
    public void cancel() {
        cancel.set(true);
    }

    @Override
    public void cleanup() {
        // no cleanup needed
    }

    @Override
    public void computeTypeNames(Context context, Result result) {
        StringBuilder pattern = new StringBuilder();
        boolean sensitive = true;

        String quotedText = Pattern.quote(context.getText());

        switch (context.getSearchType()) {
            case CASE_INSENSITIVE_EXACT_NAME:
                sensitive = false;
            case CAMEL_CASE:
                pattern.append(Queries.createCamelCaseRegExp(context.getText(), null, null, sensitive));
                break;
            case CASE_INSENSITIVE_CAMEL_CASE:
                sensitive = false;
                pattern.append(Queries.createCamelCaseRegExp(context.getText(), null, null, sensitive));
                break;
            case EXACT_NAME:
                pattern.append("^").append(quotedText).append("$"); // NOI18N
                break;
            case CASE_INSENSITIVE_PREFIX:
                sensitive = false;
            case PREFIX:
                pattern.append("^").append(quotedText); // NOI18N
                break;
            case CASE_INSENSITIVE_REGEXP:
                sensitive = false;
            case REGEXP:
                pattern.append(
                        NameMatcherFactory.wildcardsToRegexp(
                            JavaTypeProvider.removeNonJavaChars(context.getText()),
                            false
                        )
                );
                break;
        }
        Pattern searchPattern = Pattern.compile(
                pattern.toString(),
                Pattern.MULTILINE +
                    (sensitive ? 0 : Pattern.CASE_INSENSITIVE));

        for (Map.Entry<FileObject, OpenProjectFastIndex.NameIndex> one : fastIndex.copyIndexes().entrySet()) {
            FileObject root = one.getKey();
            Project p = FileOwnerQuery.getOwner(root);

            if (context.getProject() != null && !context.getProject().equals(p)) {
                continue;
            }
            OpenProjectFastIndex.NameIndex fileIndex = one.getValue();

            Matcher m = searchPattern.matcher(fileIndex.files());
            while (m.find()) {
                if (cancel.get()) {
                    LOG.fine("Search canceled");
                    return;
                }
                if (m.start() == m.end()) {
                    continue;
                }
                CharSequence f = fileIndex.getFilename(m.start(), m.end());
                CharSequence pkg = fileIndex.findPath(m.start());
                SimpleDescriptor desc = new SimpleDescriptor(p, root, f, pkg);
                result.addResult(desc);
            }
        }
    }

    @Override
    public String getDisplayName() {
        return NbBundle.getMessage(FastTypeProvider.class, "LBL_FastJavaIndex"); // NOI18N
    }

    @Override
    public String name() {
        return "fastJavaIndex";  // NOI18N
    }

    private class SimpleDescriptor extends TypeDescriptor {
        public static final String JAVA_EXTENSION = ".java"; // NOI18N
        private FileObject      root;
        private String          simpleName;
        private String          pkgName;
        private Project         project;

        public SimpleDescriptor(Project project, FileObject root, CharSequence simpleName, CharSequence pkgName) {
            this.root = root;
            this.simpleName = simpleName.toString();
            this.pkgName = pkgName.toString();
            this.project = project;
        }

        @Override
        public String getContextName() {
            return NbBundle.getMessage(FastTypeProvider.class, "FMT_TypeContextName",
                pkgName == null ? NbBundle.getMessage(FastTypeProvider.class, "LBL_DefaultPackage") : pkgName);
        }

        @Override
        @CheckForNull
        public FileObject getFileObject() {
            String s = simpleName;

            if (pkgName != null && !"".equals(pkgName)) {
                StringBuilder sb = new StringBuilder();
                s = sb.append(pkgName).append('.').append(simpleName).toString().replaceAll("\\.", "/"); // NOI18N
            }
            return root.getFileObject(s + JAVA_EXTENSION);
        }

        @Override
        public Icon getIcon() {
            return getClassIcon();
        }

        @Override
        public int getOffset() {
            return -1;
        }

        @Override
        public String getOuterName() {
            return null;
        }

        @Override
        public Icon getProjectIcon() {
            return fastIndex.getProjectIcon(project);
        }

        @Override
        public String getProjectName() {
            return fastIndex.getProjectName(project);
        }

        @Override
        public String getSimpleName() {
            return simpleName.toString();
        }

        @Override
        public String getTypeName() {
            return simpleName;
        }

        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(simpleName).append(" (");
            if (pkgName == null || "".equals(pkgName)) {
                sb.append("Default Package");
            } else {
                sb.append(pkgName);
            }
            sb.append(")");
            return sb.toString();
        }

        @Override
        public int hashCode() {
            int hc = 17;
            hc = hc * 31 + (root == null ? 0 : root.hashCode());
            hc = hc * 31 + (pkgName == null ? 0 : pkgName.hashCode());
            hc = hc * 31 + (simpleName == null ? 0 : simpleName.hashCode());
            return hc;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (!(obj instanceof SimpleDescriptor)) {
                return false;
            }
            final SimpleDescriptor other = (SimpleDescriptor) obj;
            return root == null ? other.root == null : root.equals(other.root) &&
                pkgName == null ? other.pkgName == null : pkgName.equals(other.pkgName) &&
                simpleName == null ? other.simpleName == null : simpleName.equals(other.simpleName);
        }

        @Override
        public void open() {
            boolean success = false;
            try {
                final FileObject fo = getFileObject();
                if (fo != null) {
                    final DataObject d = DataObject.find(fo);
                    final EditCookie cake = d.getCookie(EditCookie.class);
                    if (cake != null) {
                        cake.edit();
                        success = true;
                    }
                }
            } catch (DataObjectNotFoundException ex) {
                Exceptions.printStackTrace(ex);
            }
            if (!success) {
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }

}
