package net.ddns.mucaho.jnetrobust;

import net.ddns.mucaho.jnetrobust.control.Metadata;
import net.ddns.mucaho.jnetrobust.controller.DebugController;
import net.ddns.mucaho.jnetrobust.controller.Packet;
import net.ddns.mucaho.jnetrobust.controller.RetransmissionController;
import net.ddns.mucaho.jnetrobust.util.*;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.*;


public class Protocol<T> implements Comparator<Short> {
    private final RetransmissionController<T> controller;

    public Protocol(ProtocolListener<T> protocolListener) {
        this(new ProtocolConfig<T>(protocolListener));
    }
    public Protocol(ProtocolConfig<T> config) {
        this(config, null, null);
    }
    public Protocol(ProtocolListener<T> protocolListener, String name, Logger logger) {
        this(new ProtocolConfig<T>(protocolListener), name, logger);
    }
    public Protocol(ProtocolConfig<T> config, String name, Logger logger) {
        if (name != null && logger != null) {
            ProtocolListener<T> debugListener = new DebugProtocolListener<T>(config.listener, name, logger);
            this.controller = new DebugController<T>(new ProtocolConfig<T>(debugListener, config), name, logger);
        } else {
            this.controller = new RetransmissionController<T>(config);
        }

    }




    private final PacketEntry<T> sentPacketOut = new PacketEntry<T>();

    public synchronized Map.Entry<Short, Packet<T>> send() {
        return send(null);
    }

    public synchronized Map.Entry<Short, Packet<T>> send(T data) {
        Packet<T> packet = controller.produce();
        Collection<Metadata<T>> retransmits = controller.retransmit();
        for (Metadata<T> retransmit : retransmits) {
            controller.send(packet, retransmit);
        }
        if (data != null)
            controller.send(packet, controller.produce(data));

        sentPacketOut.packet = packet;
        sentPacketOut.id = data != null ? packet.getLastMetadata().getDataId() : null;
        return sentPacketOut;
    }

    public synchronized Map.Entry<Short, Packet<T>> send(T data, ObjectOutput objectOutput) throws IOException {
        Map.Entry<Short, Packet<T>> packetEntry = send(data);
        Packet.<T>writeExternalStatic(packetEntry.getValue(), objectOutput);
        return packetEntry;
    }



    private final NavigableMap<Short, T> receivedDatas = new TreeMap<Short, T>(IdComparator.instance);
    private final NavigableMap<Short, T> receivedDatasOut = CollectionUtils.unmodifiableNavigableMap(receivedDatas);

    public synchronized NavigableMap<Short, T> receive(Packet<T> packet) {
        receivedDatas.clear();

        controller.consume(packet);
        Metadata<T> metadata = controller.receive(packet);
        while (metadata != null) {
            receivedDatas.put(metadata.getDataId(), controller.consume(metadata));
            metadata = controller.receive(packet);
        }

        return receivedDatasOut;
    }


    public synchronized NavigableMap<Short, T> receive(ObjectInput objectInput) throws IOException, ClassNotFoundException {
        Packet<T> packet = Packet.<T>readExternalStatic(objectInput);
        return receive(packet);
    }




    @Override
    public int compare(Short o1, Short o2) {
        return IdComparator.instance.compare(o1, o2);
    }

    public long getSmoothedRTT() {
        return controller.getSmoothedRTT();
    }

    public long getRTTVariation() {
        return controller.getRTTVariation();
    }

    private static class PacketEntry<T> implements Map.Entry<Short, Packet<T>> {
        private Short id;
        private Packet<T> packet;

        public PacketEntry() {
        }


        @Override
        public Short getKey() {
            return id;
        }

        @Override
        public Packet<T> getValue() {
            return packet;
        }

        @Override
        public Packet<T> setValue(Packet<T> packet) {
            throw new UnsupportedOperationException();
        }
    }
}

@@ -531,16 +531,60 @@
 troller;
+%0A    private final boolean shouldRetransmit;
 %0A%0A    pu
@@ -654,47 +654,266 @@
 his(
-new ProtocolConfig%3CT%3E(protocolListener)
+protocolListener, true);%0A    %7D%0A    public Protocol(ProtocolConfig%3CT%3E config) %7B%0A        this(config, true);%0A    %7D%0A    public Protocol(ProtocolListener%3CT%3E protocolListener, boolean shouldRetransmit) %7B%0A        this(protocolListener, shouldRetransmit, null, null
 );%0A 
@@ -953,32 +953,58 @@
 Config%3CT%3E config
+, boolean shouldRetransmit
 ) %7B%0A        this
@@ -1011,16 +1011,34 @@
 (config,
+ shouldRetransmit,
  null, n
@@ -1150,38 +1150,16 @@
    this(
-new ProtocolConfig%3CT%3E(
 protocol
@@ -1166,17 +1166,22 @@
 Listener
-)
+, true
 , name, 
@@ -1263,32 +1263,439 @@
 ogger logger) %7B%0A
+        this(config, true, name, logger);%0A    %7D%0A    public Protocol(ProtocolListener%3CT%3E protocolListener, boolean shouldRetransmit, String name, Logger logger) %7B%0A        this(new ProtocolConfig%3CT%3E(protocolListener), shouldRetransmit, name, logger);%0A    %7D%0A    public Protocol(ProtocolConfig%3CT%3E config, boolean shouldRetransmit, String name, Logger logger) %7B%0A        this.shouldRetransmit = shouldRetransmit;%0A
         if (name
@@ -2335,24 +2335,60 @@
 .produce();%0A
+        if (shouldRetransmit) %7B%0A    
         Coll
@@ -2454,16 +2454,20 @@
         
+    
 for (Met
@@ -2499,24 +2499,28 @@
 ransmits) %7B%0A
+    
             
@@ -2552,24 +2552,38 @@
 etransmit);%0A
+            %7D%0A
         %7D%0A  
@@ -4046,17 +4046,18 @@
 e(Short 
-o
+id
 1, Short
@@ -4057,17 +4057,18 @@
 , Short 
-o
+id
 2) %7B%0A   
@@ -4113,13 +4113,15 @@
 are(
-o1, o
+id1, id
 2);%0A

Add retransmit option to Protocol.