package org.ovirt.engine.ui.webadmin.plugin.jsni;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;

/**
 * Simple wrapper around a native JS function object.
 */
public final class JsFunction extends JavaScriptObject {

    /**
     * Callback interface for handling results from native function invocation.
     */
    public interface ResultHandler<T> {

        void onResult(T result);

    }

    /**
     * Callback interface for handling errors during native function invocation.
     */
    public interface ErrorHandler {

        void onError(String message);

    }

    public static final String RESULT_TYPE_STRING = "string"; //$NON-NLS-1$
    public static final String RESULT_TYPE_NUMBER = "number"; //$NON-NLS-1$
    public static final String RESULT_TYPE_BOOLEAN = "boolean"; //$NON-NLS-1$

    protected JsFunction() {
    }

    /**
     * Retrieves a function object from the given {@code owner} object.
     * <p>
     * Returns an empty (no-op) function in following situations:
     * <ul>
     * <li>{@code owner} object is {@code null}
     * <li>requested function object is not present in {@code owner} object
     * <li>requested function object is present in {@code owner} object, but has wrong type (i.e. not a function)
     * </ul>
     * <p>
     * As a result, this method will never return {@code null}.
     */
    public static native JsFunction get(JavaScriptObject owner, String functionName) /*-{
        var targetFunction = (owner != null) ? owner[functionName] : null;

        if (targetFunction != null && typeof targetFunction === 'function') {
            return targetFunction;
        }

        return function() {};
    }-*/;

    /**
     * Invokes the native function, without expecting return value.
     * <p>
     * All method parameters are optional (can be {@code null}).
     * <p>
     * Returns {@code true} if the function completed successfully, or {@code false} if an exception escaped the
     * function call.
     */
    public boolean invoke(JsArray<?> args, ErrorHandler errorHandler) {
        return invoke(args, null, null, errorHandler);
    }

    /**
     * Invokes the native function.
     * <p>
     * All method parameters are optional (can be {@code null}). Following result types are supported:
     * <ul>
     * <li>{@value #RESULT_TYPE_STRING}, maps to {@code ResultHandler<String>}
     * <li>{@value #RESULT_TYPE_NUMBER}, maps to {@code ResultHandler<Double>}
     * <li>{@value #RESULT_TYPE_BOOLEAN}, maps to {@code ResultHandler<Boolean>}
     * </ul>
     * <p>
     * Returns {@code true} if the function completed successfully, or {@code false} if an exception escaped the
     * function call.
     */
    public native <T> boolean invoke(JsArray<?> args, String resultType, ResultHandler<T> resultHandler, ErrorHandler errorHandler) /*-{
        var wrappedFunction = this;

        var invokeWrappedFunction = function() {
            if (typeof wrappedFunction === 'function') {
                return wrappedFunction.apply(this, args);
            } else {
                // If the wrapped object is not a JavaScript function, use null as invocation result
                return null;
            }
        };

        var handleInvocationResult = function(result) {
            if (resultHandler != null && resultType != null && (typeof result === resultType || result === null)) {
                switch (resultType) {
                    case 'string':
                        resultHandler.@org.ovirt.engine.ui.webadmin.plugin.jsni.JsFunction.ResultHandler::onResult(Ljava/lang/Object;)(result);
                        break;
                    case 'number':
                        // All native JavaScript numeric values are implicitly double-precision
                        resultHandler.@org.ovirt.engine.ui.webadmin.plugin.jsni.JsFunction.ResultHandler::onResult(Ljava/lang/Object;)(@java.lang.Double::valueOf(D)(result));
                        break;
                    case 'boolean':
                        resultHandler.@org.ovirt.engine.ui.webadmin.plugin.jsni.JsFunction.ResultHandler::onResult(Ljava/lang/Object;)(@java.lang.Boolean::valueOf(Z)(result));
                        break;
                }
            }
        };

        var handleInvocationError = function(error) {
            if (errorHandler != null) {
                errorHandler.@org.ovirt.engine.ui.webadmin.plugin.jsni.JsFunction.ErrorHandler::onError(Ljava/lang/String;)(error.toString());
            }
        };

        try {
            handleInvocationResult(invokeWrappedFunction());
            return true;
        } catch (e) {
            handleInvocationError(e);
            return false;
        }
    }-*/;

}
