/*
 * Copyright 2004-2015 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.List;

import org.seasar.framework.util.SLinkedList;

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

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

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

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

    private TimeoutManager() {
    }

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

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

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

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

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

    /**
     * {@link TimeoutTarget}を追加します。
     * 
     * @param 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);
        timeoutTaskList.addLast(task);
        if (timeoutTaskList.size() == 1) {
            start();
        }
        return task;
    }

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

    public void run() {
        boolean interrupted = false;
        for (;;) {
            final List expiredTask = getExpiredTask();
            for (final Iterator it = expiredTask.iterator(); it.hasNext();) {
                final TimeoutTask task = (TimeoutTask) it.next();
                task.expired();
                if (task.isPermanent()) {
                    task.restart();
                }
            }
            if (interrupted || thread.isInterrupted() || stopIfLeisure()) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (final InterruptedException e) {
                interrupted = true;
            }
        }
    }

    /**
     * 期限の切れた {@link TimeoutTask}のリストを返します。
     * 
     * @return 期限の切れた {@link TimeoutTask}のリスト
     */
    protected synchronized List getExpiredTask() {
        final List expiredTask = new ArrayList();
        try {
            if (timeoutTaskList == null || timeoutTaskList.isEmpty()) {
                return expiredTask;
            }
        } catch (NullPointerException e) {
            return expiredTask;
        }
        for (SLinkedList.Entry e = timeoutTaskList.getFirstEntry(); e != null; e = e
                .getNext()) {
            final TimeoutTask task = (TimeoutTask) e.getElement();
            if (task.isCanceled()) {
                e.remove();
                continue;
            }
            if (task.isStopped()) {
                continue;
            }
            if (task.isExpired()) {
                expiredTask.add(task);
                if (!task.isPermanent()) {
                    e.remove();
                }
            }
        }
        return expiredTask;
    }

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

}
