/*
 *
 *  * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
 *  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *  *
 *  * The Universal Permissive License (UPL), Version 1.0
 *  *
 *  * Subject to the condition set forth below, permission is hereby granted to any
 *  * person obtaining a copy of this software, associated documentation and/or
 *  * data (collectively the "Software"), free of charge and under any and all
 *  * copyright rights in the Software, and any and all patent rights owned or
 *  * freely licensable by each licensor hereunder covering either (i) the
 *  * unmodified Software as contributed to or provided by such licensor, or (ii)
 *  * the Larger Works (as defined below), to deal in both
 *  *
 *  * (a) the Software, and
 *  *
 *  * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 *  * one is included with the Software each a "Larger Work" to which the Software
 *  * is contributed by such licensors),
 *  *
 *  * without restriction, including without limitation the rights to copy, create
 *  * derivative works of, display, perform, and distribute the Software and make,
 *  * use, sell, offer for sale, import, export, have made, and have sold the
 *  * Software and the Larger Work(s), and to sublicense the foregoing rights on
 *  * either these or other terms.
 *  *
 *  * This license is subject to the following condition:
 *  *
 *  * The above copyright notice and either this complete permission notice or at a
 *  * minimum a reference to the UPL must be included in all copies or substantial
 *  * portions of the Software.
 *  *
 *  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  * SOFTWARE.
 *
 */

package ninja.soroosh.hashem.lang.test;

import static ninja.soroosh.hashem.lang.test.HashemJavaInteropTest.toUnixString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import ninja.soroosh.hashem.lang.HashemLanguage;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Instrument;
import org.graalvm.polyglot.PolyglotAccess;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;

@Ignore
public class HashemSharedCodeSeparatedEnvTest {

    private ByteArrayOutputStream osRuntime;
    private ByteArrayOutputStream os1;
    private ByteArrayOutputStream os2;
    private Engine engine;
    private Context e1;
    private Context e2;

    @Before
    public void initializeEngines() {
        osRuntime = new ByteArrayOutputStream();
        engine = Engine.newBuilder().out(osRuntime).err(osRuntime).build();

        os1 = new ByteArrayOutputStream();
        os2 = new ByteArrayOutputStream();

        int instances = HashemLanguage.counter.get();
        // @formatter:off
        e1 = Context.newBuilder("hashemi").engine(engine).out(os1).allowPolyglotAccess(PolyglotAccess.ALL).build();
        e1.getPolyglotBindings().putMember("extra", 1);
        e2 = Context.newBuilder("hashemi").engine(engine).out(os2).allowPolyglotAccess(PolyglotAccess.ALL).build();
        e2.getPolyglotBindings().putMember("extra", 2);
        e1.initialize("hashemi");
        e2.initialize("hashemi");
        assertEquals("One HashemLanguage instance created", instances + 1, HashemLanguage.counter.get());
    }

    @After
    public void closeEngines() {
        engine.close();
    }

    @Test
    public void shareCodeUseDifferentOutputStreams() throws Exception {

        String sayHello =
            "bebin azinja() {\n" +
            "  bekhoon(\"Ahoj\" + import(\"extra\"));" +
            "}";
        // @formatter:on

        e1.eval("hashemi", sayHello);
        assertEquals("Ahoj1\n", toUnixString(os1));
        assertEquals("", toUnixString(os2));

        e2.eval("hashemi", sayHello);
        assertEquals("Ahoj1\n", toUnixString(os1));
        assertEquals("Ahoj2\n", toUnixString(os2));
    }

    @Test
    public void instrumentsSeeOutputOfBoth() throws Exception {
        Instrument outInstr = e2.getEngine().getInstruments().get("captureOutput");
        ByteArrayOutputStream outConsumer = outInstr.lookup(ByteArrayOutputStream.class);
        assertNotNull("Stream capturing is ready", outConsumer);

        String sayHello = "bebin azinja() {\n" +
                        "  bekhoon(\"Ahoj\" + import(\"extra\"));" +
                        "}";
        // @formatter:on

        e1.eval("hashemi", sayHello);
        assertEquals("Ahoj1\n", toUnixString(os1));
        assertEquals("", toUnixString(os2));

        e2.eval("hashemi", sayHello);
        assertEquals("Ahoj1\n", toUnixString(os1));
        assertEquals("Ahoj2\n", toUnixString(os2));

        engine.close();

        assertEquals("Output of both contexts and instruments is capturable",
                        "initializingOutputCapture\n" +
                                        "Ahoj1\n" +
                                        "Ahoj2\n" +
                                        "endOfOutputCapture\n",
                        toUnixString(outConsumer));

        assertEquals("Output of instrument goes not to os runtime if specified otherwise",
                        "initializingOutputCapture\n" + "endOfOutputCapture\n",
                        toUnixString(osRuntime));
    }

    @TruffleInstrument.Registration(id = "captureOutput", services = ByteArrayOutputStream.class)
    public static class CaptureOutput extends TruffleInstrument {
        private EventBinding<ByteArrayOutputStream> binding;

        @Override
        protected void onCreate(final TruffleInstrument.Env env) {
            final ByteArrayOutputStream capture = new ByteArrayOutputStream() {
                @Override
                public void write(byte[] b) throws IOException {
                    super.write(b);
                }

                @Override
                public synchronized void write(byte[] b, int off, int len) {
                    super.write(b, off, len);
                }

                @Override
                public synchronized void write(int b) {
                    super.write(b);
                }
            };
            binding = env.getInstrumenter().attachOutConsumer(capture);
            env.registerService(capture);
            try {
                env.out().write("initializingOutputCapture\n".getBytes("UTF-8"));
            } catch (IOException ex) {
                throw new IllegalStateException(ex);
            }
        }

        @Override
        protected void onDispose(Env env) {
            try {
                env.out().write("endOfOutputCapture\n".getBytes("UTF-8"));
            } catch (IOException ex) {
                throw new IllegalStateException(ex);
            }
            binding.dispose();
        }
    }
}