package net.unit8.wscl;

import net.unit8.wscl.dto.ResourceRequest;
import net.unit8.wscl.dto.ResourceResponse;
import net.unit8.wscl.handler.ResourceRequestWriteHandler;
import net.unit8.wscl.handler.ResourceResponseReadHandler;
import net.unit8.wscl.util.FressianUtils;
import net.unit8.wscl.util.PropertyUtils;
import org.fressian.FressianReader;
import org.fressian.FressianWriter;
import org.fressian.handlers.ILookup;
import org.fressian.handlers.ReadHandler;
import org.fressian.handlers.WriteHandler;
import org.fressian.impl.ByteBufferInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author kawasima
 */
@ClientEndpoint
public class ClassLoaderEndpoint extends Endpoint {
    private static final Logger logger = LoggerFactory.getLogger(ClassLoaderEndpoint.class);

    private Session session;
    private final ConcurrentMap<String, BlockingQueue<ResourceResponse>> waitingResponses = new ConcurrentHashMap<>();

    public ClassLoaderEndpoint () {
    }

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        this.session = session;
        session.addMessageHandler(new MessageHandler.Whole<ByteBuffer>() {
            @Override
            public void onMessage(ByteBuffer buf) {
                try {
                    FressianReader reader = new FressianReader(new ByteBufferInputStream(buf), new ILookup<Object, ReadHandler>() {
                        @Override
                        public ReadHandler valAt(Object key) {
                            if (key.equals(ResourceResponse.class.getName()))
                                return new ResourceResponseReadHandler();
                            else
                                return null;
                        }
                    });

                    Object obj = reader.readObject();
                    if (obj instanceof ResourceResponse) {
                        ResourceResponse response = (ResourceResponse) obj;
                        BlockingQueue<ResourceResponse> queue = waitingResponses.get(response.getResourceName());
                        if (queue != null) {
                            queue.offer(response);
                        }
                    } else {
                        logger.warn("Fressian read response: " + obj + "(" + obj.getClass() + ")");
                    }
                } catch (IOException ex) {
                    logger.warn("read response error", ex);
                }
            }
        });
    }

    public ResourceResponse request(ResourceRequest request) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FressianWriter fw = new FressianWriter(baos, new ILookup<Class, Map<String, WriteHandler>>() {
            @Override
            public Map<String, WriteHandler> valAt(Class key) {
                if (key.equals(ResourceRequest.class)) {
                    return FressianUtils.map(ResourceRequest.class.getName(),
                            new ResourceRequestWriteHandler());
                } else {
                    return null;
                }
            }
        });
        fw.writeObject(request);

        logger.debug("fetch class:" + request.getResourceName() + ":" + request.getClassLoaderId());

        waitingResponses.putIfAbsent(request.getResourceName(), new ArrayBlockingQueue<ResourceResponse>(10));
        BlockingQueue<ResourceResponse> queue = waitingResponses.get(request.getResourceName());
        try {
            session.getAsyncRemote().sendBinary(ByteBuffer.wrap(baos.toByteArray()));
            ResourceResponse response = queue.poll(PropertyUtils.getLongSystemProperty("wscl.timeout", 5000), TimeUnit.MILLISECONDS);

            if (response == null)
                throw new IOException("WebSocket request error." + request.getResourceName());
            return response;
        } catch(InterruptedException ex) {
            throw new IOException("Interrupted in waiting for request." + request.getResourceName(), ex);
        } finally {
            synchronized (waitingResponses) {
                if (queue.isEmpty()) {
                    waitingResponses.remove(request.getResourceName());
                }
            }
        }
    }

    public void close() throws IOException {
        if (session != null && session.isOpen()) {
            session.close();
            session = null;
        }
    }
}

@@ -1151,17 +1151,16 @@
 Endpoint
- 
 () %7B%0A   
@@ -1537,16 +1537,44 @@
 am(buf),
+%0A                           
  new ILo
@@ -1619,32 +1619,40 @@
                 
+       
+ 
 @Override%0A      
@@ -1637,32 +1637,40 @@
       @Override%0A
+        
                 
@@ -1736,32 +1736,40 @@
                 
+  
+      
 if (key.equals(R
@@ -1802,16 +1802,24 @@
 ame()))%0A
+        
         
@@ -1912,16 +1912,24 @@
         
+        
 else%0A   
@@ -1949,32 +1949,40 @@
                 
+ 
+       
 return null;%0A   
@@ -1994,34 +1994,50 @@
                 
-%7D%0A
+        %7D%0A        
                 
@@ -3548,16 +3548,24 @@
 rId());%0A
+        
 %0A       
@@ -3557,32 +3557,159 @@
        %0A        
+%0A        BlockingQueue%3CResourceResponse%3E queue = null;%0A        try %7B%0A            synchronized (waitingResponses) %7B%0A            
 waitingResponses
@@ -3793,53 +3793,92 @@
 se%3E(
-10));%0A        BlockingQueue%3CResourceResponse%3E
+PropertyUtils.getLongSystemProperty(%22wscl.queue.size%22, 10).intValue()));%0A           
  que
@@ -3931,30 +3931,16 @@
 ame());%0A
-        try %7B%0A
         
@@ -4151,16 +4151,28 @@
 CONDS);%0A
+            
 %0A       
@@ -4322,16 +4322,30 @@
 sponse;%0A
+            %7D%0A
         

Add sync timing.