/**
 * Original work: copyright 1999-2004 The Apache Software Foundation
 * (http://www.apache.org/)
 *
 * This project is based on the work 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.
 *
 * Modified work: copyright 2013-2019 Valery Silaev (http://vsilaev.com)
 *
 * Licensed 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.apache.commons.javaflow.providers.asm3;

import org.objectweb.asm.Type;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.SimpleVerifier;
import org.objectweb.asm.tree.analysis.Value;

class FastClassVerifier extends SimpleVerifier {
    private final ClassHierarchy classHierarchy;
    
    public FastClassVerifier(ClassHierarchy classHierarchy) {
        this.classHierarchy = classHierarchy;
    }
    
    @Override
    protected boolean isAssignableFrom(Type t, Type u) {
        if (t.equals(u)) {
            return true;
        }
        // Null is assignable to any reference type
        if ("Lnull;".equals(u.getDescriptor()) && t.getSort() >= Type.ARRAY) {
            return true;
        }
        Type et, eu;
        if (t.getSort() == Type.ARRAY) {
            if (u.getSort() != Type.ARRAY ) {
                return false;
            }
            et = t.getElementType();
            eu = u.getElementType();
            int dt = t.getDimensions();
            int du = u.getDimensions();
            boolean isObject = et.equals(((BasicValue)BasicValue.REFERENCE_VALUE).getType());
            // u must be an array of equals dimension or bigger dimension if t is Object
            if (dt == du || dt < du && isObject) {
                // Ok
            } else {
                return false;
            }
        } else {
            et = t; 
            eu = u;
        }
        /*
        Type commonType = classHierarchy.getCommonSuperType(et, eu);
        */
        // isAssignableFrom(Number, Integer) => getCommonSuperType(Integer, Number) == Number        
        // Use ClassHierarchy.isSubclass biased behavior (for performance)
        Type commonType = classHierarchy.getCommonSuperType(eu, et);
        return commonType.equals(et);

    }
    
    @Override
    public Value merge(Value v, Value w) {
        if (!v.equals(w)) {
            Type t = ((BasicValue)v).getType();
            Type u = ((BasicValue)w).getType();
            int tsort = t == null ? -1 : t.getSort();
            if (tsort == Type.OBJECT || tsort == Type.ARRAY) {
                int usort = u == null ? -1 : u.getSort();
                if (usort == Type.OBJECT || usort == Type.ARRAY) {
                    if ("Lnull;".equals(t.getDescriptor())) {
                        return w;
                    }
                    if ("Lnull;".equals(u.getDescriptor())) {
                        return v;
                    }
                    if (isAssignableFrom(t, u)) {
                        return v;
                    }
                    if (isAssignableFrom(u, t)) {
                        return w;
                    }
                    return new BasicValue(classHierarchy.getCommonSuperType(t, u));
                }
            }
            return BasicValue.UNINITIALIZED_VALUE;
        }
        return v;
    }

    @Override
    protected Class<?> getClass(Type t) { 
        throw new UnsupportedOperationException();
    }
    
    @Override
    protected boolean isInterface(Type t) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    protected Type getSuperClass(Type t) {
        throw new UnsupportedOperationException();
    }

}