/*
 * Copyright 2015
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inductiveautomation.opcua.sdk.client.fsm.states;

import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;

import com.inductiveautomation.opcua.sdk.client.OpcUaClient;
import com.inductiveautomation.opcua.sdk.client.api.UaSession;
import com.inductiveautomation.opcua.sdk.client.fsm.SessionState;
import com.inductiveautomation.opcua.sdk.client.fsm.SessionStateContext;
import com.inductiveautomation.opcua.sdk.client.fsm.SessionStateEvent;
import com.inductiveautomation.opcua.stack.client.UaTcpStackClient;
import com.inductiveautomation.opcua.stack.core.types.builtin.ByteString;
import com.inductiveautomation.opcua.stack.core.types.structured.CreateSessionRequest;
import com.inductiveautomation.opcua.stack.core.types.structured.CreateSessionResponse;
import com.inductiveautomation.opcua.stack.core.util.NonceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CreatingSession implements SessionState {

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

    private final AtomicReference<CreateSessionResponse> response = new AtomicReference<>();

    private final CompletableFuture<UaSession> sessionFuture;

    public CreatingSession(CompletableFuture<UaSession> sessionFuture) {
        this.sessionFuture = sessionFuture;
    }

    @Override
    public void activate(SessionStateEvent event, SessionStateContext context) {
        OpcUaClient client = context.getClient();
        UaTcpStackClient stackClient = client.getStackClient();

        String serverUri = stackClient.getEndpoint().flatMap(e -> {
            String gatewayServerUri = e.getServer().getGatewayServerUri();
            if (gatewayServerUri != null && !gatewayServerUri.isEmpty()) {
                return Optional.ofNullable(e.getServer().getApplicationUri());
            } else {
                return Optional.empty();
            }
        }).orElse(null);

        CreateSessionRequest request = new CreateSessionRequest(
                client.newRequestHeader(),
                stackClient.getApplication(),
                serverUri,
                stackClient.getEndpointUrl(),
                client.getConfig().getSessionName().get(),
                NonceUtil.generateNonce(16),
                ByteString.NULL_VALUE,
                client.getConfig().getSessionTimeout(),
                client.getConfig().getMaxResponseMessageSize());

        CompletableFuture<CreateSessionResponse> future = stackClient.sendRequest(request);

        future.whenComplete((r, ex) -> {
            if (r != null) {
                logger.debug("CreateSession succeeded, id={}, timeout={}",
                        r.getSessionId(), r.getRevisedSessionTimeout());

                response.set(r);
                context.handleEvent(SessionStateEvent.CREATE_SESSION_SUCCEEDED);
            } else {
                logger.debug("CreateSession failed: {}", ex.getMessage(), ex);

                context.handleEvent(SessionStateEvent.CREATE_SESSION_FAILED);
            }
        });
    }

    @Override
    public SessionState transition(SessionStateEvent event, SessionStateContext context) {
        switch (event) {
            case CREATE_SESSION_FAILED:
                return new Inactive();

            case CREATE_SESSION_SUCCEEDED:
                return new ActivatingSession(sessionFuture, response.get());
        }

        return this;
    }

    @Override
    public CompletableFuture<UaSession> sessionFuture() {
        return sessionFuture;
    }

}

@@ -1160,32 +1160,319 @@
 TcpStackClient;%0A
+import com.inductiveautomation.opcua.stack.core.UaException;%0Aimport com.inductiveautomation.opcua.stack.core.channel.ClientSecureChannel;%0Aimport com.inductiveautomation.opcua.stack.core.security.SecurityAlgorithm;%0Aimport com.inductiveautomation.opcua.stack.core.security.SecurityPolicy;%0A
 import com.induc
@@ -1529,16 +1529,16 @@
 String;%0A
-
 import c
@@ -2443,16 +2443,92 @@
 lient();
+%0A        ClientSecureChannel secureChannel = stackClient.getSecureChannel();
 %0A%0A      
@@ -2916,24 +2916,446 @@
 lse(null);%0A%0A
+%0A        SecurityAlgorithm algorithm = secureChannel.getSecurityPolicy()%0A                .getAsymmetricEncryptionAlgorithm();%0A        ByteString clientNonce = NonceUtil.generateNonce(algorithm);%0A%0A        ByteString clientCertificate;%0A        try %7B%0A            clientCertificate = secureChannel.getLocalCertificateBytes();%0A        %7D catch (UaException e) %7B%0A            clientCertificate = ByteString.NULL_VALUE;%0A        %7D%0A%0A
         Crea
@@ -3648,35 +3648,19 @@
     
-NonceUtil.generate
+client
 Nonce
-(16)
 ,%0A  
@@ -3673,37 +3673,33 @@
         
-ByteString.NULL_VALUE
+clientCertificate
 ,%0A      

Set the nonce and certificate when creating a session