/*
 * 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.services;

import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.milo.opcua.sdk.server.DiagnosticsContext;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.Session;
import org.eclipse.milo.opcua.sdk.server.api.services.AttributeHistoryServices.HistoryReadContext;
import org.eclipse.milo.opcua.sdk.server.api.services.AttributeHistoryServices.HistoryUpdateContext;
import org.eclipse.milo.opcua.stack.core.StatusCodes;
import org.eclipse.milo.opcua.stack.core.types.builtin.DiagnosticInfo;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryReadDetails;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryReadRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryReadResponse;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryReadResult;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryReadValueId;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryUpdateDetails;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryUpdateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryUpdateResponse;
import org.eclipse.milo.opcua.stack.core.types.structured.HistoryUpdateResult;
import org.eclipse.milo.opcua.stack.core.types.structured.ResponseHeader;
import org.eclipse.milo.opcua.stack.server.services.AttributeHistoryServiceSet;
import org.eclipse.milo.opcua.stack.server.services.ServiceRequest;

import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.a;
import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.l;

public class DefaultAttributeHistoryServiceSet implements AttributeHistoryServiceSet {

    private final ServiceCounter historyReadMetric = new ServiceCounter();
    private final ServiceCounter historyUpdateMetric = new ServiceCounter();

    @Override
    public void onHistoryRead(ServiceRequest service) {
        historyReadMetric.record(service);

        HistoryReadRequest request = (HistoryReadRequest) service.getRequest();

        OpcUaServer server = service.attr(ServiceAttributes.SERVER_KEY).get();
        Session session = service.attr(ServiceAttributes.SESSION_KEY).get();

        List<HistoryReadValueId> nodesToRead = l(request.getNodesToRead());

        if (nodesToRead.isEmpty()) {
            service.setServiceFault(StatusCodes.Bad_NothingToDo);
            return;
        }

        if (nodesToRead.size() > server.getConfig().getLimits().getMaxNodesPerRead().longValue()) {
            service.setServiceFault(StatusCodes.Bad_TooManyOperations);
            return;
        }

        if (request.getTimestampsToReturn() == null) {
            service.setServiceFault(StatusCodes.Bad_TimestampsToReturnInvalid);
            return;
        }

        DiagnosticsContext<HistoryReadValueId> diagnosticsContext = new DiagnosticsContext<>();

        HistoryReadContext context = new HistoryReadContext(
            server,
            session,
            diagnosticsContext
        );

        HistoryReadDetails details = (HistoryReadDetails) request.getHistoryReadDetails().decode(
            server.getSerializationContext()
        );

        server.getAddressSpaceManager().historyRead(
            context,
            details,
            request.getTimestampsToReturn(),
            nodesToRead
        );

        context.getFuture().thenAccept(values -> {
            ResponseHeader header = service.createResponseHeader();

            DiagnosticInfo[] diagnosticInfos =
                diagnosticsContext.getDiagnosticInfos(nodesToRead);

            HistoryReadResponse response = new HistoryReadResponse(
                header,
                a(values, HistoryReadResult.class),
                diagnosticInfos
            );

            service.setResponse(response);
        });
    }

    @Override
    public void onHistoryUpdate(ServiceRequest service) {
        historyUpdateMetric.record(service);

        HistoryUpdateRequest request = (HistoryUpdateRequest) service.getRequest();

        OpcUaServer server = service.attr(ServiceAttributes.SERVER_KEY).get();
        Session session = service.attr(ServiceAttributes.SESSION_KEY).get();

        List<HistoryUpdateDetails> historyUpdateDetailsList = l(request.getHistoryUpdateDetails())
            .stream().map(e -> (HistoryUpdateDetails) e.decode(
                server.getSerializationContext()
            ))
            .collect(Collectors.toList());

        if (historyUpdateDetailsList.isEmpty()) {
            service.setServiceFault(StatusCodes.Bad_NothingToDo);
            return;
        }

        if (historyUpdateDetailsList.size() > server.getConfig().getLimits().getMaxNodesPerWrite().intValue()) {
            service.setServiceFault(StatusCodes.Bad_TooManyOperations);
            return;
        }

        DiagnosticsContext<HistoryUpdateDetails> diagnosticsContext = new DiagnosticsContext<>();

        HistoryUpdateContext context = new HistoryUpdateContext(
            server,
            session,
            diagnosticsContext
        );

        server.getAddressSpaceManager().historyUpdate(context, historyUpdateDetailsList);

        context.getFuture().thenAccept(values -> {
            ResponseHeader header = service.createResponseHeader();

            DiagnosticInfo[] diagnosticInfos =
                diagnosticsContext.getDiagnosticInfos(historyUpdateDetailsList);

            HistoryUpdateResponse response = new HistoryUpdateResponse(
                header,
                a(values, HistoryUpdateResult.class),
                diagnosticInfos
            );

            service.setResponse(response);
        });
    }

}
