package com.nifty.cloud.ncs.transfer;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;

import com.nifty.cloud.ncs.NiftyCloudClientException;
import com.nifty.cloud.ncs.NiftyCloudServiceException;
import com.nifty.cloud.ncs.service.NcsObject;
import com.nifty.cloud.ncs.transfer.Transfer.TransferState;

/**
 * 転送監視用の抽象クラスです。
 * @param <T>
 * @param <V>
 */
public abstract class AbstractTransferMonitor<T, V> implements TransferMonitor, Callable<T>
{
	private ScheduledExecutorService timedThreadPool;
	protected final Transfer transfer;
	protected ExecutorService threadPool;
	protected Future<T> nextFuture;
	protected List<Future<V>> futures;

	/**
	 * NcsObjectを指定してAbstractTransferMonitorを構築します。
	 *
	 * @param object
	 *  NcsObject
	 */
	protected AbstractTransferMonitor(NcsObject object)
	{
		String description = String.format("Uploading to %s/%s", object.getBucketName(), object.getObjectName());
	    this.transfer = new Transfer(description);
	}

	@Override
	public final synchronized boolean isDone()
	{
		shutdownIfDone();
		return (transfer.isDone());
	}

	@Override
	public final TransferState getState()
	{
		shutdownIfDone();
		return (transfer.getState());
	}

	@Override
	public String getDescription()
	{
		return (transfer.getDescription());
	}

	/**
	 * スケジューリングされたThreadPoolEcecutorを取得します。
	 *
	 * @return
	 */
	protected ScheduledExecutorService getDelayExecutor()
	{
		if ( timedThreadPool == null || timedThreadPool.isShutdown() )
			timedThreadPool = new ScheduledThreadPoolExecutor(1);
		return (timedThreadPool);
	}

	/**
	 * 転送処理が終了している場合はスレッドプールを終了します。
	 */
	public void shutdownIfDone()
	{
		if (transfer.isDone() && timedThreadPool != null && timedThreadPool.isShutdown() == false) {
			timedThreadPool.shutdownNow();
		}
	}

	/**
	 * 処理が完了するまで待機します。
	 *
	 * @throws NiftyCloudClientException
	 * @throws NiftyCloudServiceException
	 * @throws InterruptedException
	 */
	public void waitForCompletion()
    		throws NiftyCloudClientException, NiftyCloudServiceException, InterruptedException
    {
    	try {
    		Object result = null;
    		while (!isDone() || result == null) {
    			Future<?> f = nextFuture;
    			result = (Object)f.get();
    		}
    	} catch (ExecutionException e) {
    		throw (getNiftyCloudException(e));
    	}
    }

	/**
     * 転送が完了するまで待機します。
     * 完了した場合は結果が返却されます。
     *
     * @return 転送結果
     * @throws NiftyCloudClientException
     * @throws NiftyCloudServiceException
     * @throws InterruptedException
     */
    @SuppressWarnings("unchecked")
	public T waitForResult()
    		throws NiftyCloudClientException, NiftyCloudServiceException, InterruptedException
    {
    	try {
    		T result = null;
    		while (!isDone() || result == null) {
    			Future<?> f = nextFuture;
    			result = (T)f.get();
    		}
    		return (result);

    	} catch (ExecutionException e) {
    		e.printStackTrace();
    		throw (getNiftyCloudException(e));
    	}
    }

	/**
	 * 処理が完了するか、エラーが発生するまで待機します。<br>
	 * エラーが発生した場合は発生した例外、そうでなければ<code>null</code>を返却します。
	 *
	 * @return NiftyCloudClientException
	 * @throws InterruptedException
	 */
	public NiftyCloudClientException waitForException() throws InterruptedException
	{
		try {
			while (!isDone()) {
				nextFuture.get();
			}
			nextFuture.get();

			return (null);

		} catch (ExecutionException e) {
			return (getNiftyCloudException(e));
		}
	}

	/**
	 * {@link ExecutionException}でラップされた例外を{@link NiftyCloudClientException}
	 * として返却します。
	 *
	 * @param e
	 * @return
	 */
	protected NiftyCloudClientException getNiftyCloudException(ExecutionException e)
	{
		Throwable t = e.getCause();
		if (t instanceof NiftyCloudClientException) {
			return ((NiftyCloudClientException)t);
		}
		return (new NiftyCloudClientException("Unable to complete transfer: " + t.getMessage(), t));
	}

	/**
	 * 途中で処理失敗した場合にキャンセルする場合に使用します。
	 * 実行待ちのタスクを削除し、現在実行中のタスクの終了を待ちます。
	 *
	 */
	protected void cancel()
	{
		List<Future<V>> startedTask = new ArrayList<Future<V>>();
		if (futures != null) {
			for (Future<V> f : futures) {
				if ( ((ThreadPoolExecutor) threadPool).remove((Runnable) f) == false) {
					startedTask.add(f);
				}
			}

			for (Future<V> f : startedTask) {
				try {
					f.get();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * このメソッドはこのクラスを継承するクラス内で実装される必要があります。
	 */
	@Override
	public abstract T call() throws Exception;

}
