/*
 * Capsule
 * Copyright (c) 2014-2015, Parallel Universe Software Co. All rights reserved.
 * 
 * This program and the accompanying materials are licensed under the terms 
 * of the Eclipse Public License v1.0, available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package co.paralleluniverse.capsule.test;

import co.paralleluniverse.capsule.Jar;
import co.paralleluniverse.common.Exceptions;
import co.paralleluniverse.common.JarClassLoader;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.StringReader;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.util.Properties;

/**
 *
 * @author pron
 */
public final class CapsuleTestUtils {
    private static final Class<?> capsuleClass;

    static {
        try {
            capsuleClass = Class.forName("Capsule");
            accessible(capsuleClass.getDeclaredField("PROFILE")).set(null, 10); // disable profiling even when log=debug
        } catch (ReflectiveOperationException e) {
            throw new AssertionError(e);
        }
    }

    public static Object newCapsule(Jar jar, Path path) {
        try {
            jar.write(path);
            //accessible(capsuleClass.getDeclaredField("MY_JAR")).set(null, path);

            final String mainClass = jar.getAttribute("Main-Class");
            final Class<?> clazz = Class.forName(mainClass);

            return newCapsule(clazz, path);
        } catch (ReflectiveOperationException e) {
            throw new AssertionError(e);
        } catch (Exception e) {
            throw rethrow(e);
        }
    }

    public static Class<?> loadCapsule(Jar jar, Path path) {
        try {
            jar.write(path);

            final String mainClass = jar.getAttribute("Main-Class");
            final Class<?> clazz = new JarClassLoader(path, true).loadClass(mainClass);

            return clazz;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    public static Object newCapsule(Class<?> clazz, Path path) {
        try {
            Constructor<?> ctor = accessible(clazz.getDeclaredConstructor(Path.class));
            return ctor.newInstance(path);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e.getCause());
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    public static void setCacheDir(Class<?> capsuleClass, Path cache) {
        try {
            accessible(actualCapsuleClass(capsuleClass).getDeclaredField("CACHE_DIR")).set(null, cache);
        } catch (ReflectiveOperationException e) {
            throw new AssertionError(e);
        }
    }

    public static void setProperties(Class<?> capsuleClass, Properties props) {
        try {
            accessible(actualCapsuleClass(capsuleClass).getDeclaredField("PROPERTIES")).set(null, props);
        } catch (ReflectiveOperationException e) {
            throw new AssertionError(e);
        }
    }

    public static void resetOutputStreams(Class<?> capsuleClass) {
        setSTDOUT(capsuleClass, System.out);
        setSTDERR(capsuleClass, System.err);
    }

    public static <T extends PrintStream> T setSTDOUT(Class<?> capsuleClass, T ps) {
        setStream(capsuleClass, "STDOUT", ps);
        return ps;
    }

    public static <T extends PrintStream> T setSTDERR(Class<?> capsuleClass, T ps) {
        setStream(capsuleClass, "STDERR", ps);
        return ps;
    }

    public static void setStream(Class<?> capsuleClass, String stream, PrintStream ps) {
        try {
            accessible(actualCapsuleClass(capsuleClass).getDeclaredField(stream)).set(null, ps);
        } catch (ReflectiveOperationException e) {
            throw new AssertionError(e);
        }
    }

    public static void setCacheDir(Path cache) {
        setCacheDir(capsuleClass, cache);
    }

    public static void setProperties(Properties props) {
        setProperties(capsuleClass, props);
    }

    public static void resetOutputStreams() {
        resetOutputStreams(capsuleClass);
    }

    public static <T extends PrintStream> T setSTDOUT(T ps) {
        return setSTDOUT(capsuleClass, ps);
    }

    public static <T extends PrintStream> T setSTDERR(T ps) {
        return setSTDERR(capsuleClass, ps);
    }

    public static void setStream(String stream, PrintStream ps) {
        setStream(capsuleClass, stream, ps);
    }

    public static <T extends AccessibleObject> T accessible(T x) {
        if (!x.isAccessible())
            x.setAccessible(true);

        if (x instanceof Field) {
            Field field = (Field) x;
            if ((field.getModifiers() & Modifier.FINAL) != 0) {
                try {
                    Field modifiersField = Field.class.getDeclaredField("modifiers");
                    modifiersField.setAccessible(true);
                    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
                } catch (ReflectiveOperationException e) {
                    throw new AssertionError(e);
                }
            }
        }

        return x;
    }

    public static Class<?> actualCapsuleClass(Class<?> clazz) {
        for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
            if (c.getName().equals("Capsule"))
                return c;
        }
        throw new AssertionError("Class " + clazz.getName() + " is not a capsule class");
    }

    public static boolean isCI() {
        return (isEnvTrue("CI") || isEnvTrue("CONTINUOUS_INTEGRATION") || isEnvTrue("TRAVIS"));
    }

    private static boolean isEnvTrue(String envVar) {
        final String ev = System.getenv(envVar);
        if (ev == null)
            return false;
        try {
            return Boolean.parseBoolean(ev);
        } catch (Exception e) {
            return false;
        }
    }

    public static RuntimeException rethrow(Throwable t) {
        return Exceptions.rethrow(t);
    }

    public static final PrintStream DEVNULL = new PrintStream(new OutputStream() {
        @Override
        public void write(int b) {
        }

        // OPTIONAL (overriding the above method is enough)
        @Override
        public void write(byte[] b, int off, int len) {
        }

        @Override
        public void write(byte[] b) throws IOException {
        }
    });

    public static class StringPrintStream extends PrintStream {
        private final ByteArrayOutputStream baos;

        public StringPrintStream() {
            super(new ByteArrayOutputStream());
            this.baos = (ByteArrayOutputStream) out;
        }

        @Override
        public String toString() {
            close();
            return baos.toString();
        }

        public BufferedReader toReader() {
            return new BufferedReader(new StringReader(toString()));
        }
    }

    private CapsuleTestUtils() {
    }
}
