import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

import static java.lang.invoke.MethodHandles.*;

/**
 * @author Anastasiya Solodkaya.
 */
public class CountedLoopImpl2 {
    private static final MethodHandle EXAMPLE_COUNTER = MethodHandles.constant(int.class, 13);
    private static final MethodHandle ZERO_COUNTER = MethodHandles.constant(int.class, 0);

    public static void main(String[] args) {
        LambdamanModified example1 = new LambdamanModified("Test");
        try {
            MethodHandles.countedLoop(ZERO_COUNTER, EXAMPLE_COUNTER, null, example1.stepHandle());
            System.out.println("JDK impl succeded");
        } catch (Exception e) {
            e.printStackTrace();
        }
        LambdamanModified example2 = new LambdamanModified("Test");
        try {
            docImplementation(ZERO_COUNTER, EXAMPLE_COUNTER, null, example2.stepHandle());
            System.out.println("Doc impl succeded");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * Provided implementation notes from {@link MethodHandles#countedLoop(MethodHandle, MethodHandle, MethodHandle, MethodHandle)}
     */
    private static MethodHandle docImplementation(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
        MethodHandle returnVar = MethodHandles.dropArguments(identity(init.type().returnType()), 0, int.class, int.class);
        // assume MH_increment and MH_lessThan are handles to x+1 and x<y of type int
        MethodHandle[]
                indexVar = {start, incHandle}, // i = start; i = i+1
                loopLimit = {end, null, lessThanHandle, returnVar}, // i<end
                bodyClause = {init, dropArguments(body, 1, int.class)};  // v = body(i, v);
        return loop(indexVar, loopLimit, bodyClause);
    }

    private static MethodHandle incHandle;
    private static MethodHandle lessThanHandle;

    static {
        try {
            incHandle = MethodHandles.lookup().findStatic(CountedLoopImpl2.class, "inc", MethodType.methodType(int.class, int.class));
            lessThanHandle = MethodHandles.lookup().findStatic(CountedLoopImpl2.class, "lessThan", MethodType.methodType(boolean.class, int.class, int.class));
        } catch (NoSuchMethodException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * x + 1 function for {@link CountedLoopImpl2#docImplementation(MethodHandle, MethodHandle, MethodHandle, MethodHandle)}
     */
    private static int inc(int x) {
        return x + 1;
    }

    /**
     * x < y function for {@link CountedLoopImpl2#docImplementation(MethodHandle, MethodHandle, MethodHandle, MethodHandle)}
     */
    private static boolean lessThan(int x, int y) {
        return x < y;
    }


    /**
     * Represents class for 'lambdaman' example given in {@link MethodHandles#countedLoop(MethodHandle, MethodHandle, MethodHandle)}
     * with modification: it could be used without 'init' function.
     */
    private static class LambdamanModified {
        private String innerState;

        public LambdamanModified(String innerState) {
            this.innerState = innerState;
        }

        String step(int counter) {
            return "na " + innerState;
        }


        /**
         * Method handle for {@link LambdamanModified#step(int)} not bound to any instance
         */
        private static MethodHandle stepHandle;

        static {
            try {
                stepHandle = MethodHandles.lookup().findVirtual(LambdamanModified.class, "step", MethodType.methodType(String.class, int.class));
            } catch (NoSuchMethodException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        /**
         * Creates method handle for {@link LambdamanModified#step(int)} bound to current instance
         */
        MethodHandle stepHandle() {
            return stepHandle.bindTo(this);
        }
    }
}


