package org.kohsuke.stapler.jelly.jruby;

import org.apache.commons.io.IOUtils;
import org.apache.commons.jelly.*;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.LocalVariableBehavior;
import org.jruby.embed.ScriptingContainer;
import org.jruby.runtime.backtrace.TraceType;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.StaplerTestCase;
import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;

import java.io.IOException;
import java.io.StringWriter;

/**
 * @author Hiroshi Nakamura
 */
public class JRubyJellyScriptTest extends StaplerTestCase {
    private ScriptingContainer ruby;
    private JellyContext context;

    public JRubyJellyScriptTest() {
        ruby = new ScriptingContainer(LocalContextScope.SINGLETHREAD, LocalVariableBehavior.TRANSIENT);
        ruby.setClassLoader(getClass().getClassLoader());
        ruby.getProvider().getRubyInstanceConfig().setTraceType(TraceType.traceTypeFor("raw"));
        ruby.setOutput(System.out);
        ruby.setError(System.err);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        MetaClass mc = webApp.getMetaClass(JRubyJellyScriptTest.class);
        context = mc.classLoader.getTearOff(JellyClassLoaderTearOff.class).createContext();
    }

    public void testContext() throws Exception {
        Script script = getScript("test_context.erb");
        context.setVariable("name", "ERB");
        StringWriter out = new StringWriter();
        script.run(context, XMLOutput.createXMLOutput(out));
        assertEquals("ERB", out.toString());
    }

    public void testTaglib() throws Exception {
        Script script = getScript("test_taglib.erb");
        context.setVariable("name", "ERB");
        StringWriter out = new StringWriter();
        script.run(context, XMLOutput.createXMLOutput(out));
        assertEquals("<b>Hello from Jelly to ERB</b><i>\n" +
                "  47\n" +
                "</i>", out.toString());
    }

    public void testThreadSafety() throws Exception {
        Script script = getScript("test_taglib.erb");
        int num = 100;
        EvaluatorThread[] threads = new EvaluatorThread[num];
        for (int idx = 0; idx < num; ++idx) {
            threads[idx] = new EvaluatorThread(script, idx);
            threads[idx].start();
        }
        for (int idx = 0; idx < num; ++idx) {
            threads[idx].join();
            System.out.println(threads[idx].result);
        }
    }

    private class EvaluatorThread extends Thread {
        private final Script script;
        private final int idx;
        private String result = null;

        private EvaluatorThread(Script script, int idx) {
            this.script = script;
            this.idx = idx;
        }

        public void run() {
            try {
                MetaClass mc = webApp.getMetaClass(JRubyJellyScriptTest.class);
                JellyContext context = mc.classLoader.getTearOff(JellyClassLoaderTearOff.class).createContext();
                context.setVariable("name", "ERB" + idx);

                StringWriter out = new StringWriter();
                script.run(context, XMLOutput.createXMLOutput(out));
                result = out.toString();
            } catch (Exception e) {
                result = e.getMessage();
            }
        }
    }

    public void testNoSuchTaglib() throws Exception {
        Script script = getScript("test_nosuch_taglib.erb");
        StringWriter out = new StringWriter();
        try {
            script.run(context, XMLOutput.createXMLOutput(out));
            fail("should raise JellyTagException");
        } catch (JellyTagException jte) {
            assertTrue(true);
        }
    }

    public void testNoSuchTagscript() throws Exception {
        Script script = getScript("test_nosuch_tagscript.erb");
        StringWriter out = new StringWriter();
        try {
            script.run(context, XMLOutput.createXMLOutput(out));
            fail("should raise JellyTagException");
        } catch (JellyTagException jte) {
            assertTrue(true);
        }
    }

    private Script getScript(String fixture) throws IOException {
        ruby.put("template", getTemplate(fixture));
        return (Script) ruby.runScriptlet(
                "require 'org/kohsuke/stapler/jelly/jruby/JRubyJellyScriptImpl'\n" +
                        "JRubyJellyScriptImpl::JRubyJellyERbScript.new(template)");

    }

    private String getTemplate(String fixture) throws IOException {
        return IOUtils.toString(getClass().getResource(fixture).openStream(), "UTF-8");
    }
}

@@ -2005,16 +2005,27 @@
 adSafety
+NotRequired
 () throw
@@ -2030,32 +2030,201 @@
 ows Exception %7B%0A
+        // WebApp is created per servletContext which means 1-1 to Thread.%0A        // CustomTagLibrary is not threadsafe but it's OK by design.%0A        if (false) %7B%0A    
         Script s
@@ -2269,16 +2269,20 @@
         
+   
+ 
 int num 
@@ -2288,16 +2288,20 @@
  = 100;%0A
+    
         
@@ -2354,16 +2354,20 @@
 d%5Bnum%5D;%0A
+    
         
@@ -2408,32 +2408,36 @@
 ) %7B%0A            
+    
 threads%5Bidx%5D = n
@@ -2473,32 +2473,36 @@
 x);%0A            
+    
 threads%5Bidx%5D.sta
@@ -2507,34 +2507,42 @@
 tart();%0A        
+  
-%7D%0A
+  %7D%0A    
         for (int
@@ -2575,32 +2575,36 @@
 ) %7B%0A            
+   
+ 
 threads%5Bidx%5D.joi
@@ -2624,27 +2624,85 @@
     
-System.out.println(
+    assertEquals(%22%3Cb%3EHello from Jelly to ERB%22 + idx + %22%3C/b%3E%3Ci%3E%5Cn  47%5Cn%3C/i%3E%22, 
 thre
@@ -2715,24 +2715,38 @@
 x%5D.result);%0A
+            %7D%0A
         %7D%0A  

Test update: thread safety not required