/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 org.apache.sshd.common.util.closeable;

import java.util.concurrent.atomic.AtomicReference;

import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.future.DefaultCloseFuture;
import org.apache.sshd.common.future.SshFuture;
import org.apache.sshd.common.future.SshFutureListener;

/**
 * Provides some default implementations
 *
 * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
 */
public abstract class AbstractCloseable extends IoBaseCloseable {

    public enum State {
        Opened, Graceful, Immediate, Closed
    }

    /**
     * Lock object for this session state
     */
    protected final Object lock = new Object();

    /**
     * State of this object
     */
    protected final AtomicReference<AbstractCloseable.State> state = new AtomicReference<>(State.Opened);

    /**
     * A future that will be set 'closed' when the object is actually closed
     */
    protected final CloseFuture closeFuture = new DefaultCloseFuture(lock);

    protected AbstractCloseable() {
        this("");
    }

    protected AbstractCloseable(String discriminator) {
        super(discriminator);
    }

    @Override
    public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
        closeFuture.addListener(listener);
    }

    @Override
    public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
        closeFuture.removeListener(listener);
    }

    @Override
    public CloseFuture close(boolean immediately) {
        if (immediately) {
            if (state.compareAndSet(State.Opened, State.Immediate)
                    || state.compareAndSet(State.Graceful, State.Immediate)) {
                if (log.isDebugEnabled()) {
                    log.debug("close({}) Closing immediately", this);
                }
                preClose();
                doCloseImmediately();
                if (log.isDebugEnabled()) {
                    log.debug("close({})[Immediately] closed", this);
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("close({})[Immediately] state already {}", this, state.get());
                }
            }
        } else {
            if (state.compareAndSet(State.Opened, State.Graceful)) {
                if (log.isDebugEnabled()) {
                    log.debug("close({}) Closing gracefully", this);
                }
                preClose();
                SshFuture<CloseFuture> grace = doCloseGracefully();
                if (grace != null) {
                    grace.addListener(future -> {
                        if (state.compareAndSet(State.Graceful, State.Immediate)) {
                            doCloseImmediately();
                            if (log.isDebugEnabled()) {
                                log.debug("close({}][Graceful] - operationComplete() closed", AbstractCloseable.this);
                            }
                        }
                    });
                } else {
                    if (state.compareAndSet(State.Graceful, State.Immediate)) {
                        doCloseImmediately();
                        if (log.isDebugEnabled()) {
                            log.debug("close({})[Graceful] closed", this);
                        }
                    }
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("close({})[Graceful] state already {}", this, state.get());
                }
            }
        }
        return closeFuture;
    }

    @Override
    public boolean isClosed() {
        return state.get() == State.Closed;
    }

    @Override
    public boolean isClosing() {
        return state.get() != State.Opened;
    }

    /**
     * preClose is guaranteed to be called before doCloseGracefully or doCloseImmediately.
     * When preClose() is called, isClosing() == true
     */
    protected void preClose() {
        // nothing
    }

    protected CloseFuture doCloseGracefully() {
        return null;
    }

    /**
     * <P>doCloseImmediately is called once and only once
     * with state == Immediate</P>
     *
     * <P>Overriding methods should always call the base implementation.
     * It may be called concurrently while preClose() or doCloseGracefully is executing</P>
     */
    protected void doCloseImmediately() {
        closeFuture.setClosed();
        state.set(State.Closed);
    }

    protected Builder builder() {
        return new Builder(lock);
    }
}