/*
 * Copyright (c) 2019 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.sdk.server.diagnostics.objects;

import java.util.Map;
import java.util.UUID;

import com.google.common.collect.Maps;
import org.eclipse.milo.opcua.sdk.core.Reference;
import org.eclipse.milo.opcua.sdk.server.AbstractLifecycle;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.Session;
import org.eclipse.milo.opcua.sdk.server.SessionListener;
import org.eclipse.milo.opcua.sdk.server.api.NodeManager;
import org.eclipse.milo.opcua.sdk.server.diagnostics.variables.SessionDiagnosticsVariableArray;
import org.eclipse.milo.opcua.sdk.server.diagnostics.variables.SessionSecurityDiagnosticsVariableArray;
import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.SessionDiagnosticsObjectTypeNode;
import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.SessionsDiagnosticsSummaryTypeNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaNodeContext;
import org.eclipse.milo.opcua.sdk.server.nodes.factories.NodeFactory;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SessionsDiagnosticsSummaryObject extends AbstractLifecycle {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final Map<NodeId, SessionDiagnosticsObject> sessionDiagnosticsObjects = Maps.newConcurrentMap();

    private SessionDiagnosticsVariableArray sessionDiagnosticsVariableArray;
    private SessionSecurityDiagnosticsVariableArray sessionSecurityDiagnosticsVariableArray;

    private SessionListener sessionListener;

    private final OpcUaServer server;
    private final NodeFactory nodeFactory;
    private final NodeManager<UaNode> diagnosticsNodeManager;

    private final SessionsDiagnosticsSummaryTypeNode node;

    public SessionsDiagnosticsSummaryObject(
        SessionsDiagnosticsSummaryTypeNode node,
        NodeManager<UaNode> diagnosticsNodeManager
    ) {

        this.node = node;
        this.diagnosticsNodeManager = diagnosticsNodeManager;

        this.server = node.getNodeContext().getServer();

        this.nodeFactory = new NodeFactory(new UaNodeContext() {
            @Override
            public OpcUaServer getServer() {
                return server;
            }

            @Override
            public NodeManager<UaNode> getNodeManager() {
                return diagnosticsNodeManager;
            }
        });
    }

    @Override
    protected void onStartup() {
        sessionDiagnosticsVariableArray = new SessionDiagnosticsVariableArray(
            node.getSessionDiagnosticsArrayNode(),
            diagnosticsNodeManager
        );
        sessionDiagnosticsVariableArray.startup();

        sessionSecurityDiagnosticsVariableArray = new SessionSecurityDiagnosticsVariableArray(
            node.getSessionSecurityDiagnosticsArrayNode(),
            diagnosticsNodeManager
        );
        sessionSecurityDiagnosticsVariableArray.startup();


        server.getSessionManager().getAllSessions().forEach(this::createSessionDiagnosticsObject);

        server.getSessionManager().addSessionListener(sessionListener = new SessionListener() {
            @Override
            public void onSessionCreated(Session session) {
                synchronized (SessionsDiagnosticsSummaryObject.this) {
                    createSessionDiagnosticsObject(session);
                }
            }

            @Override
            public void onSessionClosed(Session session) {
                synchronized (SessionsDiagnosticsSummaryObject.this) {
                    SessionDiagnosticsObject sdo = sessionDiagnosticsObjects.remove(session.getSessionId());

                    if (sdo != null) {
                        sdo.shutdown();
                    }
                }
            }
        });
    }

    private void createSessionDiagnosticsObject(Session session) {
        try {
            SessionDiagnosticsObjectTypeNode sdoNode = (SessionDiagnosticsObjectTypeNode) nodeFactory.createNode(
                new NodeId(1, UUID.randomUUID()),
                Identifiers.SessionDiagnosticsObjectType
            );
            sdoNode.setBrowseName(new QualifiedName(1, session.getSessionName()));
            sdoNode.setDisplayName(LocalizedText.english(session.getSessionName()));

            sdoNode.addReference(new Reference(
                sdoNode.getNodeId(),
                Identifiers.HasComponent,
                node.getNodeId().expanded(),
                Reference.Direction.INVERSE
            ));
            diagnosticsNodeManager.addNode(sdoNode);

            SessionDiagnosticsObject sdo = new SessionDiagnosticsObject(sdoNode, session, diagnosticsNodeManager);

            sessionDiagnosticsObjects.put(session.getSessionId(), sdo);

            sdo.startup();
        } catch (UaException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onShutdown() {
        logger.debug("SessionsDiagnosticsSummaryObject onShutdown()");

        if (sessionListener != null) {
            server.getSessionManager().removeSessionListener(sessionListener);
        }

        sessionDiagnosticsVariableArray.shutdown();
        sessionSecurityDiagnosticsVariableArray.shutdown();

        sessionDiagnosticsObjects.values()
            .forEach(SessionDiagnosticsObject::shutdown);
        sessionDiagnosticsObjects.clear();

        node.delete();
    }

}
