/*
 * Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.util;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * A method call that is executed in a separate thread. If the method throws an
 * exception, it is wrapped in a RuntimeException.
 */
public abstract class Task implements Runnable {

    private static final AtomicInteger counter = new AtomicInteger();

    /**
     * A flag indicating the get() method has been called.
     */
    public volatile boolean stop;

    /**
     * The result, if any.
     */
    private volatile Object result;

    private volatile boolean finished;

    private Thread thread;

    private volatile Exception ex;

    /**
     * The method to be implemented.
     *
     * @throws Exception any exception is wrapped in a RuntimeException
     */
    public abstract void call() throws Exception;

    @Override
    public void run() {
        try {
            call();
        } catch (Exception e) {
            this.ex = e;
        }
        finished = true;
    }

    /**
     * Start the thread.
     *
     * @return this
     */
    public Task execute() {
        return execute(getClass().getName() + ":" + counter.getAndIncrement());
    }

    /**
     * Start the thread.
     *
     * @param threadName the name of the thread
     * @return this
     */
    public Task execute(String threadName) {
        thread = new Thread(this, threadName);
        thread.setDaemon(true);
        thread.start();
        return this;
    }

    /**
     * Calling this method will set the stop flag and wait until the thread is
     * stopped.
     *
     * @return the result, or null
     * @throws RuntimeException if an exception in the method call occurs
     */
    public Object get() {
        Exception e = getException();
        if (e != null) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Whether the call method has returned (with or without exception).
     *
     * @return true if yes
     */
    public boolean isFinished() {
        return finished;
    }

    /**
     * Get the exception that was thrown in the call (if any).
     *
     * @return the exception or null
     */
    public Exception getException() {
        join();
        if (ex != null) {
            return ex;
        }
        return null;
    }

    /**
     * Stop the thread and wait until it is no longer running. Exceptions are
     * ignored.
     */
    public void join() {
        stop = true;
        if (thread == null) {
            throw new IllegalStateException("Thread not started");
        }
        try {
            thread.join();
        } catch (InterruptedException e) {
            // ignore
        }
    }

}
