/*
 * Copyright (C) 2007 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.io.File;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
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.foundation.NSClassLookupContext;
import org.opengroupware.jope.foundation.UString;

/**
 * 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 File scriptFile;
  
  protected Script script;
  protected long   timestamp;
  protected long   size;

  public JSComponentDefinition(String _name, Class _compClass, File _script) {
    super(_name, _compClass);
    
    this.scriptFile = _script;
    this.timestamp  = 0;
  }
  
  /* instantiate */

  @Override
  public WOComponent instantiateComponent
    (NSClassLookupContext _rm, WOContext _ctx)
  {
    WOComponent comp = super.instantiateComponent(_rm, _ctx);
    
    if (this.scriptFile != null)
      this.applyScriptOnComponent(comp, _ctx);
    
    return comp;
  }
  
  public void applyScriptOnComponent(WOComponent _component, WOContext _ctx) {
    if (this.log != null && this.log.isDebugEnabled())
      this.log.debug("loading JavaScript into component: " + this.scriptFile);
    
    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.)
    Script lScript = this.compileScript(jscx);
    
    
    /* run script */
    
    Scriptable componentScope = (Scriptable)_component.valueForKey("jsScope");
    if (componentScope == null)
      componentScope = JSComponent.jsScopeForComponent(_component);

    lScript.exec(jscx, componentScope);
    if (this.log != null && this.log.isDebugEnabled())
      this.log.debug("  did load JS into component: " + _component);
  }
  
  public Script compileScript(Context _jscx) {
    if (this.scriptFile == null)
      return null;
    
    long newTimestamp = this.scriptFile.lastModified();
    long newSize      = this.scriptFile.length();
    
    synchronized(this) { 
      if (this.script != null &&
          this.timestamp == newTimestamp && this.size == newSize)
      {
        /* did not change */
        //System.err.println("CACHED SCRIPT");
        return this.script;
      }
    }
    //System.err.println("NEW SCRIPT");
    
    // TBD: This could read partial file (written during read)? Should we retry
    //      on error? Unlikely, but well ...
    String src = UString.loadFromFile(this.scriptFile);
    String fn  = this.scriptFile.getPath();
    
    Script lScript =
      _jscx.compileString(src, fn, 0 /* line */, null /* security domain */);
    
    if (lScript != null) {
      synchronized(this) {
        this.script    = lScript;
        this.timestamp = newTimestamp;
        this.size      = newSize;
      }
    }
    
    return lScript;
  }
  
}
