/*
 * Copyright 2010, 2011, 2012 mapsforge.org
 * Copyright 2013 Hannes Janetzek
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.oscim.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An abstract base class for threads which support pausing and resuming.
 */
public abstract class PausableThread extends Thread {
    private final static Logger log = LoggerFactory.getLogger(PausableThread.class);
    private final static boolean dbg = false;

    private boolean mPausing = true;
    private boolean mRunning = true;
    private boolean mShouldPause = false;
    private boolean mShouldStop = false;

    /**
     * Causes the current thread to wait until this thread is pausing.
     */
    public final void awaitPausing() {
        synchronized (this) {

            while (!isPausing()) {

                if (dbg)
                    log.debug("Await Pause {}", getThreadName());

                try {
                    wait(100);
                } catch (InterruptedException e) {
                    /* restore the interrupted status */
                    this.interrupt();
                }
            }
        }
    }

    public synchronized void finish() {
        if (!mRunning)
            return;

        log.debug("Finish {}", getThreadName());

        mShouldStop = true;
        this.interrupt();
    }

    /**
     * @return true if this thread is currently pausing, false otherwise.
     */
    public final synchronized boolean isPausing() {
        return mPausing;
    }

    /**
     * The thread should stop its work temporarily.
     */
    public final synchronized void pause() {
        if (!mShouldPause) {
            mShouldPause = true;
            this.interrupt();
        }
    }

    /**
     * The paused thread should continue with its work.
     */
    public final synchronized void proceed() {
        if (mShouldPause) {
            mShouldPause = false;
            notify();
        }
    }

    public final synchronized boolean isCanceled() {
        return mShouldPause;
    }

    @Override
    public final void run() {
        mRunning = true;
        setName(getThreadName());
        setPriority(getThreadPriority());

        O:
        while (!mShouldStop) {

            synchronized (this) {
                if (mShouldStop)
                    break;

                while ((mShouldPause || !hasWork())) {
                    try {
                        if (mShouldPause) {
                            mPausing = true;

                            if (dbg)
                                log.debug("Pausing: {}",
                                        getThreadName());
                        }

                        wait();

                    } catch (InterruptedException e) {
                        if (dbg)
                            log.debug("Interrupted {} {}:{}",
                                    getThreadName(),
                                    mShouldPause,
                                    mShouldStop);

                        if (mShouldStop)
                            break O;
                    }
                }

                if (mPausing) {
                    mPausing = false;
                    afterPause();
                }
            }

            try {
                doWork();
            } catch (InterruptedException e) {
                if (dbg)
                    log.debug("Interrupted {} {}:{}",
                            getThreadName(),
                            mShouldPause,
                            mShouldStop);

            }
        }

        log.debug("Done {}", getThreadName());

        mPausing = true;
        mRunning = false;

        afterRun();
    }

    /**
     * Called once when this thread continues to work after a pause. The default
     * implementation is empty.
     */
    protected void afterPause() {
        // do nothing
    }

    /**
     * Called once at the end of the {@link #run()} method. The default
     * implementation is empty.
     */
    protected void afterRun() {
        // do nothing
    }

    /**
     * Called when this thread is not paused and should do its work.
     *
     * @throws InterruptedException if the thread has been interrupted.
     */
    protected abstract void doWork() throws InterruptedException;

    /**
     * @return the name of this thread.
     */
    protected abstract String getThreadName();

    /**
     * @return the priority of this thread. The default value is
     * {@link Thread#NORM_PRIORITY}.
     */
    protected int getThreadPriority() {
        return Thread.NORM_PRIORITY;
    }

    /**
     * @return true if this thread has some work to do, false otherwise.
     */
    protected abstract boolean hasWork();
}
