/*
 * Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 */

package me.lucko.luckperms.commands;

import lombok.Getter;
import me.lucko.luckperms.constants.Permission;

import java.lang.ref.WeakReference;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Factory class to make a thread-safe sender instance
 * @param <T> the command sender type
 */
public abstract class SenderFactory<T> implements Runnable {
    private final Map<T, List<String>> messages = new HashMap<>();
    //private final List<Map.Entry<T, String>> messages = new ArrayList<>();
    private final SenderFactory<T> factory = this;

    protected abstract String getName(T t);
    protected abstract UUID getUuid(T t);
    protected abstract void sendMessage(T t, String s);
    protected abstract boolean hasPermission(T t, String node);

    public final Sender wrap(T t) {
        return new Sender() {
            final WeakReference<T> tRef = new WeakReference<>(t);

            // Cache these permissions, so they can be accessed async
            final Map<Permission, Boolean> perms = Arrays.stream(Permission.values())
                    .map(p -> new AbstractMap.SimpleEntry<>(p, factory.hasPermission(t, p.getNode())))
                    .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));

            @Getter
            final String name = factory.getName(t);

            @Getter
            final UUID uuid = factory.getUuid(t);

            @Override
            public void sendMessage(String s) {
                final T t = tRef.get();
                if (t != null) {
                    synchronized (messages) {
                        if (!messages.containsKey(t)) {
                            messages.put(t, new ArrayList<>());
                        }

                        messages.get(t).add(s);
                    }
                }
            }

            @Override
            public boolean hasPermission(Permission permission) {
                synchronized (perms) {
                    return perms.get(permission);
                }
            }
        };
    }

    @Override
    public final void run() {
        synchronized (messages) {
            if (!messages.isEmpty()) {
                messages.entrySet().forEach(e -> e.getValue().forEach(s -> factory.sendMessage(e.getKey(), s)));
                messages.clear();
            }
        }
    }
}

@@ -1180,16 +1180,63 @@
 mands;%0A%0A
+import com.google.common.collect.ImmutableMap;%0A
 import l
@@ -1354,16 +1354,66 @@
 util.*;%0A
+import java.util.concurrent.atomic.AtomicBoolean;%0A
 import j
@@ -1674,18 +1674,16 @@
 ();%0A    
-//
 private 
@@ -1692,62 +1692,58 @@
 nal 
-List%3CMap.Entry%3CT, String%3E%3E messages = new ArrayList%3C%3E(
+AtomicBoolean shouldSend = new AtomicBoolean(false
 );%0A 
@@ -2254,16 +2254,36 @@
 perms = 
+ImmutableMap.copyOf(
 Arrays.s
@@ -2334,202 +2334,84 @@
    .
-map(p -%3E new AbstractMap.SimpleEntry%3C%3E(p, factory.hasPermission(t, p.getNode())))%0A                    .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue
+collect(Collectors.toMap(p -%3E p, p -%3E factory.hasPermission(t, p.getNode()))
 ));%0A
@@ -2953,32 +2953,74 @@
               %7D%0A
+                    shouldSend.set(true);%0A
                 
@@ -3128,51 +3128,8 @@
 ) %7B%0A
-                synchronized (perms) %7B%0A    
     
@@ -3170,34 +3170,16 @@
 ssion);%0A
-                %7D%0A
         
@@ -3258,59 +3258,105 @@
     
-synchronized (messages) %7B%0A            if
+if (!shouldSend.getAndSet(false)) %7B%0A            return;%0A        %7D%0A%0A        synchronized
  (
-!
 messages
 .isE
@@ -3355,18 +3355,8 @@
 ages
-.isEmpty()
 ) %7B%0A
@@ -3371,70 +3371,134 @@
     
-    messages.entrySet().forEach(e -%3E e.getValue().forEach(s -%3E
+for (Map.Entry%3CT, List%3CString%3E%3E e : messages.entrySet()) %7B%0A                for (String s : e.getValue()) %7B%0A                   
  fac
@@ -3528,24 +3528,51 @@
 ey(), s)
-))
 ;%0A    
+            %7D%0A            %7D%0A%0A
         
@@ -3593,30 +3593,16 @@
 lear();%0A
-            %7D%0A
         

make SenderFactory nicer & maybe faster