package no.stelar7.api.l4j8.basic.ratelimiting;


import no.stelar7.api.l4j8.basic.DataCall;

import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;

/**
 * Burst ratelimiter will use as many calls as possible, then wait when it reaches the limit
 */
public class BurstRateLimiter extends RateLimiter
{
    
    public BurstRateLimiter(RateLimit... limits)
    {
        super(limits);
    }
    
    @Override
    public void acquire()
    {
        try
        {
            semaphore.acquireUninterruptibly();
            
            Thread.sleep(getDelay());
            
            update();
            
            semaphore.release();
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
    
    @Override
    public void updatePermitsPerX(Map<Integer, Long> data)
    {
        semaphore.acquireUninterruptibly();
        
        for (Entry<Integer, Long> key : data.entrySet())
        {
            limits.stream().filter(l -> l.getTimeframeInMS() / 1000 == key.getKey()).forEach(l ->
                                                                                             {
                                                                                                 long oldVal = callCountInTime.get(l);
                                                                                                 long newVal = key.getValue();
                                                                                                 if (oldVal + 1 < newVal)
                                                                                                 {
                                                                                                     callCountInTime.put(l, newVal);
                    
                                                                                                     if (DataCall.VERBOSE_LIMITING)
                                                                                                     {
                                                                                                         System.out.println("limit " + key + " has changed from " + oldVal + " to " + newVal);
                                                                                                     }
                                                                                                 }
                                                                                             });
        }
        semaphore.release();
    }
    
    private long getDelay()
    {
        Instant now   = Instant.now();
        long[]  delay = {0};
        int     bias  = 2;
        
        limits.stream().sorted(Comparator.comparing(RateLimit::getTimeframeInMS)).forEachOrdered(limit ->
                                                                                                 {
                                                                                                     if (callCountInTime.get(limit) >= limit.getRequests())
                                                                                                     {
                                                                                                         long newDelay = firstCallInTime.get(limit).toEpochMilli() + limit.getTimeframeInMS() - now.toEpochMilli();
                                                                                                         if (newDelay > delay[0])
                                                                                                         {
                                                                                                             delay[0] = newDelay;
                                                                                                         }
                                                                                                     }
                                                                                                 });
        
        if (delay[0] != 0)
        {
            delay[0] = (long) ((Math.ceil(delay[0] / 1000f) + bias) * 1000L);
        }
        
        if (DataCall.VERBOSE_LIMITING)
        {
            System.out.println("Sleeping for: " + delay[0]);
        }
        
        return delay[0];
    }
    
    private void update()
    {
        Instant now = Instant.now();
        limits.forEach(l ->
                       {
                           if (firstCallInTime.get(l).toEpochMilli() < now.toEpochMilli() - l.getTimeframeInMS())
                           {
                               firstCallInTime.put(l, now);
                               callCountInTime.put(l, 0L);
                           }
            
                           callCountInTime.compute(l, (k, v) -> v + 1);
            
                           if (DataCall.VERBOSE_LIMITING)
                           {
                               System.out.println("Calls made: " + callCountInTime.get(l) + " in: " + l.getTimeframeInMS() / 1000);
                           }
                       });
    }
    
}
@@ -554,31 +554,15 @@
     
-Thread.sleep(getDelay()
+update(
 );%0A 
@@ -577,39 +577,55 @@
    %0A            
-update(
+Thread.sleep(getDelay()
 );%0A            %0A
@@ -4451,32 +4451,36 @@
 limits.forEach(l
+imit
  -%3E%0A            
@@ -4522,16 +4522,17 @@
      if 
+(
 (firstCa
@@ -4545,16 +4545,20 @@
 me.get(l
+imit
 ).toEpoc
@@ -4566,17 +4566,17 @@
 Milli() 
-%3C
+-
  now.toE
@@ -4590,12 +4590,17 @@
 li()
- - l
+) + limit
 .get
@@ -4614,16 +4614,20 @@
 meInMS()
+ %3C 0
 )%0A      
@@ -4701,16 +4701,20 @@
 me.put(l
+imit
 , now);%0A
@@ -4765,16 +4765,20 @@
 me.put(l
+imit
 , 0L);%0A 
@@ -4870,16 +4870,20 @@
 ompute(l
+imit
 , (k, v)
@@ -5082,16 +5082,20 @@
 me.get(l
+imit
 ) + %22 in
@@ -5101,16 +5101,20 @@
 n: %22 + l
+imit
 .getTime

Update before sleeping