/*
 * 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 org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.WrapFactory;
import org.opengroupware.jope.appserver.core.WOComponent;
import org.opengroupware.jope.appserver.core.WOComponentDefinition;
import org.opengroupware.jope.appserver.core.WOContext;
import org.opengroupware.jope.appserver.core.WOResourceManager;

/**
 * JSComponentDefinition
 * <p>
 * This manages a wrapper based, scriptable component. The component logic is
 * stored in some script file inside the wrapper. The script will get evaluated
 * on component instantiation.
 */
public class JSComponentDefinition extends WOComponentDefinition {
  // TBD: threading?
  
  protected Script                script;
  protected JSKeyValueCodingScope sharedScope;
  
  public JSComponentDefinition
    (String _name, Class _compClass, Script _script,
     JSKeyValueCodingScope _sharedScope)
  {
    super(_name, _compClass);
    this.script      = _script;
    this.sharedScope = _sharedScope;
  }
  
  
  /* instantiate */

  @Override
  public WOComponent instantiateComponent(WOResourceManager _rm, WOContext _ctx)
  {
    WOComponent comp = super.instantiateComponent
      (null /* rm, HACK to make it use the context-rm */, _ctx);
    
    //if (this.script != null || this.sharedScope != null)
    // the scope is still required for associations
    applyScriptOnComponent(this.script, this.sharedScope, comp, _ctx);
    
    return comp;
  }
  
  public static void applyScriptOnComponent
    (Script _script, JSKeyValueCodingScope _sharedScope,
     WOComponent _component, WOContext _ctx)
  {
    if (log != null && log.isDebugEnabled())
      log.debug("loading JavaScript into component: " + _script);
    
    Context jscx = ((JSContext)_ctx).jsContext();

    // TBD: add compile cache
    // TBD: make independend of JSContext
    // TBD: we might want to scan the script source for additional information
    //      like Jo protections and such (or should the script execute
    //      appropriate declare() calls? Probably.)
    
    /* setup scope */
    
    if (_sharedScope == null) {
      // THREAD?
      if (log.isInfoEnabled())
        log.info("no shared scope, assigning one: " + _component);
      
      // TBD: directly use no-kvc scope?
      _sharedScope = JSKeyValueCodingScope.wrap
        (new ImporterTopLevel(jscx, true /* sealed */));
    }
    
    WrapFactory wrapFactory = jscx.getWrapFactory();
    Scriptable scope = (Scriptable)wrapFactory.wrap(
        jscx,
        _sharedScope.scope
        /* parent scope, hm, required and then reset below */,
        _component       /* java object */,
        null             /* static type */);
    scope.setPrototype(_sharedScope.scope);
    scope.setParentScope(null); /* we are a global variable root */
    
    /* assign scope */
    
    if (_component instanceof JSComponent) {
      JSComponent jc = (JSComponent)_component;
      jc.setJsScope(scope);
      jc.setJsSharedScope(_sharedScope);
    }
    else {
      // TBD: careful with Wrapping?
      _component.takeValueForKey(scope,        "jsScope");
      _component.takeValueForKey(_sharedScope, "jsSharedScope");
    }
    
    /* run instance script */
    
    if (_script != null) {
      _script.exec(jscx, scope);
      if (log != null && log.isDebugEnabled())
        log.debug("  did load JS into component: " + _component);
    }
    
    /* call init when available */
    
    Object func = ScriptableObject.getProperty(scope, "init");
    if (func instanceof Callable) {
      /* call function */

      Scriptable wrappedCtx =
        (Scriptable)wrapFactory.wrap(jscx, scope, _ctx, null);

      ((Callable)func).call(jscx,
          scope /* scope */,
          scope /* this  */,
          new Object[] { wrappedCtx });
    }
    else if (func != null && func != Scriptable.NOT_FOUND)
      log.warn("found an init slot, but its not a function: " + func);
  }
  
  
  /* description */

  @Override
  public void appendAttributesToDescription(final StringBuilder _d) {
    super.appendAttributesToDescription(_d);
    
    if (this.script != null) {
      _d.append(" script=");
      _d.append(this.script);
    }
    else if (this.sharedScope == null) /* if both are null */
      _d.append(" no-script");
    
    if (this.sharedScope != null) {
      _d.append(" scope=");
      _d.append(this.sharedScope);
    }
    else
      _d.append(" no-scope");
  }
}
