package net.sf.saxon.dotnet;

import cli.System.Reflection.*;
import net.sf.saxon.Configuration;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.ExpressionTool;
import net.sf.saxon.expr.ExpressionVisitor;
import net.sf.saxon.expr.StaticContext;
import net.sf.saxon.functions.CompileTimeFunction;
import net.sf.saxon.functions.ExtensionFunctionCall;
import net.sf.saxon.functions.FunctionLibrary;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.Type;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.Cardinality;

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

/**
 * The DotNetExtensionLibrary is a FunctionLibrary that binds XPath function calls to
 * calls on .NET methods (or constructors, or properties). It performs a mapping from
 * the namespace URI of the function to the .NET assembly and type (the mapping is partly table
 * driven and partly algorithmic), and maps the local name of the function to the
 * .NET method, constructor, or property within the class. If the .NET methods are
 * polymorphic, then it tries to select the appropriate method based on the static types
 * of the supplied arguments. Binding is done entirely at XPath compilation time.
 */

// derived from JavaExtensionLibrary

public class DotNetExtensionLibrary implements FunctionLibrary {

    private Configuration config;

    // HashMap containing URI->Class mappings. This includes conventional
    // URIs such as the Saxon and EXSLT namespaces, and mapping defined by
    // the user using saxon:script

    private HashMap explicitMappings = new HashMap(10);

    // Output destination for debug messages. At present this cannot be configured.

    private transient PrintStream diag = System.err;

    /**
     * Construct a JavaExtensionLibrary and establish the default uri->class mappings.
     * @param config The Saxon configuration
     */

    public DotNetExtensionLibrary(Configuration config) {
        this.config = config;
        setDefaultURIMappings();
    }

    /**
     * Define initial mappings of "well known" namespace URIs to .NET classes (this covers
     * the Saxon and EXSLT extensions). The method is protected so it can be overridden in
     * a subclass.
     */
    protected void setDefaultURIMappings() {
        //declareJavaClass(NamespaceConstant.SAXON, net.sf.saxon.functions.Extensions.class);
    }

    /**
     * Declare a mapping from a specific namespace URI to a .NET class
     * @param uri the namespace URI of the function name
     * @param theClass the .NET class that implements the functions in this namespace
     */

    public void declareDotNetType(String uri, cli.System.Type theClass) {
        explicitMappings.put(uri, theClass);
    }

    /**
     * Test whether an extension function with a given name and arity is available. This supports
     * the function-available() function in XSLT. This method may be called either at compile time
     * or at run time.
     * @param functionName the name of the function being sought
     * @param arity The number of arguments. This is set to -1 in the case of the single-argument
     * function-available() function; in this case the method should return true if there is some
     */

    public boolean isAvailable(StructuredQName functionName, int arity) {
        if (!config.isAllowExternalFunctions()) {
            return false;
        }
        String uri = functionName.getNamespaceURI();
        String local = functionName.getLocalName();
        cli.System.Type reqClass;
        try {
            reqClass = getExternalDotNetType(uri, null, config.isTraceExternalFunctions());
            if (reqClass == null) {
                return false;
            }
        } catch (Exception err) {
            return false;
        }
        int significantArgs;

        cli.System.Type theClass = reqClass;

        // if the method name is "new", look for a matching constructor

        if ("new".equals(local)) {

            if (theClass.get_IsAbstract()) {
                return false;
            } else if (theClass.get_IsInterface()) {
                return false;
            } else if (!theClass.get_IsPublic()) {
                return false;
            }

            if (arity == -1) return true;

            ConstructorInfo[] constructors = theClass.GetConstructors();
            for (int c = 0; c < constructors.length; c++) {
                ConstructorInfo theConstructor = constructors[c];
                if (theConstructor.GetParameters().length == arity) {
                    return true;
                }
            }
            return false;
        } else {

            // convert any hyphens in the name, camelCasing the following character

            String name = ExtensionFunctionCall.toCamelCase(local, false, diag);

            // look through the methods of this class to find one that matches the local name

            MethodInfo[] methods = theClass.GetMethods();
            for (int m = 0; m < methods.length; m++) {

                MethodInfo theMethod = methods[m];
                if (theMethod.get_Name().equals(name) && theMethod.get_IsPublic()) {
                    if (arity == -1) {
                        return true;
                    }
                    ParameterInfo[] theParameterTypes = theMethod.GetParameters();
                    boolean isStatic = theMethod.get_IsStatic();

                    // if the method is not static, the first supplied argument is the instance, so
                    // discount it

                    significantArgs = (isStatic ? arity : arity - 1);

                    if (significantArgs >= 0) {

                        boolean usesContext = theParameterTypes.length > 0 &&
                                theParameterTypes[0].get_ParameterType()
                                .get_FullName().equals("net.sf.saxon.expr.XPathContext");

                        if (theParameterTypes.length == significantArgs &&
                                (significantArgs == 0 || !usesContext)) {
                            return true;
                        }

                        // we allow the method to have an extra parameter if the first parameter is XPathContext

                        if (theParameterTypes.length == significantArgs + 1 && usesContext) {
                            return true;
                        }
                    }
                }
            }

            // look through the properties of this class to find those that match the local name

            PropertyInfo[] properties = theClass.GetProperties();
            for (int m = 0; m < properties.length; m++) {

                PropertyInfo theProperty = properties[m];
                if (theProperty.get_Name().equals(name) &&
                        theProperty.get_CanRead() && theProperty.GetGetMethod().get_IsPublic()) {
                    if (arity == -1) return true;
                    boolean isStatic = theProperty.GetGetMethod().get_IsStatic();

                    // if the property is not static, the first supplied argument is the instance, so
                    // discount it

                    significantArgs = (isStatic ? arity : arity - 1);

                    if (significantArgs == 0) {
                        return true;
                    }
                }
            }

            // look through the fields of this class to find those that match the local name

            FieldInfo[] fields = theClass.GetFields();
            for (int m = 0; m < fields.length; m++) {

                FieldInfo theField = fields[m];
                if (theField.get_Name().equals(name) && theField.get_IsPublic()) {
                    if (arity == -1) return true;
                    boolean isStatic = theField.get_IsStatic();

                    // if the field is not static, the first supplied argument is the instance, so
                    // discount it

                    significantArgs = (isStatic ? arity : arity - 1);

                    if (significantArgs == 0) {
                        return true;
                    }
                }
            }

            return false;
        }

    }

    /**
     * Bind an extension function, given the URI and local parts of the function name,
     * and the list of expressions supplied as arguments. This method is called at compile
     * time.
     * @param functionName the name of the function
     * @param staticArgs  the expressions supplied statically in the function call. The intention is
     * that the static type of the arguments (obtainable via getItemType() and getCardinality()) may
     * be used as part of the binding algorithm.
     * @param env the static context of the function call
     * @return An object representing the extension function to be called, if one is found;
     * null if no extension function was found matching the required name, arity, or signature.
     */

    public Expression bind(StructuredQName functionName, Expression[] staticArgs, StaticContext env)
            throws XPathException {

        boolean debug = config.isTraceExternalFunctions();
        final String uri = functionName.getNamespaceURI();
        final String local = functionName.getLocalName();
        if (!config.isAllowExternalFunctions()) {
            if (debug) {
                diag.println("Calls to extension functions have been disabled");
            }
            return null;
        }

        cli.System.Type requiredType = null;
        Exception theException = null;
        ArrayList candidateMethods = new ArrayList(10);
        cli.System.Type resultType = null;

        try {
            requiredType = getExternalDotNetType(uri, env.getBaseURI(), debug);
            if (requiredType == null) {
                return null;
            }
        } catch (Exception err) {
            if (debug) {
                diag.println("Cannot load external .NET type " + requiredType + " (" + err.getMessage() + ")");
            }
            throw new XPathException("Cannot load external .NET type " + requiredType, err);
        }

        if (debug) {
            diag.println("Looking for method " + local + " in .NET type " + requiredType);
            diag.println("Number of actual arguments = " + staticArgs.length);
        }

        int numArgs = staticArgs.length;
        int significantArgs;

        cli.System.Type theType = requiredType;

        // if the method name is "new", look for a matching constructor

        if ("new".equals(local)) {

            if (debug) {
                diag.println("Looking for a constructor");
            }

            if (theType.get_IsAbstract()) {
                theException = new XPathException("Type " + theType + " is abstract");
            } else if (theType.get_IsInterface()) {
                theException = new XPathException("Type " + theType + " is an interface");
            } else if (!theType.get_IsPublic()) {
                theException = new XPathException("Type " + theType + " is not public");
            }

            if (theException != null) {
                if (debug) {
                    diag.println("Cannot construct an instance: " + theException.getMessage());
                }
                return null;
            }

            ConstructorInfo[] constructors = theType.GetConstructors();
            for (int c = 0; c < constructors.length; c++) {
                ConstructorInfo theConstructor = constructors[c];
                if (debug) {
                    diag.println("Found a constructor with " + theConstructor.GetParameters().length + " arguments");
                }
                if (theConstructor.GetParameters().length == numArgs) {
                    candidateMethods.add(theConstructor);
                }
            }
            if (candidateMethods.isEmpty()) {
                theException = new XPathException("No constructor with " + numArgs +
                        (numArgs == 1 ? " parameter" : " parameters") +
                        " found in type " + theType.get_Name());
                if (debug) {
                    diag.println(theException.getMessage());
                }
                return null;
            }
        } else {

            // convert any hyphens in the name, camelCasing the following character

            String name = ExtensionFunctionCall.toCamelCase(local, debug, diag);

            // look through the methods of this class to find one that matches the local name

            MethodInfo[] methods = theType.GetMethods();
            boolean consistentReturnType = true;
            for (int m = 0; m < methods.length; m++) {

                MethodInfo theMethod = methods[m];

                if (debug) {
                    if (theMethod.get_Name().equals(name)) {
                        diag.println("Trying method " + theMethod.get_Name() + ": name matches");
                        if (!theMethod.get_IsPublic()) {
                            diag.println(" -- but the method is not public");
                        }
                    } else {
                        diag.println("Trying method " + theMethod.get_Name() + ": name does not match");
                    }
                }

                if (theMethod.get_Name().equals(name) && theMethod.get_IsPublic()) {

                    if (consistentReturnType) {
                        if (resultType == null) {
                            resultType = theMethod.get_ReturnType();
                        } else {
                            consistentReturnType =
                                    (theMethod.get_ReturnType() == resultType);
                        }
                    }
                    ParameterInfo[] theParameterTypes = theMethod.GetParameters();
                    boolean isStatic = theMethod.get_IsStatic();

                    // if the method is not static, the first supplied argument is the instance, so
                    // discount it

                    if (debug) {
                        diag.println("Method is " + (isStatic ? "" : "not ") + "static");
                    }

                    significantArgs = (isStatic ? numArgs : numArgs - 1);

                    if (significantArgs >= 0) {

                        boolean allParamsAreInParams = true;
                        for (int i=0; i<theParameterTypes.length; i++) {
                            if (!theParameterTypes[i].get_IsIn()) {
                                allParamsAreInParams = true;
                            }
                        }

                        if (debug) {
                            diag.println("Method has " + theParameterTypes.length + " argument" +
                                    (theParameterTypes.length == 1 ? "" : "s") +
                                    "; expecting " + significantArgs);
                            if (!allParamsAreInParams) {
                                diag.println("Method cannot be used because not all parameters are In parameters");
                            }
                        }

                        if (allParamsAreInParams) {
                            boolean usesContext = theParameterTypes.length > 0 &&
                                    theParameterTypes[0].get_ParameterType()
                                    .get_FullName().equals("net.sf.saxon.expr.XPathContext");

                            if (theParameterTypes.length == significantArgs &&
                                    (significantArgs == 0 || !usesContext)) {
                                if (debug) {
                                    diag.println("Found a candidate method:");
                                    diag.println("    " + theMethod);
                                }
                                candidateMethods.add(theMethod);
                            }

                            // we allow the method to have an extra parameter if the first parameter is XPathContext

                            if (theParameterTypes.length == significantArgs + 1 && usesContext) {
                                if (debug) {
                                    diag.println("Method is a candidate because first argument is XPathContext");
                                }
                                candidateMethods.add(theMethod);
                            }
                        }
                    }
                }
            }

            // look through the properties of this class to find those that matches the local name

            PropertyInfo[] properties = theType.GetProperties();
            for (int m = 0; m < properties.length; m++) {

                PropertyInfo theProperty = properties[m];

                if (debug) {
                    if (theProperty.get_Name().equals(name)) {
                        diag.println("Trying property " + theProperty.get_Name() + ": name matches");
                        if (!theProperty.get_CanRead()) {
                            diag.println("-- but the property is write-only");
                        }
                        if (!theProperty.GetGetMethod().get_IsPublic()) {
                            diag.println(" -- but the property is not public");
                        }
                    } else {
                        diag.println("Trying property " + theProperty.get_Name() + ": name does not match");
                    }
                }

                if (theProperty.get_Name().equals(name) &&
                        theProperty.get_CanRead() && theProperty.GetGetMethod().get_IsPublic()) {
                    if (consistentReturnType) {
                        if (resultType == null) {
                            resultType = theProperty.GetGetMethod().get_ReturnType();
                        } else {
                            consistentReturnType =
                                    (theProperty.GetGetMethod().get_ReturnType() == resultType);
                        }
                    }
                    boolean isStatic = theProperty.GetGetMethod().get_IsStatic();

                    // if the property is not static, the first supplied argument is the instance, so
                    // discount it

                    if (debug) {
                        diag.println("Property is " + (isStatic ? "" : "not ") + "static");
                    }

                    significantArgs = (isStatic ? numArgs : numArgs - 1);

                    if (significantArgs == 0) {
                        if (debug) {
                            diag.println("Found a candidate property:");
                            diag.println("    " + theProperty);
                        }
                        candidateMethods.add(theProperty);
                    }
                }
            }

            // look through the properties of this class to find those that matches the local name

            FieldInfo[] fields = theType.GetFields();
            for (int m = 0; m < fields.length; m++) {

                FieldInfo theField = fields[m];

                if (debug) {
                    if (theField.get_Name().equals(name)) {
                        diag.println("Trying field " + theField.get_Name() + ": name matches");

                        if (!theField.get_IsPublic()) {
                            diag.println(" -- but the field is not public");
                        }
                    } else {
                        diag.println("Trying field " + theField.get_Name() + ": name does not match");
                    }
                }

                if (theField.get_Name().equals(name) && theField.get_IsPublic()) {
                    if (consistentReturnType) {
                        if (resultType == null) {
                            resultType = theField.get_FieldType();
                        } else {
                            consistentReturnType =
                                    (theField.get_FieldType() == resultType);
                        }
                    }
                    boolean isStatic = theField.get_IsStatic();

                    // if the property is not static, the first supplied argument is the instance, so
                    // discount it

                    if (debug) {
                        diag.println("Field is " + (isStatic ? "" : "not ") + "static");
                    }

                    significantArgs = (isStatic ? numArgs : numArgs - 1);

                    if (significantArgs == 0) {
                        if (debug) {
                            diag.println("Found a candidate field:");
                            diag.println("    " + theField);
                        }
                        candidateMethods.add(theField);
                    }
                }
            }


            // No method found?

            if (candidateMethods.isEmpty()) {
                theException = new XPathException("No method, property, or field matching " + name +
                        " with " + numArgs +
                        (numArgs == 1 ? " parameter" : " parameters") +
                        " found in class " + theType.get_Name());
                if (debug) {
                    diag.println(theException.getMessage());
                }
                return null;
            }
        }
        if (candidateMethods.isEmpty()) {
            if (debug) {
                diag.println("There is no suitable method, property, or field matching the arguments of function " + local);
            }
            return null;
        }
        MemberInfo method = getBestFit(candidateMethods, staticArgs, theType);
        if (method == null) {
            if (candidateMethods.size() > 1) {
                // There was more than one candidate method, and we can't decide which to use.
                // This may be because insufficient type information is available at this stage.
                // Return an UnresolvedExtensionFunction, and try to resolve it later when more
                // type information is known.
                return new UnresolvedExtensionFunction(functionName, theType, candidateMethods, staticArgs);
            }
            return null;
        } else {
            DotNetExtensionFunctionFactory factory =
                    (DotNetExtensionFunctionFactory)config.getExtensionFunctionFactory("clitype");
            return factory.makeExtensionFunctionCall(functionName, theType, method, staticArgs);
        }
    }


    /**
     * Get the best fit amongst all the candidate methods, constructors, or properties, based on the static types
     * of the supplied arguments
     * @param candidateMethods a list of all the methods, properties, and constructors that match the extension
     * function call in name and arity (but not necessarily in the types of the arguments)
     * @param args the expressions supplied as arguments
     * @param containingType the containing type
     * @return the result is either a MethodInfo or a ConstructorInfo or a PropertyInfo, or null if no unique best fit
     * method could be found.
     */

    private MemberInfo getBestFit(List candidateMethods, Expression[] args, cli.System.Type containingType) {
        boolean debug = config.isTraceExternalFunctions();
        int candidates = candidateMethods.size();

        if (candidates == 1) {
            // short cut: there is only one candidate method
            return (MemberInfo) candidateMethods.get(0);

        } else {
            // choose the best fit method or constructor or property
            // for each pair of candidate methods, eliminate either or both of the pair
            // if one argument is less-preferred

            if (debug) {
                diag.println("Finding best fit method with arguments:");
                try {
                    ExpressionPresenter ep = new ExpressionPresenter(config,
                            ExpressionPresenter.defaultDestination(config, diag));
                    for (int v = 0; v < args.length; v++) {
                        args[v].explain(ep);
                    }
                } catch (XPathException err) {
                    // ignore the exception
                }
            }

            boolean eliminated[] = new boolean[candidates];
            for (int i = 0; i < candidates; i++) {
                eliminated[i] = false;
            }

            if (debug) {
                for (int i = 0; i < candidates; i++) {
                    int[] pref_i = getConversionPreferences(
                            args,
                            (MemberInfo) candidateMethods.get(i), containingType);
                    diag.println("Trying option " + i + ": " + candidateMethods.get(i).toString());
                    if (pref_i == null) {
                        diag.println("Arguments cannot be converted to required types");
                    } else {
                        String prefs = "[";
                        for (int p = 0; p < pref_i.length; p++) {
                            if (p != 0) prefs += ", ";
                            prefs += pref_i[p];
                        }
                        prefs += "]";
                        diag.println("Conversion preferences are " + prefs);
                    }
                }
            }

            for (int i = 0; i < candidates; i++) {
                int[] pref_i = getConversionPreferences(
                        args,
                        (MemberInfo) candidateMethods.get(i), containingType);

                if (pref_i == null) {
                    eliminated[i] = true;
                }
                if (!eliminated[i]) {
                    for (int j = i + 1; j < candidates; j++) {
                        if (!eliminated[j]) {
                            int[] pref_j = getConversionPreferences(args,
                                    (MemberInfo)candidateMethods.get(j), containingType);
                            if (pref_j == null) {
                                eliminated[j] = true;
                            } else {
                                for (int k = 0; k < pref_j.length; k++) {
                                    //noinspection ConstantConditions
                                    if (pref_i[k] > pref_j[k] && !eliminated[i]) { // high number means less preferred
                                        eliminated[i] = true;
                                        if (debug) {
                                            diag.println("Eliminating option " + i);
                                        }
                                    }
                                    if (pref_i[k] < pref_j[k] && !eliminated[j]) {
                                        eliminated[j] = true;
                                        if (debug) {
                                            diag.println("Eliminating option " + j);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            int remaining = 0;
            MemberInfo theMethod = null;
            for (int r = 0; r < candidates; r++) {
                if (!eliminated[r]) {
                    theMethod = (MemberInfo) candidateMethods.get(r);
                    remaining++;
                }
            }

            if (debug) {
                diag.println("Number of candidates remaining: " + remaining);
            }

            if (remaining == 0) {
                if (debug) {
                    diag.println("There are " + candidates +
                            " candidate .NET members matching the function name, but none is a unique best match");
                }
                return null;
            }

            if (remaining > 1) {
                if (debug) {
                    diag.println("There are several .NET methods that match the function name equally well");
                }
                return null;
            }

            return theMethod;
        }
    }

    /**
     * Get an array of integers representing the conversion distances of each "real" argument
     * to a given method
     * @param args the actual expressions supplied in the function call
     * @param method the method or constructor.
     * @param containingType the class on which this method is defined
     * @return an array of integers, one for each argument, indicating the conversion
     * distances. A high number indicates low preference. If any of the arguments cannot
     * be converted to the corresponding type defined in the method signature, return null.
     */

    private int[] getConversionPreferences(Expression[] args, MemberInfo method, cli.System.Type containingType) {

        ParameterInfo[] params;
        int firstArg;
        TypeHierarchy th = config.getTypeHierarchy();
        if (method instanceof ConstructorInfo) {
            firstArg = 0;
            params = ((ConstructorInfo) method).GetParameters();
        } else if (method instanceof MethodInfo) {
            boolean isStatic = ((MethodInfo)method).get_IsStatic();
            firstArg = (isStatic ? 0 : 1);
            params = ((MethodInfo) method).GetParameters();
        } else if (method instanceof PropertyInfo) {
            boolean isStatic = ((PropertyInfo)method).GetGetMethod().get_IsStatic();
            firstArg = (isStatic ? 0 : 1);
            params = NO_PARAMS;
        } else if (method instanceof FieldInfo) {
            boolean isStatic = ((FieldInfo)method).get_IsStatic();
            firstArg = (isStatic ? 0 : 1);
            params = NO_PARAMS;
        } else {
            throw new AssertionError("Member " + method + " is neither constructor, method, property, nor field");
        }

        int noOfArgs = args.length;
        int preferences[] = new int[noOfArgs];
        int firstParam = 0;

        if (params.length > 0 && params[0].get_Name().equals("XPathContext")) {
            firstParam = 1;
        }
        for (int i = firstArg; i < noOfArgs; i++) {
            preferences[i] = getConversionPreference(th, args[i], params[i + firstParam - firstArg].get_ParameterType());
            if (preferences[i] == -1) {
                return null;
            }
        }

        if (firstArg == 1) {
            preferences[0] = getConversionPreference(th, args[0], containingType);
            if (preferences[0] == -1) {
                return null;
            }
        }

        return preferences;
    }

    /**
     * Get the conversion preference from a given XPath type to a given .NET class
     * @param th the type hierarchy cache
     * @param arg the supplied XPath expression (the static type of this expression
     * is used as input to the algorithm)
     * @param required the .NET class of the relevant argument of the .NET method
     * @return the conversion preference. A high number indicates a low preference;
     * -1 indicates that conversion is not possible.
     */

    private int getConversionPreference(TypeHierarchy th, Expression arg, cli.System.Type required) {
        ItemType itemType = arg.getItemType(th);
        int cardinality = arg.getCardinality();
        if (required == DotNetExtensionFunctionCall.CLI_OBJECT) {
            return 100;
        } else if (Cardinality.allowsMany(cardinality)) {
            if (required.IsAssignableFrom(DotNetExtensionFunctionCall.CLI_SEQUENCEITERATOR)) {
                return 20;
//            } else if (required.IsAssignableFrom(DotNetExtensionFunctionCall.CLI_XDMVALUE)) {
//                return 21;
            } else if (required.IsAssignableFrom(DotNetExtensionFunctionCall.CLI_VALUE)) {
                return 22;
            } else if (DotNetExtensionFunctionCall.CLI_ICOLLECTION.IsAssignableFrom(required)) {
                return 23;
            } else if (required.get_IsArray()) {
                return 24;
                // sort out at run-time whether the component type of the array is actually suitable
            } else {
                return 80;    // conversion possible only if external object model supports it
            }
        } else {
            if (Type.isNodeType(itemType)) {
                if (required.IsAssignableFrom(DotNetExtensionFunctionCall.CLI_NODEINFO)) {
                    return 20;
                } else if (required.IsAssignableFrom(DotNetExtensionFunctionCall.CLI_DOCUMENTINFO)) {
                    return 21;
//                } else if (required.IsAssignableFrom(DotNetExtensionFunctionCall.CLI_XDMNODE)) {
//                    return 20;
                } else {
                    return 80;
                }
//            } else if (itemType instanceof ExternalObjectType) {
                // TODO: support wrapping of external .NET objects
//                cli.System.Type ext = ((ExternalObjectType)itemType).getJavaClass();
//                if (required.IsAssignableFrom(ext)) {
//                    return 10;
//                } else {
//                    return -1;
//                }
            } else {
                int primitiveType = itemType.getPrimitiveType();
                return atomicConversionPreference(primitiveType, required);
            }
        }
    }

    private static final ParameterInfo[] NO_PARAMS = new ParameterInfo[0];


    /**
     * Get the conversion preference from an XPath primitive atomic type to a .NET type
     * @param primitiveType integer code identifying the XPath primitive type, for example
     * {@link StandardNames#XS_STRING} or {@link StandardNames#XS_INTEGER}
     * @param required The .NET Class named in the method signature
     * @return an integer indicating the relative preference for converting this primitive type
     * to this .NET class. A high number indicates a low preference. All values are in the range
     * 50 to 100. For example, the conversion of an XPath String to {@link net.sf.saxon.value.StringValue} is 50, while
     * XPath String to {@link String} is 51. The value -1 indicates that the conversion is not allowed.
     */

    protected int atomicConversionPreference(int primitiveType, cli.System.Type required) {
        if (required == DotNetExtensionFunctionCall.CLI_OBJECT) {
            return 100;
//        } else if (required == DotNetExtensionFunctionCall.CLI_XDMITEM) {
//            return 80;
//        } else if (required == DotNetExtensionFunctionCall.CLI_XDMATOMICVALUE) {
//            return 80;
        }
        switch (primitiveType) {
            case StandardNames.XS_STRING:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.StringValue"))) return 50;
                if (required == cli.System.Type.GetType("java.lang.String")) return 51;
                if (required == DotNetExtensionFunctionCall.CLI_STRING) return 51;
                return -1;
            case StandardNames.XS_DOUBLE:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.DoubleValue"))) return 50;
                if (required == DotNetExtensionFunctionCall.CLI_DOUBLE) return 51;
                return -1;
            case StandardNames.XS_FLOAT:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.FloatValue"))) return 50;
                if (required == DotNetExtensionFunctionCall.CLI_SINGLE) return 51;
                if (required == DotNetExtensionFunctionCall.CLI_DOUBLE) return 52;
                return -1;
            case StandardNames.XS_DECIMAL:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.DecimalValue"))) return 50;
                if (required == DotNetExtensionFunctionCall.CLI_DECIMAL) return 51;
                if (required == DotNetExtensionFunctionCall.CLI_DOUBLE) return 52;
                if (required == DotNetExtensionFunctionCall.CLI_SINGLE) return 53;
                return -1;
            case StandardNames.XS_INTEGER:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.IntegerValue"))) return 50;
                if (required == DotNetExtensionFunctionCall.CLI_DECIMAL) return 51;
                if (required == DotNetExtensionFunctionCall.CLI_INT64) return 52;
                if (required == DotNetExtensionFunctionCall.CLI_INT32) return 53;
                if (required == DotNetExtensionFunctionCall.CLI_INT16) return 54;
                if (required == DotNetExtensionFunctionCall.CLI_DOUBLE) return 55;
                if (required == DotNetExtensionFunctionCall.CLI_SINGLE) return 56;
                return -1;

            case StandardNames.XS_BOOLEAN:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.BooleanValue"))) return 50;
                if (required == DotNetExtensionFunctionCall.CLI_BOOLEAN) return 51;
                return -1;
            case StandardNames.XS_DATE:
            case StandardNames.XS_G_DAY:
            case StandardNames.XS_G_MONTH_DAY:
            case StandardNames.XS_G_MONTH:
            case StandardNames.XS_G_YEAR_MONTH:
            case StandardNames.XS_G_YEAR:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.DateValue"))) return 50;
                if (required == cli.System.Type.GetType("System.DateTime")) return 51;
                return -1;
            case StandardNames.XS_DATE_TIME:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.DateTimeValue"))) return 50;
                if (required == cli.System.Type.GetType("System.DateTime")) return 51;
                return -1;
            case StandardNames.XS_TIME:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.TimeValue"))) return 50;
                if (required == cli.System.Type.GetType("System.DateTime")) return 51;
                return -1;
            case StandardNames.XS_DURATION:
            case StandardNames.XS_YEAR_MONTH_DURATION:
            case StandardNames.XS_DAY_TIME_DURATION:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.DurationValue"))) return 50;
                return -1;
            case StandardNames.XS_ANY_URI:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.AnyURIValue"))) return 50;
                if (required == cli.System.Type.GetType("System.Uri")) return 51;
                if (required == DotNetExtensionFunctionCall.CLI_STRING) return 52;
                return -1;
            case StandardNames.XS_QNAME:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.QNameValue"))) return 50;
                if (required == cli.System.Type.GetType("System.Xml.XmlQualifiedName")) return 51;
                return -1;
            case StandardNames.XS_BASE64_BINARY:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.Base64BinaryValue"))) return 50;
                return -1;
            case StandardNames.XS_HEX_BINARY:
                if (required.IsAssignableFrom(cli.System.Type.GetType("net.sf.saxon.HexBinaryValue"))) return 50;
                return -1;
            case StandardNames.XS_UNTYPED_ATOMIC:
                return 50;
            default:
                return -1;
        }
    }

    /**
     * Get an external .NET class corresponding to a given namespace URI, if there is
     * one.
     * @param uri The namespace URI corresponding to the prefix used in the function call.
     * @param baseURI The base URI from the static context of the function call
     * @param debug true if diagnostic messages are to be produced
     * @return the .NET type if a suitable type exists, otherwise return null.
     */

    private cli.System.Type getExternalDotNetType(String uri, String baseURI, boolean debug) {

        // First see if an explicit mapping has been registered for this URI

        cli.System.Type c = (cli.System.Type) explicitMappings.get(uri);
        if (c != null) {
            return c;
        }

        // Failing that, try to identify a type directly from the URI

        try {

            // support the URN format type:full.type.Name?asm=name;ver=version;loc=culture...

            if (uri.startsWith("clitype:")) {
                return ((DotNetPlatform)Configuration.getPlatform()).dynamicLoad(uri, baseURI, debug);
            }
        } catch (XPathException err) {
            return null;
        }
        return null;
    }

    /**
     * Inner class representing an unresolved extension function call. This arises when there is insufficient
     * static type information available at the time the function call is parsed to determine which of several
     * candidate .NET methods to invoke. The function call cannot be executed; it must be resolved to an
     * actual .NET method during the analysis phase.
     */

    private class UnresolvedExtensionFunction extends CompileTimeFunction {

        private List candidateMethods;
        private StructuredQName functionName;
        private cli.System.Type theClass;


        public UnresolvedExtensionFunction(StructuredQName functionName, cli.System.Type theClass, List candidateMethods, Expression[] staticArgs) {
            setArguments(staticArgs);
            this.functionName = functionName;
            this.theClass = theClass;
            this.candidateMethods = candidateMethods;
        }

        /**
         * Type-check the expression.
         */

        public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
            for (int i=0; i<argument.length; i++) {
                Expression exp = visitor.typeCheck(argument[i], contextItemType);
                if (exp != argument[i]) {
                    adoptChildExpression(exp);
                    argument[i] = exp;
                }
            }
            MemberInfo method = getBestFit(candidateMethods, argument, theClass);
            if (method == null) {
                XPathException err = new XPathException("There is more than one method matching the function call " +
                        functionName.getDisplayName() +
                        ", and there is insufficient type information to determine which one should be used");
                err.setLocator(this);
                throw err;
            } else {
                DotNetExtensionFunctionFactory factory =
                        (DotNetExtensionFunctionFactory)config.getExtensionFunctionFactory("clitype");
                Expression call = factory.makeExtensionFunctionCall(functionName, theClass, method, argument);
                ExpressionTool.copyLocationInfo(this, call);
                return call;
            }
        }
    }

    /**
     * This method creates a copy of a FunctionLibrary: if the original FunctionLibrary allows
     * new functions to be added, then additions to this copy will not affect the original, or
     * vice versa.
     *
     * @return a copy of this function library. This must be an instance of the original class.
     */

    public FunctionLibrary copy() {
        DotNetExtensionLibrary jel = new DotNetExtensionLibrary(config);
        jel.explicitMappings = new HashMap(explicitMappings);
        jel.diag = diag;
        return jel;
    }

}

//
// The contents of this file are subject to the Mozilla Public License Version 1.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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is Michael H. Kay. Contributions were made by Gunther Schadow.
//
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
//
// Contributor(s): none.
//