package com.ambrosoft.exercises;


import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created on 5/21/18
 */
public class BlockMining {
    private final static int N_THREADS = 8;
    private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();

    static class Result {
        private final long nonce;

        Result(final long nonce) {
            this.nonce = nonce;
        }

        @Override
        public String toString() {
            return Long.toString(nonce);
        }
    }

    static class Solver implements Callable<Result> {
        private final int serial;
        private final byte[] data;
        private final AtomicLong counter;

        Solver(final int serial, final byte[] data, final AtomicLong counter) {
            this.serial = serial;
            this.data = Arrays.copyOf(data, data.length + 8);
            this.counter = counter;
        }

        @Override
        public Result call() throws Exception {
            final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
            final ThreadLocalRandom random = ThreadLocalRandom.current();
            int counter = 0;
            final byte[] data = this.data;
            final int datalen = data.length;
            final Thread currentThread = Thread.currentThread();
            while (!currentThread.isInterrupted()) {
                final long longNonce = random.nextLong();

                data[datalen - 1] = (byte) (longNonce & 0xFF);
                data[datalen - 2] = (byte) ((longNonce >>> 8) & 0xFF);
                data[datalen - 3] = (byte) ((longNonce >>> 16) & 0xFF);
                data[datalen - 4] = (byte) ((longNonce >>> 24) & 0xFF);
                data[datalen - 5] = (byte) ((longNonce >>> 32) & 0xFF);
                data[datalen - 6] = (byte) ((longNonce >>> 40) & 0xFF);
                data[datalen - 7] = (byte) ((longNonce >>> 48) & 0xFF);
                data[datalen - 8] = (byte) ((longNonce >>> 56) & 0xFF);

                final byte[] hash = sha256.digest(data);
                ++counter;
                if (hash[0] == 0 && hash[1] == 0 && hash[2] == 0 && (hash[3] & 0xFF) <= 0x1F) {
                    System.out.println(serial + " found " + toHex(hash, 32));
                    System.out.println("serial = " + serial + "\t" + longNonce);
                    this.counter.addAndGet(counter);
                    return new Result(longNonce);
                }
            }
            System.out.println("interrupted serial = " + serial + "\tcounter = " + counter);
            this.counter.addAndGet(counter);
            return null;
        }
    }

    private static void solve(final Executor executor, final List<Callable<Result>> solvers) throws InterruptedException {
        final CompletionService<Result> ecs = new ExecutorCompletionService<>(executor);
        final int n = solvers.size();
        final Future[] futures = new Future[n];
        Result result = null;
        try {
            for (int i = n; --i >= 0; ) {
                futures[i] = ecs.submit(solvers.get(i));
            }
            for (int i = n; --i >= 0; ) {
                try {
                    final Result r = ecs.take().get();
                    if (r != null) {
                        result = r;
                        break;
                    }
                } catch (ExecutionException ignore) {
                }
            }
        } finally {
            System.out.println("cancelling futures");
            for (Future f : futures) {
                f.cancel(true);
            }
        }

        if (result != null) {
            use(result);
        }
    }

    private static String toHex(final byte[] bytes, final int count) {
        final char[] chars = new char[count << 1];
        for (int i = 0, j = 0; i < count; ++i, j += 2) {
            final int v = bytes[i] & 0xff;
            chars[j] = HEX_DIGITS[v >> 4];
            chars[j + 1] = HEX_DIGITS[v & 0xF];
        }
        return String.valueOf(chars);
    }

    static void use(Result result) {
        System.out.println("result = " + result);
    }

    public static void main(String[] args) throws InterruptedException {
        final ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS);
        final AtomicLong counter = new AtomicLong();

        // generate random data
        final byte[] data = new byte[120];
        ThreadLocalRandom.current().nextBytes(data);

        final ArrayList<Callable<Result>> solvers = new ArrayList<>(N_THREADS);
        for (int i = N_THREADS; --i >= 0; ) {
            solvers.add(new Solver(i, data, counter));
        }

        solve(executorService, solvers);

        executorService.shutdown();

        System.out.println("number of hashes = " + counter);
    }
}

@@ -144,33 +144,8 @@
 st;%0A
-import java.util.Random;%0A
 impo
@@ -537,46 +537,8 @@
 om;%0A
-import java.util.concurrent.TimeUnit;%0A
 impo
@@ -5154,32 +5154,34 @@
 r));%0A        %7D%0A%0A
+//
         solve(ex
@@ -5202,24 +5202,375 @@
  solvers);%0A%0A
+        try %7B%0A            // a good example of the rule to %22know your library%22%0A            // invokeAny does exactly what is needed here (and uses CompletionService internally)%0A            Result result = executorService.invokeAny(solvers);%0A            use(result);%0A        %7D catch (ExecutionException e) %7B%0A            e.printStackTrace();%0A        %7D%0A%0A
         exec
@@ -5589,25 +5589,24 @@
 shutdown();%0A
-%0A
         Syst

use invokeAny