package com.nifty.cloud.ncs.transfer;

import java.io.File;
import java.io.InputStream;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
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.service.StorageClient;
import com.nifty.cloud.ncs.service.StorageHeadersImpl;
import com.nifty.cloud.ncs.service.acl.Policy;


/**
 * ファイルアップロードを管理するクラスです。
 * <p>
 * 使用例:
 * <pre>
 * StorageClient client = new StorageClient(new Credentials(accessKey, secretKey));
 * TransferManager tm = new TransferManager(client);
 * UploadMonitor monitor = tm.upload(bucketName, objectName, file, policy);
 *
 * // 処理が終了するまで待機します
 * while (monitor.isDone() == false) {
 * 	System.out.println("Transfer:" + monitor.getDescription());
 * 	System.out.println("  - Status: " + monitor.getState());
 * 	Thread.sleep(500);
 * }
 *
 * // 使わなくなったら必ず終了してください
 * tm.shutdownNow();
 * </pre>
 */
public class TransferManager
{
	/** 最大スレッド数のデフォルト値 */
	private static final int DEFAULT_MAX_THREADS = 10;

	/** ストレージクライアント */
	private StorageClient client;

	/** Manager設定 */
	private TransferManagerConfiguration configuration;

	/** スレッドプール */
	private ThreadPoolExecutor threadPool;

	/**
	 * ストレージクライアントを指定してTransferManagerを構築します。
	 *
	 * @param client
	 *  ストレージクライアント
	 */
	public TransferManager(StorageClient client)
	{
		this(client, null, null);
	}

	/**
	 * ストレージクライアントと最大スレッド数を指定してTransferManagerを構築します。
	 *
	 * @param client
	 *  ストレージクライアント
	 * @param maxThreads
	 *  最大スレッド数
	 */
	public TransferManager(StorageClient client, int maxThreads)
	{
		this(client, null, maxThreads);
	}

	/**
	 * ストレージクライアントとスレッドプールを指定してTransferManagerを構築します。
	 *
	 * @param client
	 *  クライアント
	 * @param threadPool
	 *  スレッドプール
	 */
	public TransferManager(StorageClient client, ThreadPoolExecutor threadPool)
	{
		this(client, null, threadPool);
	}

	/**
	 * ストレージクライアント、Manager設定、スレッドプールを指定してTransferManagerを構築します。
	 *
	 * @param client
	 *  ストレージクライアント
	 * @param configuration
	 *  Manager設定
	 * @param threadPool
	 *  スレッドプール
	 */
	public TransferManager(StorageClient client, TransferManagerConfiguration configuration, ThreadPoolExecutor threadPool)
	{
		this(client, configuration);
		this.threadPool = (threadPool == null) ? createExecutorService() : threadPool;
	}

	/**
	 * ストレージクライアント、Manager設定、最大スレッド数を指定してTransferManagerを構築します。
	 *
	 * @param client
	 *  ストレージクライアント
	 * @param configuration
	 *  Manager設定
	 * @param maxThreads
	 *  最大スレッド数
	 */
	public TransferManager(StorageClient client, TransferManagerConfiguration configuration, int maxThreads)
	{
		this(client, configuration);
		this.threadPool = createExecutorService(maxThreads);
	}

	/**
	 * ストレージクライアント、Manager設定を指定してTransferManagerを構築します。
	 * このコンストラクタは内部でのみ使用されます。
	 *
	 * @param client
	 * @param configuration
	 */
	private TransferManager(StorageClient client, TransferManagerConfiguration configuration)
	{
		this.client = client;
		this.configuration = (configuration == null) ? new TransferManagerConfiguration() : configuration;
	}

	/**
	 * Manager設定を取得します。
	 *
	 * @return Manager設定
	 */
	public TransferManagerConfiguration getConfiguration()
	{
		return (configuration);
	}

	/**
	 * ストレージクライアントを取得します。
	 *
	 * @return ストレージクライアント
	 */
	public StorageClient getStorageClient()
	{
		return (client);
	}

	/**
	 * 指定したストリームデータをアップロードします。
	 *
	 * @param bucketName
	 *  バケット名
	 * @param objectName
	 *  オブジェクト名
	 * @param input
	 *  オブジェクトのボディとなる入力ストリーム
	 * @param contentLength
	 *  データサイズ
	 * @param contentType
	 *  コンテンツタイプ
	 * @param policy
	 *  オブジェクトのACL(privateのみ指定可能)
	 *
	 * @return アップロード状況を監視するモニター
	 * @throws NiftyCloudServiceException
	 * @throws NiftyCloudClientException
	 */
	public UploadMonitor upload(final String bucketName, final String objectName, final InputStream input, long contentLength, String contentType, Policy policy)
	throws NiftyCloudServiceException, NiftyCloudClientException
	{
		return (upload(new NcsObject(bucketName, objectName), input, contentLength, contentType, policy));
	}

	/**
	 * 指定したファイルをアップロードします。
	 *
	 * @param bucketName
	 *  バケット名
	 * @param objectName
	 *  オブジェクト名
	 * @param file
	 *  ファイル
	 * @param policy
	 *  オブジェクトのACL(privateのみ指定可能)
	 *
	 * @return アップロード状況を監視するモニター
	 * @throws NiftyCloudServiceException
	 * @throws NiftyCloudClientException
	 */
	public UploadMonitor upload(final String bucketName, final String objectName, final File file, Policy policy)
	throws NiftyCloudServiceException, NiftyCloudClientException
	{
		return (upload(new NcsObject(bucketName, objectName), file, policy));
	}

	/**
	 * 指定したファイルをアップロードします。
	 *
	 * @param object
	 *  アップロードするオブジェクト情報を含むNcsObject
	 * @param file
	 *  ファイル
	 * @param policy
	 *  オブジェクトのACL(privateのみ指定可能)
	 *
	 * @return アップロード状況を監視するモニター
	 * @throws NiftyCloudServiceException
	 * @throws NiftyCloudClientException
	 */
	public UploadMonitor upload(final NcsObject object, File file, Policy policy)
	throws NiftyCloudServiceException, NiftyCloudClientException
	{
		if (object == null) {
			throw (new NiftyCloudClientException("NcsObject must not be null."));
		}
		if (file == null) {
			throw (new NiftyCloudClientException("The argument of file must be specified."));
		}

		UploadMonitor monitor = new UploadMonitor(client, configuration, threadPool, object, file);

		return (monitor);
	}

	/**
	 * 指定したストリームデータをアップロードします。
	 *
	 * @param object
	 *  アップロードするオブジェクト情報を含むNcsObject
	 * @param input
	 *  オブジェクトのボディとなる入力ストリーム
	 * @param contentLength
	 *  データサイズ
	 * @param contentType
	 *  コンテンツタイプ
	 * @param policy
	 *  オブジェクトのACL(privateのみ指定可能)
	 *
	 * @return アップロード状況を監視するモニター
	 * @throws NiftyCloudServiceException
	 * @throws NiftyCloudClientException
	 */
	public UploadMonitor upload(NcsObject object, InputStream input, long contentLength, String contentType, Policy policy)
			throws NiftyCloudServiceException, NiftyCloudClientException
	{
		if (object == null) {
			throw (new NiftyCloudClientException("NcsObject must not be null."));
		}
		if (input == null) {
			throw (new NiftyCloudClientException("InputStream must not be null."));
		}

		// Content-Type設定
		if (contentType != null) {
			object.addHeader(StorageHeadersImpl.CONTENT_TYPE, contentType);
		}

		UploadMonitor monitor = new UploadMonitor(client, configuration, threadPool, object, input, contentLength);

		return (monitor);
	}

	/**
	 * スレッドプール、アップロードモニター、ストレージクライアントを終了します。
	 */
	public void shutdownNow()
	{
		threadPool.shutdownNow();
		//UploadMonitor.shutdownNow();
		client.shutdown();
	}

	/**
	 * デフォルトの最大スレッド数を持つThreadPoolExecutorを生成します。
	 *
	 * @return
	 */
	private static ThreadPoolExecutor createExecutorService()
    {
    	return (createExecutorService(DEFAULT_MAX_THREADS));
    }

	/**
	 * 最大スレッド数を指定してThreadPoolExecutorを生成します。
	 *
	 * @param maxThreads
	 * @return
	 */
    private static ThreadPoolExecutor createExecutorService(int maxThreads)
    {
    	ThreadFactory threadFactory = new ThreadFactory() {
    		private int threadCount = 1;

    		public Thread newThread(Runnable r) {
    			Thread thread = new Thread(r);
    			thread.setName("ncss-transfer-manager-task-" + threadCount++);
    			return (thread);
    		}
    	};

        return (ThreadPoolExecutor)Executors.newFixedThreadPool(maxThreads, threadFactory);
    }
}
