package net.buycraft.plugin.execution.strategy;

import net.buycraft.plugin.IBuycraftPlatform;
import net.buycraft.plugin.platform.NoBlocking;

import java.math.BigDecimal;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

public class QueuedCommandExecutor implements CommandExecutor, Runnable {
    private static final long MAXIMUM_NOTIFICATION_TIME = TimeUnit.MILLISECONDS.toNanos(5);
    private static final int RUN_MAX_COMMANDS_BLOCKING = 10;

    private final IBuycraftPlatform platform;
    private final boolean blocking;
    private final Queue<ToRunQueuedCommand> commandQueue = new ConcurrentLinkedQueue<>();
    private final PostCompletedCommandsTask completedCommandsTask;

    public QueuedCommandExecutor(IBuycraftPlatform platform, PostCompletedCommandsTask completedCommandsTask) {
        this.platform = platform;
        this.blocking = !platform.getClass().isAnnotationPresent(NoBlocking.class);
        this.completedCommandsTask = completedCommandsTask;
    }

    @Override
    public void queue(ToRunQueuedCommand command) {
        if (!commandQueue.contains(command))
            commandQueue.add(command);
    }

    @Override
    public void run() {
        long start = System.nanoTime();
        int run = 0;

        for (Iterator<ToRunQueuedCommand> it = commandQueue.iterator(); it.hasNext(); ) {
            if (blocking && run >= RUN_MAX_COMMANDS_BLOCKING) {
                break; // We have run too many commands, run more later
            }

            ToRunQueuedCommand command = it.next();
            if (command.canExecute(platform)) {
                // Run the command now.
                String finalCommand = platform.getPlaceholderManager().doReplace(command.getPlayer(), command.getCommand());
                platform.log(Level.INFO, String.format("Dispatching command '%s' for player '%s'.", finalCommand, command.getPlayer().getName()));
                try {
                    platform.dispatchCommand(finalCommand);
                    completedCommandsTask.add(command.getCommand().getId());
                } catch (Exception e) {
                    platform.log(Level.SEVERE, String.format("Could not dispatch command '%s' for player '%s'. " +
                            "This is typically a plugin error, not an issue with BuycraftX.", finalCommand, command.getPlayer().getName()), e);
                }

                it.remove();
                run++;
            }
        }

        long fullTime = System.nanoTime() - start;
        if (fullTime > MAXIMUM_NOTIFICATION_TIME) {
            // Make the time much nicer.
            BigDecimal timeMs = new BigDecimal(fullTime).divide(new BigDecimal("1000000"), 2, BigDecimal.ROUND_CEILING);
            if (blocking) {
                platform.log(Level.SEVERE, "Command execution took " + timeMs.toPlainString() + "ms to complete. " +
                        "This likely indicates an issue with one of your server's plugins, which can cause lag.");
            } else {
                platform.log(Level.SEVERE, "Command execution took " + timeMs.toPlainString() + "ms to complete. " +
                        "This likely indicates an issue with one of your server's plugins, which will slow command execution.");
            }
        }
    }
}

@@ -187,40 +187,9 @@
 til.
-Iterator;%0Aimport java.util.Queue
+*
 ;%0Aim
@@ -636,21 +636,19 @@
 e final 
-Queue
+Set
 %3CToRunQu
@@ -679,37 +679,29 @@
 e = new 
-ConcurrentLinkedQueue
+LinkedHashSet
 %3C%3E();%0A  
@@ -1141,13 +1141,22 @@
     
-if
+synchronized
  (
-!
 comm
@@ -1167,27 +1167,11 @@
 ueue
-.contains(command))
+) %7B
 %0A   
@@ -1202,24 +1202,34 @@
 d(command);%0A
+        %7D%0A
     %7D%0A%0A    @
@@ -1273,62 +1273,108 @@
     
-long start = System.nanoTime();%0A        int run = 0;%0A%0A
+List%3CToRunQueuedCommand%3E runThisTick = new ArrayList%3C%3E();%0A        synchronized (commandQueue) %7B%0A    
     
@@ -1455,24 +1455,234 @@
 Next(); ) %7B%0A
+                ToRunQueuedCommand command = it.next();%0A                if (command.canExecute(platform)) %7B%0A                    runThisTick.add(command);%0A                    it.remove();%0A                %7D%0A%0A    
             
@@ -1700,16 +1700,31 @@
 g && run
+ThisTick.size()
  %3E= RUN_
@@ -1768,223 +1768,159 @@
     
+   
+ 
 break;
- // We have run too many commands, run more later%0A            %7D%0A%0A            ToRunQueuedCommand command = it.next();%0A            if (command.canExecute(platform)) %7B%0A                // Run the command now.%0A    
+%0A                %7D%0A            %7D%0A        %7D%0A%0A        long start = System.nanoTime();%0A        for (ToRunQueuedCommand command : runThisTick) %7B%0A
     
@@ -2032,28 +2032,24 @@
 Command());%0A
-    
             
@@ -2195,22 +2195,14 @@
     
-    
 try %7B%0A
-    
     
@@ -2269,20 +2269,16 @@
         
-  
-  
 complete
@@ -2338,20 +2338,16 @@
         
-    
 %7D catch 
@@ -2370,36 +2370,32 @@
                 
-   
- 
 platform.log(Lev
@@ -2469,28 +2469,24 @@
 r '%25s'. %22 +%0A
-    
             
@@ -2617,79 +2617,8 @@
 e);%0A
-                %7D%0A%0A                it.remove();%0A                run++;%0A
     

Improve command queue deduplication.