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 {
                            ArrayBlockingQueue<ResourceResponse> tempCreateQueue = new ArrayBlockingQueue<ResourceResponse>(
                                    10);
                            waitingResponses.putIfAbsent(response.getResourceName(), tempCreateQueue);
                            tempCreateQueue.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());

        BlockingQueue<ResourceResponse> queue = null;
        try {
            synchronized (waitingResponses) {
                waitingResponses.putIfAbsent(request.getResourceName(), new ArrayBlockingQueue<ResourceResponse>(
                        PropertyUtils.getLongSystemProperty("wscl.queue.size", 10).intValue()));
                queue = waitingResponses.get(request.getResourceName());
                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) {
                while (!queue.isEmpty()) {
                    try {
                        waitingResponses.wait();
                    } catch (InterruptedException e) {
                        throw new IOException("Interrupted in waiting for request." + request.getResourceName(), e);
                    }
                }
                waitingResponses.remove(request.getResourceName());
            }
        }
    }

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

@@ -3130,32 +3130,66 @@
      %7D);%0A    %7D%0A%0A
+    @SuppressWarnings(%22resource%22)%0A
     public Resou

Add SuppressWarnings