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

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

import com.google.common.base.Preconditions;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.HashedWheelTimer;
import org.eclipse.milo.opcua.stack.client.security.ClientCertificateValidator;
import org.eclipse.milo.opcua.stack.core.Stack;
import org.eclipse.milo.opcua.stack.core.channel.EncodingLimits;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.jetbrains.annotations.Nullable;

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

public class UaStackClientConfigBuilder {

    private EndpointDescription endpoint;
    private KeyPair keyPair;
    private X509Certificate certificate;
    private X509Certificate[] certificateChain;
    private ClientCertificateValidator certificateValidator = new ClientCertificateValidator.InsecureValidator();

    private ExecutorService executor;
    private ScheduledExecutorService scheduledExecutor;
    private NioEventLoopGroup eventLoop;
    private HashedWheelTimer wheelTimer;

    private EncodingLimits encodingLimits = EncodingLimits.DEFAULT;
    private UInteger connectTimeout = uint(5_000);
    private UInteger acknowledgeTimeout = uint(5_000);
    private UInteger requestTimeout = uint(60_000);
    private UInteger channelLifetime = uint(60 * 60 * 1000);

    public UaStackClientConfigBuilder setEndpoint(EndpointDescription endpoint) {
        this.endpoint = endpoint;
        return this;
    }

    public UaStackClientConfigBuilder setKeyPair(KeyPair keyPair) {
        this.keyPair = keyPair;
        return this;
    }

    public UaStackClientConfigBuilder setCertificate(X509Certificate certificate) {
        this.certificate = certificate;
        return this;
    }

    public UaStackClientConfigBuilder setCertificateChain(X509Certificate[] certificateChain) {
        this.certificateChain = certificateChain;
        return this;
    }

    public UaStackClientConfigBuilder setCertificateValidator(ClientCertificateValidator certificateValidator) {
        this.certificateValidator = certificateValidator;
        return this;
    }

    public UaStackClientConfigBuilder setExecutor(ExecutorService executor) {
        this.executor = executor;
        return this;
    }

    public UaStackClientConfigBuilder setScheduledExecutor(ScheduledExecutorService scheduledExecutor) {
        this.scheduledExecutor = scheduledExecutor;
        return this;
    }

    public UaStackClientConfigBuilder setEventLoop(NioEventLoopGroup eventLoop) {
        this.eventLoop = eventLoop;
        return this;
    }

    public UaStackClientConfigBuilder setWheelTimer(HashedWheelTimer wheelTimer) {
        this.wheelTimer = wheelTimer;
        return this;
    }

    public UaStackClientConfigBuilder setEncodingLimits(EncodingLimits encodingLimits) {
        this.encodingLimits = encodingLimits;
        return this;
    }

    public UaStackClientConfigBuilder setConnectTimeout(UInteger connectTimeout) {
        this.connectTimeout = connectTimeout;
        return this;
    }

    public UaStackClientConfigBuilder setAcknowledgeTimeout(UInteger acknowledgeTimeout) {
        this.acknowledgeTimeout = acknowledgeTimeout;
        return this;
    }

    public UaStackClientConfigBuilder setRequestTimeout(UInteger requestTimeout) {
        this.requestTimeout = requestTimeout;
        return this;
    }

    public UaStackClientConfigBuilder setChannelLifetime(UInteger channelLifetime) {
        this.channelLifetime = channelLifetime;
        return this;
    }

    public UaStackClientConfig build() {
        Preconditions.checkNotNull(endpoint, "endpoint must be non-null");

        if (executor == null) {
            executor = Stack.sharedExecutor();
        }
        if (scheduledExecutor == null) {
            scheduledExecutor = Stack.sharedScheduledExecutor();
        }
        if (eventLoop == null) {
            eventLoop = Stack.sharedEventLoop();
        }
        if (wheelTimer == null) {
            wheelTimer = Stack.sharedWheelTimer();
        }

        return new UaStackClientConfigImpl(
            endpoint,
            keyPair,
            certificate,
            certificateChain,
            certificateValidator,
            encodingLimits,
            executor,
            scheduledExecutor,
            eventLoop,
            wheelTimer,
            connectTimeout,
            acknowledgeTimeout,
            requestTimeout,
            channelLifetime
        );
    }

    static class UaStackClientConfigImpl implements UaStackClientConfig {

        private final EndpointDescription endpoint;
        private final KeyPair keyPair;
        private final X509Certificate certificate;
        private final X509Certificate[] certificateChain;
        private final ClientCertificateValidator certificateValidator;

        private final EncodingLimits encodingLimits;
        private final ExecutorService executor;
        private final ScheduledExecutorService scheduledExecutor;
        private final NioEventLoopGroup eventLoop;
        private final HashedWheelTimer wheelTimer;
        private final UInteger connectTimeout;
        private final UInteger acknowledgeTimeout;
        private final UInteger requestTimeout;
        private final UInteger channelLifetime;

        UaStackClientConfigImpl(
            EndpointDescription endpoint,
            @Nullable KeyPair keyPair,
            @Nullable X509Certificate certificate,
            @Nullable X509Certificate[] certificateChain,
            ClientCertificateValidator certificateValidator,
            EncodingLimits encodingLimits,
            ExecutorService executor,
            ScheduledExecutorService scheduledExecutor,
            NioEventLoopGroup eventLoop,
            HashedWheelTimer wheelTimer,
            UInteger connectTimeout,
            UInteger acknowledgeTimeout,
            UInteger requestTimeout,
            UInteger channelLifetime
        ) {

            this.endpoint = endpoint;
            this.keyPair = keyPair;
            this.certificate = certificate;
            this.certificateChain = certificateChain;
            this.certificateValidator = certificateValidator;
            this.encodingLimits = encodingLimits;
            this.executor = executor;
            this.scheduledExecutor = scheduledExecutor;
            this.eventLoop = eventLoop;
            this.wheelTimer = wheelTimer;
            this.connectTimeout = connectTimeout;
            this.acknowledgeTimeout = acknowledgeTimeout;
            this.requestTimeout = requestTimeout;
            this.channelLifetime = channelLifetime;
        }

        @Override
        public EndpointDescription getEndpoint() {
            return endpoint;
        }

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

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

        @Override
        public Optional<X509Certificate[]> getCertificateChain() {
            if (certificateChain != null) {
                return Optional.of(certificateChain);
            } else {
                if (certificate != null) {
                    return Optional.of(new X509Certificate[]{certificate});
                } else {
                    return Optional.empty();
                }
            }
        }

        @Override
        public ClientCertificateValidator getCertificateValidator() {
            return certificateValidator;
        }

        @Override
        public EncodingLimits getEncodingLimits() {
            return encodingLimits;
        }

        @Override
        public UInteger getChannelLifetime() {
            return channelLifetime;
        }

        @Override
        public ExecutorService getExecutor() {
            return executor;
        }

        @Override
        public ScheduledExecutorService getScheduledExecutor() {
            return scheduledExecutor;
        }

        @Override
        public NioEventLoopGroup getEventLoop() {
            return eventLoop;
        }

        @Override
        public HashedWheelTimer getWheelTimer() {
            return wheelTimer;
        }

        @Override
        public UInteger getConnectTimeout() {
            return connectTimeout;
        }

        @Override
        public UInteger getAcknowledgeTimeout() {
            return acknowledgeTimeout;
        }

        @Override
        public UInteger getRequestTimeout() {
            return requestTimeout;
        }

    }

}
