package org.yamcs.web.websocket;

import java.io.IOException;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamcs.Processor;
import org.yamcs.management.ManagementGpbHelper;
import org.yamcs.management.ManagementListener;
import org.yamcs.management.ManagementService;
import org.yamcs.protobuf.Web.WebSocketServerMessage.WebSocketReplyData;
import org.yamcs.protobuf.Yamcs.ProtoDataType;
import org.yamcs.protobuf.YamcsManagement.ClientInfo;
import org.yamcs.protobuf.YamcsManagement.ClientInfo.ClientState;
import org.yamcs.protobuf.YamcsManagement.ProcessorInfo;
import org.yamcs.protobuf.YamcsManagement.Statistics;

/**
 * Provides access to any Processor/Client info over web socket
 */
public class ManagementResource extends AbstractWebSocketResource implements ManagementListener {

    private static final Logger log = LoggerFactory.getLogger(ManagementResource.class);
    public static final String RESOURCE_NAME = "management";

    public static final String OP_getProcessorInfo = "getProcessorInfo";
    public static final String OP_getClientInfo = "getClientInfo";
    public static final String OP_subscribe = "subscribe";

    private int clientId;

    public ManagementResource(WebSocketProcessorClient client) {
        super(client);
        clientId = client.getClientId();
    }

    @Override
    public WebSocketReply processRequest(WebSocketDecodeContext ctx, WebSocketDecoder decoder)
            throws WebSocketException {
        switch (ctx.getOperation()) {
        case OP_getProcessorInfo:
            return processGetProcessorInfoRequest(ctx, decoder);
        case OP_getClientInfo:
            return processGetClientInfoRequest(ctx, decoder);
        case OP_subscribe:
            return processSubscribeRequest(ctx, decoder);
        default:
            throw new WebSocketException(ctx.getRequestId(), "Unsupported operation '" + ctx.getOperation() + "'");
        }
    }

    private WebSocketReply processGetProcessorInfoRequest(WebSocketDecodeContext ctx, WebSocketDecoder decoder) {
        int requestId = ctx.getRequestId();
        ProcessorInfo pinfo = ManagementGpbHelper.toProcessorInfo(processor);
        try {
            wsHandler.sendReply(WebSocketReply.ack(requestId));
            wsHandler.sendData(ProtoDataType.PROCESSOR_INFO, pinfo);
        } catch (IOException e) {
            log.warn("Exception when sending data", e);
            return null;
        }
        return null;
    }

    // return client info of this client
    private WebSocketReply processGetClientInfoRequest(WebSocketDecodeContext ctx, WebSocketDecoder decoder) {
        int requestId = ctx.getRequestId();
        ClientInfo cinfo = ManagementService.getInstance().getClientInfo(clientId);
        cinfo = ClientInfo.newBuilder(cinfo).setState(ClientState.CONNECTED).setCurrentClient(true).build();
        try {
            wsHandler.sendReply(WebSocketReply.ack(requestId));
            wsHandler.sendData(ProtoDataType.CLIENT_INFO, cinfo);
        } catch (IOException e) {
            log.error("Exception when sending data", e);
            return null;
        }
        return null;
    }

    /**
     * Registers for updates on any processor or client. Sends the current set of processor, and clients (in that order)
     * to the requester.
     * <p>
     * Calling this multiple times, will cause the current set of data to be sent again. Further updates will still
     * arrive one-time only.
     */
    private WebSocketReply processSubscribeRequest(WebSocketDecodeContext ctx, WebSocketDecoder decoder) {
        try {
            wsHandler.sendReply(WebSocketReply.ack(ctx.getRequestId()));

            // Send current set of processors
            for (Processor processor : Processor.getProcessors()) {
                ProcessorInfo pinfo = ManagementGpbHelper.toProcessorInfo(processor);
                wsHandler.sendData(ProtoDataType.PROCESSOR_INFO, pinfo);
            }

            // Send current set of clients
            Set<ClientInfo> clients = ManagementService.getInstance().getClientInfo();
            for (ClientInfo client : clients) {
                ClientInfo cinfo = ClientInfo.newBuilder(client)
                        .setState(ClientState.CONNECTED).setCurrentClient(client.getId() == clientId).build();
                wsHandler.sendData(ProtoDataType.CLIENT_INFO, cinfo);
            }
        } catch (IOException e) {
            log.error("Exception when sending data", e);
            return null;
        }
        ManagementService.getInstance().addManagementListener(this);
        return null;
    }

    @Override
    public void quit() {
        ManagementService.getInstance().removeManagementListener(this);
    }

    @Override
    public void processorAdded(ProcessorInfo processorInfo) {
        try {
            wsHandler.sendData(ProtoDataType.PROCESSOR_INFO, processorInfo);
        } catch (IOException e) {
            log.error("Exception when sending data", e);
        }
    }

    @Override
    public void processorStateChanged(ProcessorInfo processorInfo) {
        try {
            wsHandler.sendData(ProtoDataType.PROCESSOR_INFO, processorInfo);
        } catch (IOException e) {
            log.error("Exception when sending data", e);
        }
    }

    @Override
    public void processorClosed(ProcessorInfo processorInfo) {
        try {
            wsHandler.sendData(ProtoDataType.PROCESSOR_INFO, processorInfo);
        } catch (IOException e) {
            log.error("Exception when sending data", e);
        }
    }

    @Override
    public void clientRegistered(ClientInfo ci) {
        ClientInfo cinfo = ClientInfo.newBuilder(ci).setState(ClientState.CONNECTED)
                .setCurrentClient(ci.getId() == clientId).build();
        try {
            wsHandler.sendData(ProtoDataType.CLIENT_INFO, cinfo);
        } catch (IOException e) {
            log.error("Exception when sending data", e);
        }
    }

    @Override
    public void clientInfoChanged(ClientInfo ci) {
        ClientInfo cinfo = ClientInfo.newBuilder(ci).setState(ClientState.CONNECTED)
                .setCurrentClient(ci.getId() == clientId).build();
        try {
            wsHandler.sendData(ProtoDataType.CLIENT_INFO, cinfo);
        } catch (IOException e) {
            log.error("Exception when sending data", e);
        }
    }

    @Override
    public void clientUnregistered(ClientInfo ci) {
        if (ci.getId() == clientId) {
            return;
        }

        ClientInfo cinfo = ClientInfo.newBuilder(ci).setState(ClientState.DISCONNECTED).setCurrentClient(false).build();
        try {
            wsHandler.sendData(ProtoDataType.CLIENT_INFO, cinfo);
        } catch (IOException e) {
            log.error("Exception when sending data", e);
        }
    }

    @Override
    public void statisticsUpdated(Processor processor, Statistics stats) {
        try {
            wsHandler.sendData(ProtoDataType.PROCESSING_STATISTICS, stats);
        } catch (IOException e) {
            log.error("Exception when sending data", e);
        }
    }
}

@@ -311,81 +311,8 @@
 ce;%0A
-import org.yamcs.protobuf.Web.WebSocketServerMessage.WebSocketReplyData;%0A
 impo
@@ -2147,36 +2147,43 @@
         
-wsHandler.sendReply(
+WebSocketReply reply = new 
 WebSocke
@@ -2180,36 +2180,32 @@
 w WebSocketReply
-.ack
 (requestId));%0A  
@@ -2191,34 +2191,229 @@
 Reply(requestId)
-)
 ;
+%0A            reply.attachData(%22ProcessorInfo%22, pinfo);%0A            wsHandler.sendReply(reply);%0A%0A            // TODO Should probably remove this line, now that we sent this already in the response.
 %0A            wsH
@@ -3034,36 +3034,43 @@
         
-wsHandler.sendReply(
+WebSocketReply reply = new 
 WebSocke
@@ -3075,20 +3075,16 @@
 ketReply
-.ack
 (request
@@ -3086,18 +3086,210 @@
 questId)
-)
 ;
+%0A            reply.attachData(%22ClientInfo%22, cinfo);%0A            wsHandler.sendReply(reply);%0A%0A            // TODO Should probably remove this line, now that we sent this already in the response.
 %0A       

Send current procInfo and clientInfo already in response