package joinery.js;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import joinery.DataFrame;
import joinery.impl.Shell;

import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import org.mozilla.javascript.WrappedException;


public class JavascriptExpressionSuite
extends Suite {

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Inherited
    public @interface JavascriptResource {
        public String name();
    }

    private static List<Runner> runnersFromJs(final Class<?> cls)
    throws InitializationError, IOException {
        final JavascriptResource js = cls.getAnnotation(JavascriptResource.class);
        final List<Runner> runners = new LinkedList<>();
        try (final LineNumberReader reader = new LineNumberReader(
                new InputStreamReader(ClassLoader.getSystemResourceAsStream(js.name())))) {
            while (true) {
                final String expr = reader.readLine();
                final int line = reader.getLineNumber();
                if (expr == null) {
                    break;
                }

                if (!expr.trim().isEmpty() && !expr.trim().startsWith("//")) {
                    runners.add(new Runner() {
                        @Override
                        public Description getDescription() {
                            final String[] parts = expr.split(" *; *");
                            final String desc = parts[parts.length - 1];
                            return Description.createTestDescription(cls,
                                String.format("%s:%s => %s", js.name(), line, desc), js);
                        }

                        @Override
                        public void run(final RunNotifier notifier) {
                            notifier.fireTestStarted(getDescription());
                            // should really do something more generic here
                            // instead of hard-coded setup, explicit
                            // data frame construction...
                            System.setIn(new ByteArrayInputStream(
                                String.format("tmp = frames[0]; df = frames[1]; %s;", expr).getBytes()));
                            try {
                                final DataFrame<Object> df = DataFrame.readCsv(ClassLoader.getSystemResourceAsStream("grouping.csv"));
                                final Object result = Shell.repl(Arrays.asList(new DataFrame<>(), df));
                                if (result instanceof WrappedException) {
                                    throw WrappedException.class.cast(result).getWrappedException();
                                } else if (result instanceof Throwable) {
                                    throw Throwable.class.cast(result);
                                }
                                org.junit.Assert.assertFalse(result == null);
                            } catch (final IOException ioe) {
                                notifier.fireTestAssumptionFailed(new Failure(getDescription(), ioe));
                            } catch (final AssertionError err) {
                                notifier.fireTestFailure(new Failure(getDescription(), err));
                            } catch (final Throwable ex) {
                                notifier.fireTestFailure(new Failure(getDescription(), ex));
                            } finally {
                                notifier.fireTestFinished(getDescription());
                            }
                        }
                    });
                }
            }
        }
        return runners;
    }

    public JavascriptExpressionSuite(final Class<?> cls, final RunnerBuilder builder)
    throws InitializationError, IOException {
        super(cls, runnersFromJs(cls));
    }
}
