package springy.context;

import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.runtime.GlobalVariable;
import org.jruby.runtime.IAccessor;
import org.jruby.runtime.builtin.IRubyObject;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.support.AbstractRefreshableApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.w3c.dom.Document;
import springy.util.IOHelper;
import springy.util.JRubyHelper;

import java.io.IOException;
import java.io.StringReader;
import java.util.Set;
import java.util.HashSet;

/**
 * This context uses an existing Ruby runtime (org.jruby.Ruby) to construct the context
 * which can be useful in cases where the caller has already created a runtime and wants springy
 * to use this instead of creating a new one.
 *
 */
public class RuntimeSpringyContext extends AbstractSpringyApplicationContext
        implements SpringyApplicationContext {


    private static RuntimeSpringyContext parentContext( Ruby runtime, boolean refresh,  Resource... resources )
    {
        RuntimeSpringyContext parentContext = null;
        if ( resources.length > 1 )
        {
            // give all but the last resource to the parent constructor...
            Resource[] parentResources;
            parentResources = new Resource[ resources.length - 1 ];
            System.arraycopy( resources , 0 , parentResources , 0 , parentResources.length );

            parentContext = new RuntimeSpringyContext( runtime, refresh, parentResources );
        }

        return parentContext;
    }

    private String serializedContext;
    private Document serializedContextAsDocument;

    private Resource contextResource;
    private Ruby runtime;

    /**
     * @param context as a string. used for testing.
     */
    public RuntimeSpringyContext(Ruby runtime, String... context) {
        this(runtime, stringArrayToResourceArray( context ));
    }

    /**
     * @param aContextResource where to find the ruby configuration
     */
    public RuntimeSpringyContext(Ruby runtime, Resource... aContextResource) {
        this(runtime, true, aContextResource );
    }

    /** construct a chain of RuntimeSpringyContexts
     * @param contextResources where to find the ruby configuration... ordered with root context first
     * @param refresh          refreshs the context immediately
     */
    public RuntimeSpringyContext(Ruby runtime, boolean refresh, Resource... contextResources ) {
        super( parentContext( runtime , refresh , contextResources ) );
        this.runtime = runtime;
        this.contextResource = thisContextResource( contextResources );

        this.setDisplayName( contextResource.getDescription() );

        if (refresh) {
            refresh();
        }
    }

    protected void loadBeanDefinitions(final DefaultListableBeanFactory beanFactory) throws IOException, BeansException {

        String springy = "load 'springy/context/springy_parse_prepare.rb'";
        String ctxt = IOHelper.inputStreamToString(contextResource.getInputStream());

        JRubyHelper.addGlobal(runtime, "bean_factory", beanFactory);

        try {
            runtime.executeScript( springy, "(springy-parse-prepare-fragment)");
            runtime.executeScript( ctxt, contextResource.getDescription() );
        } catch (RaiseException rex) {

            System.err.println(rex.getException().toString());

            rex.printStackTrace(System.err);
            rex.getException().printBacktrace(System.err);


            RubyArray array = (RubyArray) rex.getException().backtrace();
            String lastLine = null;
            if (array.getLength() > 0) {
                lastLine = array.get(array.getLength() - 1).toString();
            }

            String message = rex.getMessage();
            if (message == null) {
                message = rex.getException().message.toString();
            }

            throw new BeanDefinitionParsingException(
                    new Problem(lastLine + ": " + message,
                            new Location(contextResource, lastLine)));
        }

    }

    public String getContextAsXml() {
        serializeContext();
        return serializedContext;
    }

    public Document getContextAsDocument() {
        serializeContext();
        return serializedContextAsDocument;
    }

    /**
     * Serializes the context to XML.
     */
    private synchronized void serializeContext() {
        if (serializedContext == null || serializedContextAsDocument == null) {
            if (!isActive())
                refreshBeanFactory();

            RubyArray a = (RubyArray) runtime.evalScriptlet("serialize_context");
            serializedContext = a.get(0).toString();
            serializedContextAsDocument = (Document) a.get(1);
        }
    }
}

@@ -906,32 +906,87 @@
 re.io.Resource;%0A
+import org.springframework.core.io.FileSystemResource;%0A
 import org.w3c.d
@@ -3500,16 +3500,17 @@
 ption %7B%0A
+%09
 %0A       
@@ -3517,23 +3517,29 @@
  String 
-s
 pr
-ingy
+epareScript
  = %22load
@@ -3667,24 +3667,178 @@
 Stream());%0A%0A
+%09String ctxtFile = (contextResource instanceof FileSystemResource) ? ((FileSystemResource)contextResource).getPath() : contextResource.getDescription();%0A%0A
         JRub
@@ -3944,23 +3944,29 @@
 Script( 
-s
 pr
-ingy
+epareScript
 , %22(spri
@@ -4042,39 +4042,15 @@
 t, c
-ontextResource.getDescription()
+txtFile
  );%0A

use correct source file pathname for springy context def files