/*
 * 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.apisupport.hints;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ModifiersTree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.spi.editor.hints.Severity;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.NotifyDescriptor.Message;
import org.openide.filesystems.FileObject;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbCollections;
import org.openide.util.lookup.ServiceProvider;
import static org.netbeans.modules.apisupport.hints.Bundle.*;

/**
 * #191236: {@code ActionRegistration} conversion.
 */
@ServiceProvider(service=Hinter.class)
public class ActionRegistrationHinter implements Hinter {

     public @Override void process(final Context ctx) throws Exception {
        final FileObject file = ctx.file();
        final Object instanceCreate = ctx.instanceAttribute(file);
        if (instanceCreate == null) {
            return;
        }
        if ("method:org.openide.awt.Actions.alwaysEnabled".equals(instanceCreate)) {
            ctx.addStandardAnnotationHint(new Callable<Void>() {
                public @Override Void call() throws Exception {
                    if (!annotationsAvailable(ctx)) {
                        return null;
                    }
                    ctx.findAndModifyDeclaration(file.getAttribute("literal:delegate"), new RegisterAction(ctx, false));
                    return null;
                }
            });
        } else if ("method:org.openide.awt.Actions.checkbox".equals(instanceCreate)) {
            // #193279: no associated annotation available
        } else if ("method:org.openide.awt.Actions.callback".equals(instanceCreate) || "method:org.openide.awt.Actions.context".equals(instanceCreate)) {
            ctx.addHint(Severity.WARNING, ctx.standardAnnotationDescription()/* XXX no fixes yet */);
        } else if ("method:org.openide.windows.TopComponent.openAction".equals(instanceCreate)) {
            // XXX pending #191407: @OpenActionRegistration (w/ @ActionID and @ActionReference)
            // (could also do @Registration but would be a separate Hinter)
            // (@Description probably needed but harder since need to remove method overrides)
        } else if (file.getPath().startsWith("Actions/")) {
            // Old-style eager action of some variety.
            ctx.addStandardAnnotationHint(new Callable<Void>() {
                public @Override Void call() throws Exception {
                    if (!annotationsAvailable(ctx)) {
                        return null;
                    }
                    ctx.findAndModifyDeclaration(instanceCreate, new RegisterAction(ctx, true));
                    return null;
                }
            });
        }
    }

    @Messages("ActionRegistrationHinter.missing_org.openide.awt=You must add a dependency on org.openide.awt (7.27+) before using this fix.")
    private boolean annotationsAvailable(Context ctx) {
        if (ctx.canAccess("org.openide.awt.ActionReferences")) {
            return true;
        } else {
            DialogDisplayer.getDefault().notify(new Message(ActionRegistrationHinter_missing_org_openide_awt(), NotifyDescriptor.WARNING_MESSAGE));
            return false;
        }
    }

    private static class RegisterAction implements Context.ModifyDeclarationTask {

        private final Context ctx;
        private final boolean eager;

        RegisterAction(Context ctx, boolean eager) {
            this.ctx = ctx;
            this.eager = eager;
        }

        public @Override void run(WorkingCopy wc, Element declaration, ModifiersTree modifiers) throws Exception {
            Map<String,Object> params = new HashMap<String,Object>();
            FileObject file = ctx.file();
            params.put("category", file.getParent().getPath().substring("Actions/".length()));
            params.put("id", file.getName().replace('-', '.'));
            ModifiersTree nue = ctx.addAnnotation(wc, modifiers, "org.openide.awt.ActionID", null, params);
            params.clear();
            String displayName = ctx.bundlevalue(file.getAttribute("literal:displayName"), declaration);
            if (displayName == null) {
                // @ActionRegistration requires this attr, even though it is unused for eager actions.
                displayName = "#TODO";
            }
            params.put("displayName", displayName);
            params.put("iconBase", file.getAttribute("iconBase"));
            Object noIconInMenu = file.getAttribute("noIconInMenu");
            if (noIconInMenu instanceof Boolean) {
                params.put("iconInMenu", !((Boolean) noIconInMenu));
            }
            params.put("asynchronous", file.getAttribute("asynchronous"));
            if (eager) {
                params.put("lazy", false);
            } else {
                // XXX specify lazy=true if implements one of the 5 specials even though using Actions.* factory (but probably rare)
            }
            nue = ctx.addAnnotation(wc, nue, "org.openide.awt.ActionRegistration", null, params);
            ctx.delete(file);
            TreeMaker make = wc.getTreeMaker();
            List<AnnotationTree> anns = new ArrayList<AnnotationTree>();
            for (FileObject shadow : NbCollections.iterable(file.getFileSystem().getRoot().getData(true))) {
                if (!shadow.hasExt("shadow")) {
                    continue;
                }
                if (file.getPath().equals(shadow.getAttribute("originalFile"))) {
                    List<ExpressionTree> arguments = new ArrayList<ExpressionTree>();
                    arguments.add(make.Assignment(make.Identifier("path"), make.Literal(shadow.getParent().getPath())));
                    String name = shadow.getName();
                    if (!name.equals(file.getName())) {
                        arguments.add(make.Assignment(make.Identifier("name"), make.Literal(name)));
                    }
                    Object pos = shadow.getAttribute("position");
                    if (pos instanceof Integer) {
                        arguments.add(make.Assignment(make.Identifier("position"), make.Literal(pos)));
                    }
                    // XXX maybe look for nearby separators?
                    TypeElement ann = wc.getElements().getTypeElement("org.openide.awt.ActionReference");
                    if (ann == null) {
                        throw new IllegalArgumentException("Could not find ActionReference in classpath");
                    }
                    anns.add(make.Annotation(make.QualIdent(ann), arguments));
                    ctx.delete(shadow);
                }
            }
            if (anns.size() == 1) {
                nue = make.addModifiersAnnotation(nue, anns.get(0));
            } else if (!anns.isEmpty()) {
                TypeElement ann = wc.getElements().getTypeElement("org.openide.awt.ActionReferences");
                if (ann == null) {
                    throw new IllegalArgumentException("Could not find ActionReferences in classpath");
                }
                nue = make.addModifiersAnnotation(nue, make.Annotation(make.QualIdent(ann), Collections.singletonList(make.Assignment(make.Identifier("value"), make.NewArray(null, Collections.<ExpressionTree>emptyList(), anns)))));
            }
            wc.rewrite(modifiers, GeneratorUtilities.get(wc).importFQNs(nue));
        }

    }

}
