/*
 * Copyright (c) 2021 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.client.api.config;

import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;

import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.HashedWheelTimer;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
import org.eclipse.milo.opcua.stack.client.UaStackClientConfig;
import org.eclipse.milo.opcua.stack.client.UaStackClientConfigBuilder;
import org.eclipse.milo.opcua.stack.client.security.ClientCertificateValidator;
import org.eclipse.milo.opcua.stack.core.channel.EncodingLimits;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

public class OpcUaClientConfigBuilder extends UaStackClientConfigBuilder {

    private LocalizedText applicationName = LocalizedText.english("Eclipse Milo application name not configured");
    private String applicationUri = "urn:eclipse:milo:client:applicationUriNotConfigured";
    private String productUri = "https://github.com/eclipse/milo";

    private Supplier<String> sessionName;
    private String[] sessionLocaleIds = new String[0];
    private UInteger sessionTimeout = uint(120000);

    private IdentityProvider identityProvider = new AnonymousProvider();

    private UInteger maxResponseMessageSize = uint(0);
    private UInteger maxPendingPublishRequests = uint(UInteger.MAX_VALUE);

    private UInteger keepAliveFailuresAllowed = uint(1);
    private UInteger keepAliveInterval = uint(5000);
    private UInteger keepAliveTimeout = uint(5000);
    private double subscriptionWatchdogMultiplier = 2.0;

    public OpcUaClientConfigBuilder setApplicationName(LocalizedText applicationName) {
        this.applicationName = applicationName;
        return this;
    }

    public OpcUaClientConfigBuilder setApplicationUri(String applicationUri) {
        this.applicationUri = applicationUri;
        return this;
    }

    public OpcUaClientConfigBuilder setProductUri(String productUri) {
        this.productUri = productUri;
        return this;
    }

    public OpcUaClientConfigBuilder setSessionName(Supplier<String> sessionName) {
        this.sessionName = sessionName;
        return this;
    }

    public OpcUaClientConfigBuilder setSessionLocaleIds(String[] sessionLocaleIds) {
        this.sessionLocaleIds = sessionLocaleIds;
        return this;
    }

    public OpcUaClientConfigBuilder setSessionTimeout(UInteger sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
        return this;
    }

    public OpcUaClientConfigBuilder setMaxResponseMessageSize(UInteger maxResponseMessageSize) {
        this.maxResponseMessageSize = maxResponseMessageSize;
        return this;
    }

    public OpcUaClientConfigBuilder setMaxPendingPublishRequests(UInteger maxPendingPublishRequests) {
        this.maxPendingPublishRequests = maxPendingPublishRequests;
        return this;
    }

    public OpcUaClientConfigBuilder setIdentityProvider(IdentityProvider identityProvider) {
        this.identityProvider = identityProvider;
        return this;
    }

    public OpcUaClientConfigBuilder setKeepAliveFailuresAllowed(UInteger keepAliveFailuresAllowed) {
        this.keepAliveFailuresAllowed = keepAliveFailuresAllowed;
        return this;
    }

    public OpcUaClientConfigBuilder setKeepAliveInterval(UInteger keepAliveInterval) {
        this.keepAliveInterval = keepAliveInterval;
        return this;
    }

    public OpcUaClientConfigBuilder setKeepAliveTimeout(UInteger keepAliveTimeout) {
        this.keepAliveTimeout = keepAliveTimeout;
        return this;
    }

    public OpcUaClientConfigBuilder setSubscriptionWatchdogMultiplier(double subscriptionWatchdogMultiplier) {
        this.subscriptionWatchdogMultiplier = subscriptionWatchdogMultiplier;
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setEndpoint(EndpointDescription endpoint) {
        super.setEndpoint(endpoint);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setKeyPair(KeyPair keyPair) {
        super.setKeyPair(keyPair);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setCertificate(X509Certificate certificate) {
        super.setCertificate(certificate);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setCertificateChain(X509Certificate[] certificateChain) {
        super.setCertificateChain(certificateChain);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setCertificateValidator(ClientCertificateValidator certificateValidator) {
        super.setCertificateValidator(certificateValidator);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setChannelLifetime(UInteger channelLifetime) {
        super.setChannelLifetime(channelLifetime);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setExecutor(ExecutorService executor) {
        super.setExecutor(executor);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setEventLoop(NioEventLoopGroup eventLoop) {
        super.setEventLoop(eventLoop);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setScheduledExecutor(ScheduledExecutorService scheduledExecutor) {
        super.setScheduledExecutor(scheduledExecutor);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setWheelTimer(HashedWheelTimer wheelTimer) {
        super.setWheelTimer(wheelTimer);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setEncodingLimits(EncodingLimits encodingLimits) {
        super.setEncodingLimits(encodingLimits);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setConnectTimeout(UInteger connectTimeout) {
        super.setConnectTimeout(connectTimeout);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setAcknowledgeTimeout(UInteger acknowledgeTimeout) {
        super.setAcknowledgeTimeout(acknowledgeTimeout);
        return this;
    }

    @Override
    public OpcUaClientConfigBuilder setRequestTimeout(UInteger requestTimeout) {
        super.setRequestTimeout(requestTimeout);
        return this;
    }

    @Override
    public OpcUaClientConfig build() {
        UaStackClientConfig stackClientConfig = super.build();

        if (sessionName == null) {
            sessionName = () -> String.format("UaSession:%s:%s",
                applicationName.getText(),
                System.currentTimeMillis()
            );
        }

        return new OpcUaClientConfigImpl(
            stackClientConfig,
            applicationName,
            applicationUri,
            productUri,
            sessionName,
            sessionLocaleIds,
            sessionTimeout,
            maxResponseMessageSize,
            maxPendingPublishRequests,
            identityProvider,
            keepAliveFailuresAllowed,
            keepAliveInterval,
            keepAliveTimeout,
            subscriptionWatchdogMultiplier
        );
    }

    static class OpcUaClientConfigImpl implements OpcUaClientConfig {

        private final UaStackClientConfig stackClientConfig;
        private final LocalizedText applicationName;
        private final String applicationUri;
        private final String productUri;
        private final Supplier<String> sessionName;
        private final String[] sessionLocaleIds;
        private final UInteger sessionTimeout;
        private final UInteger maxResponseMessageSize;
        private final UInteger maxPendingPublishRequests;
        private final IdentityProvider identityProvider;
        private final UInteger keepAliveFailuresAllowed;
        private final UInteger keepAliveInterval;
        private final UInteger keepAliveTimeout;
        private final double subscriptionWatchdogMultiplier;

        OpcUaClientConfigImpl(
            UaStackClientConfig stackClientConfig,
            LocalizedText applicationName,
            String applicationUri,
            String productUri,
            Supplier<String> sessionName,
            String[] sessionLocaleIds,
            UInteger sessionTimeout,
            UInteger maxResponseMessageSize,
            UInteger maxPendingPublishRequests,
            IdentityProvider identityProvider,
            UInteger keepAliveFailuresAllowed,
            UInteger keepAliveInterval,
            UInteger keepAliveTimeout,
            double subscriptionWatchdogMultiplier
        ) {

            this.stackClientConfig = stackClientConfig;
            this.applicationName = applicationName;
            this.applicationUri = applicationUri;
            this.productUri = productUri;
            this.sessionName = sessionName;
            this.sessionLocaleIds = sessionLocaleIds;
            this.sessionTimeout = sessionTimeout;
            this.maxResponseMessageSize = maxResponseMessageSize;
            this.maxPendingPublishRequests = maxPendingPublishRequests;
            this.identityProvider = identityProvider;
            this.keepAliveFailuresAllowed = keepAliveFailuresAllowed;
            this.keepAliveInterval = keepAliveInterval;
            this.keepAliveTimeout = keepAliveTimeout;
            this.subscriptionWatchdogMultiplier = subscriptionWatchdogMultiplier;
        }

        @Override
        public LocalizedText getApplicationName() {
            return applicationName;
        }

        @Override
        public String getApplicationUri() {
            return applicationUri;
        }

        @Override
        public String getProductUri() {
            return productUri;
        }

        @Override
        public Supplier<String> getSessionName() {
            return sessionName;
        }

        @Override
        public String[] getSessionLocaleIds() {
            return sessionLocaleIds;
        }

        @Override
        public UInteger getSessionTimeout() {
            return sessionTimeout;
        }

        @Override
        public UInteger getMaxResponseMessageSize() {
            return maxResponseMessageSize;
        }

        @Override
        public UInteger getMaxPendingPublishRequests() {
            return maxPendingPublishRequests;
        }

        @Override
        public IdentityProvider getIdentityProvider() {
            return identityProvider;
        }

        @Override
        public UInteger getKeepAliveFailuresAllowed() {
            return keepAliveFailuresAllowed;
        }

        @Override
        public UInteger getKeepAliveInterval() {
            return keepAliveInterval;
        }

        @Override
        public UInteger getKeepAliveTimeout() {
            return keepAliveTimeout;
        }

        @Override
        public double getSubscriptionWatchdogMultiplier() {
            return subscriptionWatchdogMultiplier;
        }

        @Override
        public EndpointDescription getEndpoint() {
            return stackClientConfig.getEndpoint();
        }

        @Override
        public Optional<KeyPair> getKeyPair() {
            return stackClientConfig.getKeyPair();
        }

        @Override
        public Optional<X509Certificate> getCertificate() {
            return stackClientConfig.getCertificate();
        }

        @Override
        public Optional<X509Certificate[]> getCertificateChain() {
            return stackClientConfig.getCertificateChain();
        }

        @Override
        public ClientCertificateValidator getCertificateValidator() {
            return stackClientConfig.getCertificateValidator();
        }

        @Override
        public EncodingLimits getEncodingLimits() {
            return stackClientConfig.getEncodingLimits();
        }

        @Override
        public UInteger getChannelLifetime() {
            return stackClientConfig.getChannelLifetime();
        }

        @Override
        public ExecutorService getExecutor() {
            return stackClientConfig.getExecutor();
        }

        @Override
        public ScheduledExecutorService getScheduledExecutor() {
            return stackClientConfig.getScheduledExecutor();
        }

        @Override
        public NioEventLoopGroup getEventLoop() {
            return stackClientConfig.getEventLoop();
        }

        @Override
        public HashedWheelTimer getWheelTimer() {
            return stackClientConfig.getWheelTimer();
        }

        @Override
        public UInteger getConnectTimeout() {
            return stackClientConfig.getConnectTimeout();
        }

        @Override
        public UInteger getAcknowledgeTimeout() {
            return stackClientConfig.getAcknowledgeTimeout();
        }

        @Override
        public UInteger getRequestTimeout() {
            return stackClientConfig.getRequestTimeout();
        }

    }

}
