/*
 * Copyright 2004-2014 the Seasar Foundation and the Others.
 *
 * Licensed 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.seasar.extension.timer;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.logging.log4j.LogManager;

/**
 * Timerを扱うクラスです。
 *
 * @author higa
 *
 */
public final class TimeoutManager implements Runnable {

    /** シングルトンのためのインスタンスです。 */
    private static final TimeoutManager INSTANCE = new TimeoutManager();

    /** {@link TimeoutTask}管理用のリストです。 */
    private final LinkedList<TimeoutTask> timeoutTaskList = new LinkedList<>();

    /** Timerのための{@link Thread}です。 */
    private Thread thread = null;

    /**
     * Constructor
     */
    private TimeoutManager() {
        super();
    }

    /**
     * シングルトン用のインスタンスを返します。
     *
     * @return シングルトン用のインスタンス
     */
    public static TimeoutManager getInstance() {
        return INSTANCE;
    }

    /**
     * スレッド取得
     * @return スレッド
     */
    synchronized Thread getThread() {
        return this.thread;
    }

    /**
     * 処理を開始します。
     */
    public synchronized void start() {
        if (this.thread == null) {
            this.thread = new Thread(this, "Seasar2-TimeoutManager");
            this.thread.setDaemon(true);
            this.thread.start();
        }
    }

    /**
     * 処理を停止します。
     */
    public synchronized void stop() {
        if (this.thread != null) {
            this.thread.interrupt();
            this.thread = null;
        }
    }

    /**
     * スレッドに割り込みを行い、終了するまで待機します。
     *
     * @param timeoutMillis 待機する時間(ミリ秒単位)
     * @return スレッドが終了した場合は<code>true</code>
     */
    public boolean stop(final long timeoutMillis) {
        final Thread th;
        synchronized (this) {
            th = this.thread;
            if (th == null) {
                return true;
            }
            this.thread = null;
        }
        th.interrupt();
        try {
            th.join(timeoutMillis);
        } catch (final InterruptedException e) {
            LogManager.getLogger().info(e.getMessage());
        }
        return !th.isAlive();
    }

    /**
     * 管理している {@link TimeoutTask}をクリアします。
     */
    public synchronized void clear() {
        this.timeoutTaskList.clear();
    }

    /**
     * {@link TimeoutTarget}を追加します。
     *
     * @param timeoutTarget TimeoutTarget
     * @param timeout タイムアウト
     * @param permanent フラグ
     * @return {@link TimeoutTask}
     */
    public synchronized TimeoutTask addTimeoutTarget(final TimeoutTarget timeoutTarget,
            final int timeout, final boolean permanent) {
        final TimeoutTask task = new TimeoutTask(timeoutTarget, timeout, permanent);
        this.timeoutTaskList.addLast(task);
        if (this.timeoutTaskList.size() == 1) {
            start();
        }
        return task;
    }

    /**
     * 管理している {@link TimeoutTask}の数を返します。
     *
     * @return 管理している {@link TimeoutTask}の数
     */
    public synchronized int getTimeoutTaskCount() {
        return this.timeoutTaskList.size();
    }

    /**
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
        boolean interrupted = false;
        for (;;) {
            final List<TimeoutTask> expiredTask = getExpiredTask();
            for (final TimeoutTask task : expiredTask) {
                task.expired();
                if (task.isPermanent()) {
                    task.restart();
                }
            }
            if (interrupted || isInterrupted() || stopIfLeisure()) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (final InterruptedException e) {
                interrupted = true;
                LogManager.getLogger().info(e.getMessage());
            }
        }
    }

    /**
     * test interrupted
     * @return interrupted or not
     */
    private synchronized boolean isInterrupted() {
        return this.thread == null || this.thread.isInterrupted();
    }

    /**
     * 期限の切れた {@link TimeoutTask}のリストを返します。
     *
     * @return 期限の切れた {@link TimeoutTask}のリスト
     */
    protected synchronized List<TimeoutTask> getExpiredTask() {
        final List<TimeoutTask> expiredTask = new ArrayList<>();
        for (final Iterator<TimeoutTask> it = this.timeoutTaskList.iterator(); it.hasNext();) {
            final TimeoutTask task = it.next();
            if (task.isCanceled()) {
                it.remove();
            } else if (!task.isStopped() && task.isExpired()) {
                expiredTask.add(task);
                if (!task.isPermanent()) {
                    it.remove();
                }
            }
        }
        return expiredTask;
    }

    /**
     * 管理しているタスクが無いなら処理を停止します。
     *
     * @return 停止したかどうか
     */
    protected synchronized boolean stopIfLeisure() {
        if (this.timeoutTaskList.isEmpty()) {
            this.thread = null;
            return true;
        }
        return false;
    }
}
