/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 *---------------------------------------------------------------------------*
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.script.rhino;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.HashMap;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NativeJavaMethod;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.WrappedException;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;

/**
 * A class that wraps an <code>EventTarget</code> instance to expose
 * it in the Rhino engine. Then calling <code>addEventListener</code>
 * with a Rhino function as parameter should redirect the call to
 * <code>addEventListener</code> with a Java function object calling
 * the Rhino function.
 * This class also allows to pass an ECMAScript (Rhino) object as
 * a parameter instead of a function provided the fact that this object
 * has a <code>handleEvent</code> method.
 * @author <a href="mailto:cjolif@ilog.fr">Christophe Jolif</a>
 * @version $Id: EventTargetWrapper.java,v 1.9 2003/07/09 02:10:11 deweese Exp $
 */
class EventTargetWrapper extends NativeJavaObject {

    /**
     * The Java function object calling the Rhino function.
     */
    static class FunctionEventListener implements EventListener {
        private Function function;
        private RhinoInterpreter interpreter;
        FunctionEventListener(Function f, RhinoInterpreter i) {
            function = f;
            interpreter = i;
        }
        public void handleEvent(Event evt) {
            try {
                interpreter.callHandler(function, evt);
            } catch (JavaScriptException e) {
                // the only simple solution is to forward it as a
                // RuntimeException to be catched by event dispatching
                // in BridgeEventSupport.java
                // another solution will to give UserAgent to interpreters
                throw new WrappedException(e);
            }
        }
    }

    class HandleEventListener implements EventListener {
        private final static String HANDLE_EVENT = "handleEvent";

        private Scriptable scriptable;
        private Object[] array = new Object[1];

        HandleEventListener(Scriptable s) {
            scriptable = s;
        }
        public void handleEvent(Event evt) {
            try {
                array[0] = evt;
                ScriptableObject.callMethod(scriptable, HANDLE_EVENT, array);
            } catch (JavaScriptException e) {
                // the only simple solution is to forward it as a
                // RuntimeException to be catched by event dispatching
                // in BridgeEventSupport.java
                // another solution will to give UserAgent to interpreters
                throw new WrappedException(e);
            }
        }
    }

    class RhinoNativeJavaAddMethod extends NativeJavaMethod {
        Map listenerMap;
        RhinoNativeJavaAddMethod(Method method, String name,
                              Map listenerMap) {
            super(method, name);
            this.listenerMap = listenerMap;
        }

        public Object call(Context ctx, Scriptable scope,
                           Scriptable thisObj, Object[] args)
            throws JavaScriptException {
            NativeJavaObject  njo = (NativeJavaObject)thisObj;
            if (args[1] instanceof Function) {
                
                EventListener evtListener = new FunctionEventListener
                    ((Function)args[1],
                     ((RhinoInterpreter.ExtendedContext)ctx).getInterpreter());
                listenerMap.put(args[1], evtListener);
                // we need to marshall args
                Class[] paramTypes = { String.class, Function.class,
                                       Boolean.TYPE };
                for (int i = 0; i < args.length; i++)
                    args[i] = Context.toType(args[i], paramTypes[i]);

                
                ((EventTarget)njo.unwrap()).addEventListener
                    ((String)args[0], evtListener,
                     ((Boolean)args[2]).booleanValue());
                return Undefined.instance;
            } 
            if (args[1] instanceof NativeObject) {
                EventListener evtListener =
                    new HandleEventListener((Scriptable)args[1]);
                listenerMap.put(args[1], evtListener);
                // we need to marshall args
                Class[] paramTypes = { String.class, Scriptable.class,
                                       Boolean.TYPE };
                for (int i = 0; i < args.length; i++)
                    args[i] = Context.toType(args[i], paramTypes[i]);

                ((EventTarget)njo.unwrap()).addEventListener
                    ((String)args[0], evtListener,
                     ((Boolean)args[2]).booleanValue());
                return Undefined.instance;
            }

            return super.call(ctx, scope, thisObj, args);
        }
    }

    static class RhinoNativeJavaRemoveMethod extends NativeJavaMethod {
        Map listenerMap; 
        RhinoNativeJavaRemoveMethod(Method method, String name,
                                    Map listenerMap) {
            super(method, name);
            this.listenerMap = listenerMap;
        }
        public Object call(Context ctx, Scriptable scope,
                           Scriptable thisObj, Object[] args)
            throws JavaScriptException {
            NativeJavaObject  njo = (NativeJavaObject)thisObj;
            if (args[1] instanceof Function) {
                EventListener el;
                el = (EventListener)listenerMap.remove(args[1]);
                if (el == null) 
                    return Undefined.instance;

                // we need to marshall args
                Class[] paramTypes = { String.class, Function.class,
                                       Boolean.TYPE };
                for (int i = 0; i < args.length; i++)
                    args[i] = Context.toType(args[i], paramTypes[i]);

                ((EventTarget)njo.unwrap()).removeEventListener
                    ((String)args[0], el, ((Boolean)args[2]).booleanValue());
                return Undefined.instance;
            }

            if (args[1] instanceof NativeObject) {
                EventListener el;
                el = (EventListener)listenerMap.remove(args[1]);
                if (el == null) 
                    return Undefined.instance;

                // we need to marshall args
                Class[] paramTypes = { String.class, Scriptable.class,
                                       Boolean.TYPE };
                for (int i = 0; i < args.length; i++)
                    args[i] = Context.toType(args[i], paramTypes[i]);

                ((EventTarget)njo.unwrap()).removeEventListener
                    ((String)args[0], el, ((Boolean)args[2]).booleanValue());
                return Undefined.instance;
            }
            return super.call(ctx, scope, thisObj, args);
        }
    }

    private NativeJavaMethod methodadd;
    private NativeJavaMethod methodremove;

    private final static String ADD_NAME    = "addEventListener";
    private final static String REMOVE_NAME = "removeEventListener";
    private final static Class[] ARGS_TYPE = { String.class,
                                               EventListener.class,
                                               Boolean.TYPE };
    private final static String NAME = "name";

    EventTargetWrapper(Scriptable scope, EventTarget object) {
        super(scope, object, (Class)null);
        try {
            HashMap listenerMap = new HashMap(2);;
            methodadd = new RhinoNativeJavaAddMethod
                (object.getClass().getMethod(ADD_NAME,ARGS_TYPE), 
                 ADD_NAME, listenerMap);
            methodremove = new RhinoNativeJavaRemoveMethod
                (object.getClass().getMethod(REMOVE_NAME,ARGS_TYPE), 
                 REMOVE_NAME, listenerMap);
        } catch (NoSuchMethodException e) {
            // should not happened
            // we are sure the method are there as we
            // have an EventTarget in parameter
        }
    }

    /**
     * Overriden Rhino method.
     */
    public Object get(String name, Scriptable start) {
        if (name.equals(ADD_NAME))
            return methodadd;
        if (name.equals(REMOVE_NAME))
            return methodremove;
        return super.get(name, start);
    }
}
