/*
 * Copyright 2011 eBay Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.raptorjs.rhino;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider;
import org.raptorjs.resources.ClasspathSearchPathEntry;
import org.raptorjs.resources.ResourceManager;
import org.raptorjs.rhino.amd.InjectDefineScript;
import org.raptorjs.rhino.commonjs.module.Require;
import org.raptorjs.rhino.commonjs.module.provider.RaptorUrlModuleSourceProvider;





public class RaptorJSEnv {

    private JavaScriptEngine jsEnv = new JavaScriptEngine();
    private String coreModulesDir = null;
    
    private RhinoHelpers rhinoHelpers = null;
    private ResourceManager resourceManager = null;
    private InjectDefineScript injectDefineScript = null;
    
    private ScriptableObject raptor = null;
    
    public RaptorJSEnv(ResourceManager resourceManager) {
        this.resourceManager = resourceManager;
        
        this.beforeInit();
        this.init();
        this.afterInit();
    }

    public void init() { 
        Context cx = Context.enter();
        injectDefineScript = new InjectDefineScript(this);
        
        ScriptableObject globalScope = this.getJavaScriptEngine().getGlobalScope();
        globalScope.put("global", globalScope, globalScope);
        Require require = this.createRequire(cx, globalScope);
        require.install(globalScope);
        
        this.raptor = (ScriptableObject) this.getJavaScriptEngine().invokeMethod(globalScope, "require", "raptor");
        injectDefineScript.setRaptor(raptor);
        this.resourceManager.addSearchPathEntry(new ClasspathSearchPathEntry(RaptorJSEnv.class, "/META-INF/resources"));
        
        this.rhinoHelpers = this.createRhinoHelpers();
        jsEnv.setGlobal("__rhinoHelpers", this.rhinoHelpers);
        
        
        
        this.getJavaScriptEngine().invokeMethod(globalScope, "require", "raptor-main_rhino");
        
        
        Context.exit();
    }
    
    private Require createRequire(Context cx, Scriptable scope)
    {
    	URI dirUri = this.getDirectory();
    	Iterable<URI> uris = Collections.singleton(dirUri);
    	UrlModuleSourceProvider urlModuleSourceProvider = new RaptorUrlModuleSourceProvider(uris, null);

    	Require require = new RaptorRequire(
        		cx, 
        		scope, 
        		urlModuleSourceProvider, 
        		this.injectDefineScript, 
        		null, 
        		false /* not sandboxed */,
        		this);
    	
    	return require;
    }
    
    private URI getDirectory() {
    	URL raptorUrl = RaptorJSEnv.class.getResource("/META-INF/resources/raptor/raptor.js");
        final String jsFile = raptorUrl.toExternalForm();
        try {
			return new URI(jsFile.substring(0, jsFile.lastIndexOf('/') + 1));
		} catch (URISyntaxException e) {
			throw new RuntimeException("Unable to get directory for raptor.js as a URI. Exception: " + e, e);
		}
    }
    
    protected void beforeInit() {
    }
    
    protected void afterInit() {
    }
        
    protected RhinoHelpers createRhinoHelpers() {
        return new RhinoHelpers(this);
    }

    public JavaScriptEngine getJavaScriptEngine() {
        return jsEnv;
    }

    public RhinoHelpers getRhinoHelpers() {
        return rhinoHelpers;
    }

    public ResourceManager getResourceManager() {
        return resourceManager;
    }

    public String getCoreModulesDir() {
        return coreModulesDir;
    }

    public void setCoreModulesDir(String coreModulesDir) {
        this.coreModulesDir = coreModulesDir;
    }
    
    public Scriptable require(String name) {
    	return (Scriptable)this.getJavaScriptEngine().invokeMethod(this.raptor, "require", name);
    }
    
    public Scriptable find(String name) {
    	if (this.raptor == null) {
    		return null;
    	}
    	
    	Object object = this.getJavaScriptEngine().invokeMethod(this.raptor, "find", name);
    	if (object == null) {
    		return null;
    	}
    	else if (object instanceof Scriptable) {
    		return (Scriptable)object;
    	}
    	if (object instanceof Undefined) {
    		return null;
    	}
    	else {
    		throw new RuntimeException("Module is not a scriptable object. It is of type \"" + object.getClass().getName() + "\". Object: " + object );
    	}
    }
    
    public void load(String name) {
    	this.getJavaScriptEngine().invokeMethod(this.raptor, "find", name);
    }
    
    public void enableExtension(String name) {
    	this.getJavaScriptEngine().invokeMethod(this.require("raptor/packaging"), "enableExtension", name);
    }
}
