package com.airlock.iam.security.util;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.concurrent.ThreadSafe;

@ThreadSafe
public class SeededSecureRandom extends SecureRandom {
   private static final SeededSecureRandom INSTANCE = new SeededSecureRandom();
   private final AtomicInteger usageCounter = new AtomicInteger(-1);

   public static SeededSecureRandom getInstance() {
      return INSTANCE;
   }

   @Override
   public void nextBytes(byte[] bytes) {
      this.checkSeed();
      super.nextBytes(bytes);
   }

   @Override
   public boolean nextBoolean() {
      this.checkSeed();
      return super.nextBoolean();
   }

   @Override
   public double nextDouble() {
      this.checkSeed();
      return super.nextDouble();
   }

   @Override
   public float nextFloat() {
      this.checkSeed();
      return super.nextFloat();
   }

   @Override
   public int nextInt() {
      this.checkSeed();
      return super.nextInt();
   }

   @Override
   public int nextInt(int n) {
      this.checkSeed();
      return super.nextInt(n);
   }

   @Override
   public long nextLong() {
      this.checkSeed();
      return super.nextLong();
   }

   @Override
   public double nextGaussian() {
      this.checkSeed();
      return super.nextGaussian();
   }

   private void checkSeed() {
      int count = this.usageCounter.getAndIncrement();
      if (count == -1) {
         super.nextBytes(new byte[20]);
         this.usageCounter.set(1);
      } else if (count % 50000 == 0) {
         this.internalReseed();
      }
   }

   private void internalReseed() {
      this.usageCounter.set(1);
      Thread reseedThread = new Thread(() -> super.setSeed(this.generateSeed(20)), "SeededSecureRandom Reseed");
      reseedThread.setDaemon(true);
      reseedThread.start();
   }

   @Override
   public void setSeed(byte[] seed) {
      super.setSeed(seed);
      this.usageCounter.set(1);
   }

   @Override
   public void setSeed(long seed) {
      super.setSeed(seed);
      if (seed != 0L) {
         this.usageCounter.set(1);
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   private SeededSecureRandom() {
   }
}
