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

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;

import com.digitalpetri.strictmachine.Fsm;
import com.digitalpetri.strictmachine.FsmContext;
import org.eclipse.milo.opcua.sdk.client.OpcUaSession;
import org.eclipse.milo.opcua.sdk.client.SessionActivityListener;
import org.eclipse.milo.opcua.stack.client.UaStackClient;
import org.eclipse.milo.opcua.stack.core.util.Unit;

public class SessionFsm {

    static final String LOGGER_NAME = "org.eclipse.milo.opcua.sdk.client.SessionFsm";


    private final List<SessionInitializer> sessionInitializers;
    private final List<SessionActivityListener> sessionActivityListeners;

    private final Fsm<State, Event> fsm;

    SessionFsm(Fsm<State, Event> fsm) {
        this.fsm = fsm;

        sessionInitializers = fsm.getFromContext(ctx -> {
            KEY_SESSION_INITIALIZERS.set(ctx, new SessionInitializers());
            return KEY_SESSION_INITIALIZERS.get(ctx).sessionInitializers;
        });

        sessionActivityListeners = fsm.getFromContext(ctx -> {
            KEY_SESSION_ACTIVITY_LISTENERS.set(ctx, new SessionActivityListeners());
            return KEY_SESSION_ACTIVITY_LISTENERS.get(ctx).sessionActivityListeners;
        });
    }

    public CompletableFuture<OpcUaSession> openSession() {
        Event.OpenSession openSession = new Event.OpenSession();

        fsm.fireEvent(openSession);

        return openSession.future;
    }

    public CompletableFuture<Unit> closeSession() {
        Event.CloseSession closeSession = new Event.CloseSession();

        fsm.fireEvent(closeSession);

        return closeSession.future;
    }

    public CompletableFuture<OpcUaSession> getSession() {
        CompletableFuture<OpcUaSession> future = fsm.getFromContext(ctx -> {
            State state = ctx.currentState();

            if (state == State.Active) {
                SessionFuture sf = KEY_SESSION_FUTURE.get(ctx);

                assert sf != null;

                return sf.future;
            } else {
                return null;
            }
        });

        if (future != null) {
            return future;
        } else {
            // "Slow" path... not activated yet.
            Event.GetSession getSession = new Event.GetSession();

            fsm.fireEvent(getSession);

            return getSession.future;
        }
    }

    public void addInitializer(SessionInitializer initializer) {
        sessionInitializers.add(initializer);
    }

    public void removeInitializer(SessionInitializer initializer) {
        sessionInitializers.remove(initializer);
    }

    public void addActivityListener(SessionActivityListener listener) {
        sessionActivityListeners.add(listener);
    }

    public void removeActivityListener(SessionActivityListener listener) {
        sessionActivityListeners.remove(listener);
    }

    static final FsmContext.Key<Long> KEY_WAIT_TIME =
        new FsmContext.Key<>("delay", Long.class);

    static final FsmContext.Key<ScheduledFuture> KEY_WAIT_FUTURE =
        new FsmContext.Key<>("delayFuture", ScheduledFuture.class);

    static final FsmContext.Key<CloseFuture> KEY_CLOSE_FUTURE =
        new FsmContext.Key<>("closeFuture", CloseFuture.class);

    static final FsmContext.Key<OpcUaSession> KEY_SESSION =
        new FsmContext.Key<>("session", OpcUaSession.class);

    static final FsmContext.Key<SessionFuture> KEY_SESSION_FUTURE =
        new FsmContext.Key<>("sessionFuture", SessionFuture.class);

    static final FsmContext.Key<Long> KEY_KEEP_ALIVE_FAILURE_COUNT =
        new FsmContext.Key<>("keepAliveFailureCount", Long.class);

    static final FsmContext.Key<ScheduledFuture> KEY_KEEP_ALIVE_SCHEDULED_FUTURE =
        new FsmContext.Key<>("keepAliveScheduledFuture", ScheduledFuture.class);

    static final FsmContext.Key<SessionInitializers> KEY_SESSION_INITIALIZERS =
        new FsmContext.Key<>("sessionInitializers", SessionInitializers.class);

    static final FsmContext.Key<SessionActivityListeners> KEY_SESSION_ACTIVITY_LISTENERS =
        new FsmContext.Key<>("sessionActivityListeners", SessionActivityListeners.class);

    static class CloseFuture {
        final CompletableFuture<Unit> future = new CompletableFuture<>();
    }

    static class SessionFuture {
        final CompletableFuture<OpcUaSession> future = new CompletableFuture<>();
    }

    static class SessionInitializers {
        private SessionInitializers() {}

        final List<SessionInitializer> sessionInitializers = new CopyOnWriteArrayList<>();
    }

    static class SessionActivityListeners {
        private SessionActivityListeners() {}

        final List<SessionActivityListener> sessionActivityListeners = new CopyOnWriteArrayList<>();
    }


    public interface SessionInitializer {

        CompletableFuture<Unit> initialize(UaStackClient stackClient, OpcUaSession session);

    }

}
