/*
 * Copyright (C) 2007-2008 Helge Hess
 *
 * This file is part of JOPE.
 *
 * JOPE is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later version.
 *
 * JOPE is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with JOPE; see the file COPYING. If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package org.opengroupware.jope.jsapp;

import java.util.ArrayList;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Wrapper;
import org.opengroupware.jope.foundation.UString;

public class JSUtil {
  protected static final Log log = LogFactory.getLog("JSBridge");
  public static Object[] emptyArgs = new Object[0];
  
  
  /* calling funcs */

  public static Object callJSFuncWhenAvailable
    (Scriptable _wrappedObject, Map<String, Object> _slots,
     boolean _checkProto, Context _jscx, String _name, Object[] _args)
  {
    if (_slots == null && !_checkProto)
      return Scriptable.NOT_FOUND;
    
    /* Note: getProperty() would always return something because it would
     *       find the Java implementation of the object (the wrapper itself
     *       contains the NativeJavaMethod for the requested 'overridden'
     *       function).
     *       Resulting in a recursive call.
     */
    //Object func = ScriptableObject.getProperty(_wrappedObject, _name);
    
    /* First check for the function in the direct slots. The method would be
     * overridden in instance 'scope'. */
    Object func = _slots != null ? _slots.get(_name) : null;
    if (_checkProto && (func == null || func == Scriptable.NOT_FOUND)) {
      Scriptable proto = _wrappedObject.getPrototype();
      if (proto != null) {
        /* Where to start the search for the func, at the prototype? Probably,
         * though we are technically starting at the wrapped object
         */
        func = proto.get(_name, proto /* start */);
        
        if (func == null || func == Scriptable.NOT_FOUND) {
          if (false) { // _name.equals("appendToResponse")) {
            log.error("did not find '" + _name + "'\n" +
                "  prototype: " + proto + "\n" +
                "  object:    " + _wrappedObject + "\n" +
                "  ids:       " +
                UString.componentsJoinedByString(proto.getIds(), ","));
          }
          return Scriptable.NOT_FOUND;
        }
      }
      else {
        //log.error("did not find proto for '"+_name+"': " + _wrappedObject);
        return Scriptable.NOT_FOUND; // no prototype
      }
    }
    
    // log.error("FOUND FUNC: " + func);
    
    if (!(func instanceof Callable)) { // the slot is there, but its not a func
      return Scriptable.NOT_FOUND; // TBD: log that?
    }
    
    /* wrap arguments */
    
    if (_args != null && _args.length > 0) {
      Object[] wrappedArgs = new Object[_args.length];
      for (int i = 0; i < _args.length; i++) {
        Object arg = _args[i];
        
        if (arg == null)
          wrappedArgs[i] = null;
        else if (arg instanceof Scriptable)
          wrappedArgs[i] = arg;
        else
          wrappedArgs[i] = Context.javaToJS(arg, _wrappedObject);
      }
    }
    
    /* call function */
    Object result = ((Callable)func).call(_jscx,
        _wrappedObject /* scope */,
        _wrappedObject /* this  */,
        _args);
    
    if (result instanceof Wrapper)
      return ((Wrapper)result).unwrap();
    
    return result;
  }
  
  @SuppressWarnings("serial")
  public static ArrayList<Object> unwrapNativeArray(NativeArray _array) {
    if (_array == null)
      return null;
    
    int count = (int)_array.getLength(); // returns a long ...
    ArrayList<Object> list = new ArrayList<Object>(count);
    for (int i = 0; i < count; i++)
      list.add(Context.jsToJava(_array.get(i, null), null /* desiredType */));
    return list;
  }

}
