package org.yamcs.web.websocket;

import java.util.HashSet;
import java.util.Set;

import org.yamcs.Processor;
import org.yamcs.ProcessorException;
import org.yamcs.cmdhistory.CommandHistoryConsumer;
import org.yamcs.cmdhistory.CommandHistoryFilter;
import org.yamcs.cmdhistory.CommandHistoryRequestManager;
import org.yamcs.commanding.InvalidCommandId;
import org.yamcs.commanding.PreparedCommand;
import org.yamcs.parameter.Value;
import org.yamcs.protobuf.Commanding.CommandHistoryAttribute;
import org.yamcs.protobuf.Commanding.CommandHistoryEntry;
import org.yamcs.protobuf.Commanding.CommandId;
import org.yamcs.protobuf.Web.CommandHistorySubscriptionRequest;
import org.yamcs.protobuf.Yamcs.ProtoDataType;
import org.yamcs.utils.TimeEncoding;
import org.yamcs.utils.ValueUtility;

/**
 * Provides realtime command history subscription via web.
 */
public class CommandHistoryResource implements WebSocketResource, CommandHistoryConsumer {

    public static final String RESOURCE_NAME = "cmdhistory";

    private ConnectedWebSocketClient client;

    private CommandHistoryFilter allSubscription;
    private Set<CommandId> subscribedCommands = new HashSet<>();
    private CommandHistoryRequestManager requestManager;

    public CommandHistoryResource(ConnectedWebSocketClient client) {
        this.client = client;
        Processor processor = client.getProcessor();
        if (processor != null && processor.hasCommanding()) {
            requestManager = processor.getCommandHistoryManager();
        }
    }

    @Override
    public WebSocketReply subscribe(WebSocketDecodeContext ctx, WebSocketDecoder decoder) throws WebSocketException {
        if (requestManager == null) {
            return WebSocketReply.ack(ctx.getRequestId());
        }

        boolean subscribeAll = true;
        boolean ignorePastCommands = true;

        CommandHistorySubscriptionRequest req = null;
        if (ctx.getData() != null) {
            req = decoder.decodeMessageData(ctx,
                    CommandHistorySubscriptionRequest.newBuilder()).build();
            if (req.hasIgnorePastCommands()) {
                ignorePastCommands = req.getIgnorePastCommands();
            }
            if (req.getCommandIdCount() > 0) {
                subscribeAll = false;
                ignorePastCommands = false;
                for (CommandId commandId : req.getCommandIdList()) {
                    if (!subscribedCommands.contains(commandId)) {
                        try {
                            CommandHistoryEntry entry = requestManager.subscribeCommand(commandId, this);
                            subscribedCommands.add(commandId);

                            entry = CommandHistoryEntry.newBuilder(entry)
                                    .setGenerationTimeUTC(TimeEncoding.toString(commandId.getGenerationTime()))
                                    .build();
                            client.sendData(ProtoDataType.CMD_HISTORY, entry);

                        } catch (InvalidCommandId e) {
                            WebSocketException ex = new WebSocketException(ctx.getRequestId(), e);
                            ex.attachData("InvalidCommandIdentification", commandId);
                            throw ex;
                        }
                    }
                }
            }
        }

        if (subscribeAll) {
            long since = ignorePastCommands ? client.getProcessor().getCurrentTime() : 0;
            allSubscription = requestManager.subscribeCommandHistory(null, since, this);
        }

        return WebSocketReply.ack(ctx.getRequestId());
    }

    @Override
    public WebSocketReply unsubscribe(WebSocketDecodeContext ctx, WebSocketDecoder decoder) throws WebSocketException {
        return null;
    }

    @Override
    public void unselectProcessor() {
        if (requestManager != null) {
            if (allSubscription != null) {
                requestManager.unsubscribeCommandHistory(allSubscription.subscriptionId);
            }
            for (CommandId commandId : subscribedCommands) {
                requestManager.unsubscribeCommand(commandId, this);
            }
        }
        requestManager = null;
    }

    @Override
    public void selectProcessor(Processor processor) throws ProcessorException {
        if (processor.hasCommanding()) {
            requestManager = processor.getCommandHistoryManager();
            if (requestManager != null && allSubscription != null) {
                requestManager.addSubscription(allSubscription, this);
            }
        }
    }

    @Override
    public void addedCommand(PreparedCommand pc) {
        CommandHistoryEntry entry = CommandHistoryEntry.newBuilder().setCommandId(pc.getCommandId())
                .setGenerationTimeUTC(TimeEncoding.toString(pc.getCommandId().getGenerationTime()))
                .addAllAttr(pc.getAttributes())
                .build();
        client.sendData(ProtoDataType.CMD_HISTORY, entry);
    }

    @Override
    public void updatedCommand(CommandId cmdId, long changeDate, String key, Value value) {
        CommandHistoryAttribute cha = CommandHistoryAttribute.newBuilder()
                .setName(key)
                .setValue(ValueUtility.toGbp(value))
                .build();
        CommandHistoryEntry entry = CommandHistoryEntry.newBuilder()
                .setGenerationTimeUTC(TimeEncoding.toString(cmdId.getGenerationTime()))
                .setCommandId(cmdId)
                .addAttr(cha)
                .build();
        client.sendData(ProtoDataType.CMD_HISTORY, entry);
    }

    @Override
    public void socketClosed() {
        if (requestManager != null) {
            if (allSubscription != null) {
                requestManager.unsubscribeCommandHistory(allSubscription.subscriptionId);
            }
            for (CommandId commandId : subscribedCommands) {
                requestManager.unsubscribeCommand(commandId, this);
            }
        }
    }
}

@@ -1048,16 +1048,65 @@
 lient;%0A%0A
+    private volatile boolean subscribed = false;%0A
     priv
@@ -1692,32 +1692,60 @@
 cketException %7B%0A
+        subscribed = true;%0A%0A
         if (requ
@@ -4376,32 +4376,46 @@
 n %7B%0A        if (
+subscribed && 
 processor.hasCom
@@ -4419,32 +4419,32 @@
 Commanding()) %7B%0A
-
             requ
@@ -4532,20 +4532,40 @@
  != null
- && 
+) %7B%0A                if (
 allSubsc
@@ -4575,32 +4575,36 @@
 tion != null) %7B%0A
+    
                 
@@ -4650,32 +4650,245 @@
 ription, this);%0A
+                %7D else %7B%0A                    long since = client.getProcessor().getCurrentTime();%0A                    allSubscription = requestManager.subscribeCommandHistory(null, since, this);%0A                %7D%0A
             %7D%0A  

Fix cmdhist subscription not always transferring