/*
 * Copyright 2015, 2017 StreamEx contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package one.util.streamex;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.function.DoublePredicate;
import java.util.function.IntPredicate;
import java.util.function.LongPredicate;
import java.util.function.Predicate;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

/**
 * @author Tagir Valeev
 */
/* package */ class Java9Specific extends VersionSpecific {
    private static final MethodHandle[][] JDK9_METHODS = initJdk9Methods();
    private static final int IDX_STREAM = 0;
    private static final int IDX_INT_STREAM = 1;
    private static final int IDX_LONG_STREAM = 2;
    private static final int IDX_DOUBLE_STREAM = 3;
    private static final int IDX_TAKE_WHILE = 0;
    private static final int IDX_DROP_WHILE = 1;

    static MethodHandle[][] initJdk9Methods() {
        MethodHandles.Lookup lookup = MethodHandles.publicLookup();
        MethodType[] types = {MethodType.methodType(Stream.class, Predicate.class),
                MethodType.methodType(IntStream.class, IntPredicate.class),
                MethodType.methodType(LongStream.class, LongPredicate.class),
                MethodType.methodType(DoubleStream.class, DoublePredicate.class)};
        MethodHandle[][] methods = new MethodHandle[types.length][];
        try {
            int i = 0;
            for (MethodType type : types) {
                methods[i++] = new MethodHandle[]{
                        lookup.findVirtual(type.returnType(), "takeWhile", type),
                        lookup.findVirtual(type.returnType(), "dropWhile", type)
                };
            }
        } catch (NoSuchMethodException | IllegalAccessException e) {
            return null;
        }
        return methods;
    }

    @Override
    @SuppressWarnings("unchecked")
    <T, S extends AbstractStreamEx<T, S>> S callWhile(AbstractStreamEx<T, S> stream, Predicate<? super T> predicate, boolean drop) {
        try {
            return stream.supply((Stream<T>) JDK9_METHODS[IDX_STREAM][drop ? IDX_DROP_WHILE : IDX_TAKE_WHILE]
                    .invokeExact(stream.stream(), predicate));
        } catch (Error | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new InternalError(e);
        }
    }

    @Override
    final IntStreamEx callWhile(IntStreamEx stream, IntPredicate predicate, boolean drop) {
        try {
            return new IntStreamEx((IntStream) JDK9_METHODS[IDX_INT_STREAM][drop ? IDX_DROP_WHILE : IDX_TAKE_WHILE]
                    .invokeExact(stream.stream(), predicate), stream.context);
        } catch (Error | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new InternalError(e);
        }
    }

    @Override
    final LongStreamEx callWhile(LongStreamEx stream, LongPredicate predicate, boolean drop) {
        try {
            return new LongStreamEx((LongStream) JDK9_METHODS[IDX_LONG_STREAM][drop ? IDX_DROP_WHILE : IDX_TAKE_WHILE]
                    .invokeExact(stream.stream(), predicate), stream.context);
        } catch (Error | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new InternalError(e);
        }
    }

    @Override
    final DoubleStreamEx callWhile(DoubleStreamEx stream, DoublePredicate predicate, boolean drop) {
        try {
            return new DoubleStreamEx((DoubleStream) JDK9_METHODS[IDX_DOUBLE_STREAM][drop ? IDX_DROP_WHILE : IDX_TAKE_WHILE]
                    .invokeExact(stream.stream(), predicate), stream.context);
        } catch (Error | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new InternalError(e);
        }
    }

    @Override
    IntStream ofChars(CharSequence seq) {
        return seq.chars();
    }
}
