/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.channel.cc.commons.spring;

import static java.util.Objects.requireNonNull;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;

/**
 * Basic abstract implementation of {@link Application}.
 */
public abstract class AbstractApplication implements Application {
    private final CountDownLatch shutdownLatch = new CountDownLatch(1);

    private final Queue<ApplicationException> exceptions =
        new ConcurrentLinkedQueue<>();

    @Override
    public final void run(String[] args) throws ApplicationException {
        try {
            doRun(args);
        } catch (ApplicationException e) {
            exceptions.add(e);
        }
        checkNoExceptions();
    }

    @Override
    public final void shutdown() {
        shutdownLatch.countDown();
    }

    @Override
    public final void shutdown(Throwable e) {
        requireNonNull(e, "Exception is null.");
        ApplicationException exception;
        if (e instanceof ApplicationException) {
            exception = (ApplicationException) e;
        } else {
            exception = new ApplicationException(
                "Application is shutdown abnormally.", e);
        }
        exceptions.offer(exception);
        shutdownLatch.countDown();
    }

    /**
     * Runs the application. Implementation can use {@link #isShutdown()} and
     * {@link #waitForShutdown()} to handle shutdown requests.
     * 
     * @param args
     * @throws ApplicationException
     *             failed to run the application.
     */
    protected abstract void doRun(String[] args)
            throws ApplicationException;

    /**
     * Returns whether the application is shutdown.
     * 
     * @return the application is shutdown.
     */
    protected final boolean isShutdown() {
        return shutdownLatch.getCount() == 0;
    }

    /**
     * Waits for shutdown.
     * 
     * @throws ApplicationException
     *             failed to wait for shutdown.
     */
    protected final void waitForShutdown() throws ApplicationException {
        try {
            shutdownLatch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new ApplicationException("Failed to wait for shutdown.",
                e);
        }
    }

    private void checkNoExceptions() throws ApplicationException {
        ApplicationException first = exceptions.poll();
        if (first != null) {
            ApplicationException next;
            while ((next = exceptions.poll()) != null) {
                first.addSuppressed(next);
            }
            throw first;
        }
    }
}
