package com.nifty.cloud.ncs.service.cli;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;

import javax.crypto.SecretKey;

import com.nifty.cloud.ncs.NiftyCloudClientException;
import com.nifty.cloud.ncs.NiftyCloudServiceException;
import com.nifty.cloud.ncs.auth.Credentials;
import com.nifty.cloud.ncs.encrypt.PrivateKeyGenerator;
import com.nifty.cloud.ncs.service.acl.Policy;

/**
 * ニフクラ　ストレージサービス
 * 　コマンドラインインタフェース
 *
 */
public class CommandLineInterface
{
	private static final String NIFS3_PREFIX = "ncss://";
	private String credentialFileName = "credentials.properties";
	private Policy policy = Policy.Private;
	private String bucketName = null;
	private String objectName = null;
	private String copyBucketName = null;
	private String copyObjectName = null;
	private ArrayList<String> localFileName = null;
	private HashMap<String, String> headers = new HashMap<String, String>();
	private String[] parameters = null;
	private boolean isUsingEncryption = false;
	private String uploadId = null;

	private String protocol = "https";
	private int cmdNo;


	/**
	 * コンストラクタ
	 */
	public CommandLineInterface()
	{
	}

	/**
	 * パラメータの解析
	 * @param parameters
	 *  パラメータ
	 */
	private void parseParameters(String[] parameters)
	{
		for (int i=0; i<parameters.length; i++) {
			if (parameters[i].startsWith(NIFS3_PREFIX)) {
				parseBucketName(parameters[i]);
			} else {
				if (localFileName == null) {
					localFileName = new ArrayList<String>();
				}
				localFileName.add(parameters[i]);
			}
		}
	}

	/**
	 * バケット名とオブジェクト名の取得
	 *
	 * @param str
	 *  コマンドライン引数
	 * @throws IllegalArgumentException
	 *  コマンドライン引数が正しくない
	 */
	private void parseBucketName(String str)
	{
		String s = str.replaceFirst(NIFS3_PREFIX, "");
		StringTokenizer st = new StringTokenizer(s, "/");

		int numTokens = st.countTokens();
		if (numTokens <= 1) {
			if (bucketName == null) {
				bucketName = s;
			} else {
				copyBucketName = s;
			}
		} else if (numTokens > 1) {
			if (bucketName != null || objectName != null) {
				if (copyBucketName != null || copyObjectName != null) {
					throw (new IllegalArgumentException("Too many arguments."));
				}
				copyBucketName = st.nextToken();
				copyObjectName = s.replaceFirst(copyBucketName + "/", "");
			} else {
				bucketName = st.nextToken();
				objectName = s.replaceFirst(bucketName + "/", "");
			}
		}
	}

	/**
	 * コマンドライン引数の解析
	 *
	 * @param args
	 *  コマンドライン引数
	 * @throws NiftyCloudClientException
	 * @throws IllegalArgumentException
	 *  コマンドライン引数が正しくない
	 */
	public void parseOptions(String[] args) throws IllegalArgumentException
	{
		if (args.length == 0) {
			throw (new IllegalArgumentException("Invalid argument."));
		}

		/*
		 * オプション判定
		 */
		int i = 0;
		while (args[i].startsWith("-")) {
			if ("-c".equals(args[i])) {
				// 認証用設定ファイル
				i++;
				if (args.length < i+1) {
					throw (new IllegalArgumentException("Invalid argument."));
				}
				this.credentialFileName = args[i];
				i++;
			} else if ("-h".equals(args[i]) || "--help".equals(args[i])) {
				// ヘルプ
				printUsage();
				System.exit(0);
			} else if (args[i].startsWith("--add-header=")) {
				// add header
				String header = args[i].replaceFirst("--add-header=", "");
				StringTokenizer st = new StringTokenizer(header, ":");
				if (st.countTokens() < 2) {
					throw (new IllegalArgumentException("Invalid Parameter: " + args[i]));
				}
				String key = st.nextToken();
				if (headers.containsKey(key)) {
					throw (new IllegalArgumentException("Duplicate Header Key: " + key));
				}
				headers.put(key, header.replaceFirst(key + ":", ""));
				i++;
			} else if ("--http".equals(args[i])) {
				protocol = "http";
				i++;
			} else if ("--encrypt".equals(args[i])) {
				isUsingEncryption = true;
				i++;
			} else if (args[i].startsWith("--upload-id=")) {
				String header = args[i].replaceFirst("--upload-id=", "");
				if (header.length() == 0) {
					throw (new IllegalArgumentException("Invalid Parameter: " + args[i]));
				}
				uploadId = header;
				i++;
			} else {
				throw (new IllegalArgumentException("Invalid Parameter: " + args[i]));
			}

			if (args.length < (i+1)) {
				throw (new IllegalArgumentException("Invalid argument. Must be given a command."));
			}
		}

		/*
		 * コマンド判定
		 */
		List<String> commands = Arrays.asList(new String[] {"mb", "rb", "ls", "la", "put", "get", "del", "cp", "head", "abort"});
		if (commands.contains(args[i]) == false) {
			throw (new IllegalArgumentException("Invalid command. [" + args[i] + "]"));
		}
		cmdNo = commands.indexOf(args[i]);
		i++;

		/*
		 * パラメータの取得
		 */
		int parameterLength = args.length - i;
		parameters = new String[parameterLength];
		for (int j = 0; j < parameterLength; j++) {
			parameters[j] = args[i + j];
		}

		parseParameters(parameters);
		if (cmdNo == commands.indexOf("get") || cmdNo == commands.indexOf("put")) {
			if (localFileName == null) {
				throw (new IllegalArgumentException("Invalid argument. Must be given a local file name."));
			}
		}
	}

	/**
	 * コマンド実行
	 *
	 * @throws NiftyCloudClientException
	 */
	public void execute() throws NiftyCloudClientException, NiftyCloudServiceException
	{
		Command command = null;
		SecretKey privateKey = null;

		try {
			File credentialFile = new File(credentialFileName);

			// 暗号化鍵生成
			if (isUsingEncryption) {
				PrivateKeyGenerator generator = new PrivateKeyGenerator(credentialFile);
				privateKey = generator.generatePrivateKey();
			}

			command = new Command(new Credentials(credentialFile), protocol, privateKey);

		} catch (Exception e) {
			throw (new NiftyCloudClientException(e.getMessage(), e));
		}

		switch (cmdNo) {
		case 0:
			// バケット作成
			validateBucketAndObjectArgument(false);
			command.putBucket(bucketName, policy, null);
			break;
		case 1:
			// バケット削除
			validateBucketAndObjectArgument(false);
			command.deleteBucket(bucketName);
			break;
		case 2:
			// 一覧出力
			if (bucketName != null && bucketName.equals("")) {
				throw (new IllegalArgumentException("Invalid argument. Must be given a bucket name."));
			}
			command.listBucket(bucketName, parameters);
			break;
		case 3:
			// 全オブジェクト一覧
			command.listAllObjects();
			break;
		case 4:
			// オブジェクト アップロード
			validateBucketAndObjectArgument(true);
			command.putObject(bucketName, objectName, localFileName.get(0), policy, headers);
			break;
		case 5:
			// オブジェクト ダウンロード
			validateBucketAndObjectArgument(true);
			command.getObject(bucketName, objectName, localFileName.get(0), headers);
			break;
		case 6:
			// オブジェクト削除
			validateBucketAndObjectArgument(true);
			command.deleteObject(bucketName, objectName);
			break;
		case 7:
			// オブジェクトコピー
			validateBucketAndObjectArgument(true);
			validateCopyBucketAndObjectArgument(true);
			command.copyObject(bucketName, objectName, copyBucketName, copyObjectName, policy, headers);
			break;
		case 8:
			// オブジェクトヘッダー情報取得
			validateBucketAndObjectArgument(true);
			command.headObject(bucketName, objectName);
			break;
		case 9:
			// マルチパートアップロード破棄
			validateBucketAndObjectArgument(true);
			command.abortMultipartUpload(bucketName, objectName, uploadId);
			break;
		default:
			break;
		}
	}

	/**
	 * 引数にバケット名、オブジェクト名が含まれているかどうかの確認
	 *
	 * @param objectRequired
	 * 	オブジェクト名が必須な場合はtrue、そうでなければfalseを設定
	 */
	private void validateBucketAndObjectArgument(boolean objectNameRequired) throws IllegalArgumentException
	{
		if (bucketName == null || bucketName.equals("")) {
			throw (new IllegalArgumentException("Invalid argument. Must be given a bucket name."));
		}
		if (objectNameRequired) {
			if (objectName == null) {
				throw (new IllegalArgumentException("Invalid argument. Must be given a object name."));
			}
		}
	}

	/**
	 * コピー先のバケット名、オブジェクト名が含まれているかどうかの確認
	 *
	 * @param objectNameRequired
	 * @throws IllegalArgumentException
	 */
	private void validateCopyBucketAndObjectArgument(boolean objectNameRequired) throws IllegalArgumentException
	{
		if (copyBucketName == null || copyBucketName.equals("")) {
			throw (new IllegalArgumentException("Invalid argument. Must be given a destination bucket name."));
		}
		if (objectNameRequired) {
			if (copyObjectName == null) {
				throw (new IllegalArgumentException("Invalid argument. Must be given a destination object name."));
			}
		}
	}

	/**
	 * usage出力
	 */
	public void printUsage()
	{
		println("usage: ");
		println("    CommandLineInterface [options] COMMAND [parameters]");
		println("");
		println("options:");
		println("  -h, --help\t\t\tこのコマンドのヘルプ情報を表示し終了します。");
		println("  -c FILE\t\t\t認証キーの設定ファイルを指定します。 デフォルトは'credentials.properties' です。");
		println("  --add-header\t\t\t追加したいHTTPリクエストヘッダを指定します。（複数指定可）");
		println("  --encrypt\t\t\tこのオプションを指定した場合、データが暗号化/復号化されます。");
		println("\t\t\t\t  マルチパートアップロードの場合、このオプションを指定しても暗号化されません。");
		println("\t\t\t\t  このオプションを指定せずにgetコマンドでオブジェクトを取得した場合、復号化は行われません。");
		println("  --upload-id\t\t\tマルチパートアップロードデータ破棄で、データを特定するためにアップロードIDを指定します。");
		println("\t\t\t\tアップロードIDは、デバッグレベルをINFO以上に設定してマルチアップロードした際にログに出力されます。");
		println("");
		println("COMMAND:");
		println("  mb  ncss://<bucket-name>\tバケットを作成します。");
		println("  rb  ncss://<bucket-name>\tバケットを削除します。");
		println("  ls  [ncss://<bucket-name>]\tバケットの一覧、またはバケット内のオブジェクトの一覧を表示します。");
		println("  la  \t\t\t\tすべてのオブジェクトの一覧を表示します。");
		println("  put <local-filename> ncss://<bucket-name>[/<object-name>]");
		println("\t\t\t\t指定ファイルをオブジェクトとして、バケット内に作成します。オブジェクト名の指定が可能です。");
		println("\t\t\t\t一定サイズを超えるファイルの場合は、分割してアップロードされます。");
		println("  put <local-dirname> ncss://<bucket-name>/<object-name>/");
		println("\t\t\t\t指定ディレクトリをオブジェクトとして、バケット内に作成します。オブジェクト名の末尾にスラッシュが必要です。");
		println("\t\t\t\t<local-dirname>の末尾に「/」を付けた場合、そのディレクトリが実際に存在しなくても作成が可能です。");
		println("  get ncss://<bucket-name>/<object-name> <local-filename>");
		println("\t\t\t\tバケット内のオブジェクトを取得します。");
		println("  del ncss://<bucket-name>/<object-name>");
		println("\t\t\t\tバケット内のオブジェクトを削除します。");
		println("  cp ncss://<source-bucket-name>/<source-object-name> ncss://<destination-bucket-name>/<destination-object-name>");
		println("\t\t\t\t指定したオブジェクトを指定した指定したパスにコピーします。");
		println("  head ncss://<bucket-name>/<object-name>");
		println("\t\t\t\t指定したオブジェクトのヘッダー情報一覧を表示します。");
		println("  abort ncss://<bucket-name>/<object-name>");
		println("\t\t\t\t指定したオブジェクトのマルチパートアップロード異常終了時のアップロードデータを破棄します。");
		println("\t\t\t\t--upload-idの指定が無い場合、アップロードデータの破棄は行われません。");
	}

	/**
	 * メッセージ出力
	 *
	 * @param message
	 *  出力メッセージ
	 */
	private void println(String message)
	{
		System.out.println(message);
	}

	/**
	 * @param args
	 *  コマンドライン引数
	 *
	 * Exit code:
	 *    0   成功
	 *   -1   クライアント側エラー
	 *   -2   サーバ側エラー
	 *   -3   引数エラー
	 */
	public static void main(String[] args)
	{
		CommandLineInterface cli = new CommandLineInterface();

		try {
			cli.parseOptions(args);
			cli.execute();
		} catch (NiftyCloudServiceException e) {
			System.out.println("Caught NiftyCloudServiceException which means your request made it "
					+ "NIFCLOUD Storage, but was rejected with an error response for some reason.");
			System.out.println("Error Message:                " + e.getMessage());
			System.out.println("HTTP Status Code:             " + e.getStatusCode());
			System.out.println("Error Code: " + e.getErrorCode());
			System.out.println("Error Type:                   " + e.getErrorType());
			System.out.println("Request ID:                   " + e.getRequestId());
			System.exit(-1);
		} catch (NiftyCloudClientException e) {
	        System.out.println("Caught an NiftyCloudClientException, which means the client encountered "
	                + "a serious internal problem while trying to communicate with NIFCLOUD Storage, "
	                + "such as not being able to access the network.");
	        System.out.println("Error Message: " + e.getMessage());
	        System.exit(-2);
		} catch (IllegalArgumentException e) {
			System.out.println("Error: " + e.getMessage());
			System.out.println();
			cli.printUsage();
			System.exit(-3);
		}

		System.exit(0);
	}
}
