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

import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TreePath;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.modules.java.hints.spi.AbstractHint;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
import org.openide.util.NbBundle;

/**
 *
 * @author Jan Lahoda
 */
public class IllegalInstanceOf extends AbstractHint {

    public IllegalInstanceOf() {
        super(false, true, HintSeverity.WARNING);
    }

    public Set<Kind> getTreeKinds() {
        return EnumSet.of(Kind.INSTANCE_OF);
    }

    public List<ErrorDescription> run(CompilationInfo info, TreePath treePath) {
        if (treePath.getLeaf().getKind() != Kind.INSTANCE_OF) {
            return null;
        }
        
        InstanceOfTree iot = (InstanceOfTree) treePath.getLeaf();
        TypeMirror     leftTypeMirror = info.getTrees().getTypeMirror(new TreePath(treePath, iot.getExpression()));
        Element        rightType = info.getTrees().getElement(new TreePath(treePath, iot.getType()));
        
        if (leftTypeMirror == null || leftTypeMirror.getKind() != TypeKind.DECLARED) {
            return null;
        }
        
        Element leftType = ((DeclaredType) leftTypeMirror).asElement();
        
        if (leftType == null || rightType == null || !leftType.getKind().isInterface() || !rightType.getKind().isInterface()) {
            //no problem:
            return null;
        }
        
        TypeElement left = (TypeElement) leftType;
        TypeElement right = (TypeElement) rightType;
        
        if (   left.getEnclosingElement().getKind() != ElementKind.PACKAGE
            || right.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
            return null;
        }
        
        PackageElement leftPackage = (PackageElement) left.getEnclosingElement();
        PackageElement rightPackage = (PackageElement) right.getEnclosingElement();
        
        String leftPackageFQN = leftPackage.getQualifiedName().toString();
        String rightPackageFQN = rightPackage.getQualifiedName().toString();
        
        if (packagesToCheck.containsKey(leftPackageFQN) && leftPackageFQN.equals(rightPackageFQN)) {
            String verifyClass = packagesToCheck.get(leftPackageFQN);
            TypeElement loadedVerify = info.getElements().getTypeElement(verifyClass);
            
            if (loadedVerify == null || !info.getTypes().isSubtype(left.asType(), loadedVerify.asType()))
                return null;
            
            if (!info.getTypes().isSubtype(right.asType(), loadedVerify.asType()))
                return null;
            
            int start = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), iot);
            int end   = (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), iot);
            return Collections.<ErrorDescription>singletonList(
                       ErrorDescriptionFactory.createErrorDescription(getSeverity().toEditorSeverity(),
                                                                      NbBundle.getMessage(IllegalInstanceOf.class, "MSG_IllegalInstanceOf"),
                                                                      info.getFileObject(),
                                                                      start,
                                                                      end
                                                                     )
                   );
        }
        
        return null;
    }
    
    private static Map<String, String> packagesToCheck = new HashMap<String, String>();
    
    static {
        packagesToCheck.put("javax.lang.model.element", "javax.lang.model.element.Element"); //NOI18N
        packagesToCheck.put("javax.lang.model.type", "javax.lang.model.type.TypeMirror"); //NOI18N
        packagesToCheck.put("com.sun.source.tree", "com.sun.source.tree.Tree"); //NOI18N
    }

    public String getId() {
        return IllegalInstanceOf.class.getName();
    }

    public String getDisplayName() {
        return NbBundle.getMessage(IllegalInstanceOf.class, "LBL_IllegalInstanceOf");
    }

    public String getDescription() {
        return NbBundle.getMessage(IllegalInstanceOf.class, "DSC_IllegalInstanceOf");
    }

    public void cancel() {}

}
