package net.ladenthin.btcdetector;

import net.ladenthin.btcdetector.configuration.ProbeAddresses;
import net.ladenthin.javacommons.StreamHelper;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import net.ladenthin.btcdetector.persistence.Persistence;
import net.ladenthin.btcdetector.persistence.PersistenceUtils;
import net.ladenthin.btcdetector.persistence.lmdb.LMDBPersistence;
import org.bitcoinj.core.ECKey;
import org.bouncycastle.util.encoders.Hex;

public abstract class Prober implements Runnable {

    private static final int MILLISECONDS_TO_SECONDS = 1000;
    public static final String MISS_PREFIX = "miss: Could not find the address: ";
    public static final String HIT_PREFIX = "hit: Found the address: ";

    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    protected final NetworkParameters networkParameters = MainNetParams.get();
    protected final KeyUtility keyUtility = new KeyUtility(networkParameters, new ByteBufferUtility(false));
    protected final AtomicLong checkedKeys = new AtomicLong();
    protected final AtomicLong emptyConsumer = new AtomicLong();
    protected final AtomicLong hits = new AtomicLong();
    protected long startTime;

    protected final AtomicBoolean shouldRun = new AtomicBoolean(true);

    protected final ProbeAddresses probeAddresses;
    protected final Timer timer = new Timer();
    
    protected Persistence persistence;

    protected Prober(ProbeAddresses probeAddresses) {
        this.probeAddresses = probeAddresses;
    }

    void setLogger(Logger logger) {
        this.logger = logger;
    }
    
    protected void initLMDB() {
        PersistenceUtils persistenceUtils = new PersistenceUtils(networkParameters);
        persistence = new LMDBPersistence(probeAddresses.lmdbConfigurationReadOnly, persistenceUtils);
        persistence.init();
    }

    protected void startStatisticsTimer() {
        long period = probeAddresses.printStatisticsEveryNSeconds * MILLISECONDS_TO_SECONDS;
        if (period < 1) {
            period = 60 * MILLISECONDS_TO_SECONDS;
        }
        startTime = System.currentTimeMillis();

        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                long uptime = System.currentTimeMillis() - startTime;
                long keys = checkedKeys.get();
                // prevent division by zero in the statistics log
                long uptimeInSeconds = Math.max(uptime / (long) MILLISECONDS_TO_SECONDS, 1);
                long uptimeInMinutes = uptimeInSeconds / 60;
                logger.info("Statistics: Checked " + (keys / 1_000_000L) + "M keys in " + uptimeInMinutes + " minutes. [" + keys / uptimeInSeconds + " keys/second] [" + keys / uptimeInMinutes + " keys/minute]. " + emptyConsumer.get() + " times an empty consumer." + hits.get() + " hits.");
            }
        }, period, period);
    }

    protected void addSchutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            shouldRun.set(false);
            timer.cancel();
            logger.info("Shut down.");
        }));
    }
}

@@ -99,56 +99,8 @@
 s;%0D%0A
-import net.ladenthin.javacommons.StreamHelper;%0D%0A
 impo
@@ -248,219 +248,8 @@
 %0D%0A%0D%0A
-import java.io.File;%0D%0Aimport java.io.IOException;%0D%0Aimport java.nio.ByteBuffer;%0D%0Aimport java.util.Arrays;%0D%0Aimport java.util.Deque;%0D%0Aimport java.util.HashSet;%0D%0Aimport java.util.LinkedList;%0D%0Aimport java.util.Set;%0D%0A
 impo
@@ -591,85 +591,8 @@
 ce;%0D
-%0Aimport org.bitcoinj.core.ECKey;%0D%0Aimport org.bouncycastle.util.encoders.Hex;%0D
 %0A%0D%0Ap
@@ -673,32 +673,35 @@
 nal int 
-MILLI
+ONE_
 SECOND
-S_TO_
+_IN_MILLI
 SECONDS 
@@ -1527,20 +1527,16 @@
 mer();%0D%0A
-    
 %0D%0A    pr
@@ -1757,20 +1757,16 @@
 %0A    %7D%0D%0A
-    
 %0D%0A    pr
@@ -2133,32 +2133,35 @@
 conds * 
-MILLI
+ONE_
 SECOND
-S_TO_
+_IN_MILLI
 SECONDS;
@@ -2182,18 +2182,19 @@
 period %3C
- 1
+= 0
 ) %7B%0D%0A   
@@ -2206,45 +2206,76 @@
     
-period = 60 * MILLISECONDS_TO_SECONDS
+throw new IllegalArgumentException(%22period must be greater than 0.%22)
 ;%0D%0A 
@@ -2440,24 +2440,70 @@
 id run() %7B%0D%0A
+                // get transient information%0D%0A
             
@@ -2629,57 +2629,45 @@
     
-// prevent division by zero in the statistics log
+%0D%0A                // calculate uptime
 %0D%0A  
@@ -2707,17 +2707,8 @@
 s = 
-Math.max(
 upti
@@ -2723,35 +2723,34 @@
 ng) 
-MILLI
+ONE_
 SECOND
-S_TO_
+_IN_MILLI
 SECONDS
-, 1)
 ;%0D%0A 
@@ -2806,24 +2806,329 @@
 onds / 60;%0D%0A
+                %0D%0A                // calculate per time, prevent division by zero with Math.max%0D%0A                long keysPerSecond = keys / Math.max(uptimeInSeconds, 1);%0D%0A                long keysPerMinute = keys / Math.max(uptimeInMinutes, 1);%0D%0A                %0D%0A                // log the information%0D%0A
             
@@ -3247,26 +3247,17 @@
 keys
- / uptimeIn
+Per
 Second
-s
  + %22
@@ -3283,26 +3283,17 @@
 keys
- / uptimeIn
+Per
 Minute
-s
  + %22
@@ -3359,16 +3359,17 @@
 onsumer.
+ 
 %22 + hits

Improve statistics timer.