package org.opengion.plugin.cloud;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.opengion.fukurou.model.CloudFileOperation;
import org.opengion.fukurou.model.FileOperation;
import org.opengion.fukurou.model.FileOperationInfo;
import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.ListObjectsV2Request;
import com.amazonaws.services.s3.model.ListObjectsV2Result;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;

/**
 * FileOperation_AWSは、S3ストレージに対して、
 * ファイル操作を行うクラスです。
 * 
 * 認証は下記の２通りが可能です。
 * ・実行サーバのEC2のインスタンスに、S3ストレージのアクセス許可を付与する
 * ・システムリソースにアクセスキー・シークレットキー・エンドポイント・レギオンを登録する
 * （CLOUD_STORAGE_S3_ACCESS_KEY、CLOUD_STORAGE_S3_SECRET_KEY、CLOUD_STORAGE_S3_SERVICE_END_POINT、CLOUD_STORAGE_S3_REGION）
 * 
 * 注意：
 * バケット名は全ユーザで共有のため、自身のバケット名か、作成されていないバケット名を指定する必要があります。
 * 
 * @og.rev 5.10.8.0 (2019/02/01) 新規作成
 *
 * @version 5
 * @author  oota
 * @sinse   JDK7.0
 */
public class FileOperation_AWS extends CloudFileOperation {
	/** クラス変数 */
	private final AmazonS3 amazonS3;
	private final String PLUGIN = "aws";

	/**
	 * コンストラクター
	 * 
	 * 初期化処理です。
	 * AWSの認証処理を行います。
	 * 
	 * @param bucket バケット
	 * @param inPath パス
	 */
	public FileOperation_AWS(String bucket, String inPath) {
		super(StringUtil.nval(bucket, HybsSystem.sys("CLOUD_BUCKET")), inPath);
		setPlugin(PLUGIN);

		// ｱｸｾｽｷｰ
		final String s3AccessKey = HybsSystem.sys("CLOUD_STORAGE_S3_ACCESS_KEY");
		String s3SecretKey = "";
		String s3ServiceEndPoint = "";
		String s3Region = "";

		// S3アクセスクライアントの生成
		if (StringUtils.isEmpty(s3AccessKey)) {
			// IAMロールによる認証
			amazonS3 = AmazonS3ClientBuilder.standard()
					.withCredentials(new InstanceProfileCredentialsProvider(false))
					.build();
		} else {
			// リソースのアクセスキーによる認証
			// ｼｰｸﾚｯﾄｷｰ
			s3SecretKey = HybsSystem.sys("CLOUD_STORAGE_S3_SECRET_KEY");
			// ｴﾝﾄﾞﾎﾟｲﾝﾄ
			s3ServiceEndPoint = HybsSystem.sys("CLOUD_STORAGE_S3_SERVICE_END_POINT");
			// ﾚｷﾞｵﾝ
			s3Region = HybsSystem.sys("CLOUD_STORAGE_S3_REGION");

			// AWSの認証情報
			AWSCredentials credentials = new BasicAWSCredentials(s3AccessKey, s3SecretKey);

			// エンドポイント設定
			EndpointConfiguration endpointConfiguration = new EndpointConfiguration(s3ServiceEndPoint, s3Region);
			amazonS3 = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(credentials))
					.withEndpointConfiguration(endpointConfiguration)
					.build();
		}

		try {
			// S3に指定されたﾊﾞｹｯﾄ(ｺﾝﾃﾅ)が存在しない場合は、作成する
			if (!amazonS3.doesBucketExist(conBucket)) { // doesBucketExistV2最新JARだと出ている
				amazonS3.createBucket(conBucket);
			}
		} catch (AmazonS3Exception ase) {
			StringBuilder errMsg = new StringBuilder(HybsSystem.BUFFER_MIDDLE);
			if (StringUtils.isEmpty(s3AccessKey)) {
				errMsg.append("IAMロールによる認証が失敗しました。");

			} else {
				errMsg.append("アクセスキーによる認証が失敗しました。");
				errMsg.append(" CLOUD_STORAGE_S3_ACCESS_KEY：").append(s3AccessKey);
				errMsg.append(" CLOUD_STORAGE_S3_SECRET_KEY：非表示");
				errMsg.append(" CLOUD_STORAGE_S3_SERVICE_END_POINT：").append(s3ServiceEndPoint);
				errMsg.append(" CLOUD_STORAGE_S3_REGION：").append(s3Region);
			}
			errMsg.append(" システムエラー情報：").append(ase.getMessage());
			throw new HybsSystemException(errMsg.toString());
		}
	}

	/**
	 * 書き込み処理
	 * 
	 * InputStreamのデータを書き込みます。
	 * 
	 * @param is 書き込みデータのInputStream
	 * @throws IOException ファイル関連エラー情報
	 */
	@Override
	public void write(InputStream is) throws IOException {
		ByteArrayInputStream bais = null;
		try {
			ObjectMetadata om = new ObjectMetadata();

			byte[] bytes = toByteArray(is);
			om.setContentLength(bytes.length);
			bais = new ByteArrayInputStream(bytes);

			PutObjectRequest request = new PutObjectRequest(conBucket, conPath, bais, om);

			amazonS3.putObject(request);
		} catch (Exception e) {
			StringBuilder errMsg = new StringBuilder(HybsSystem.BUFFER_MIDDLE);
			errMsg.append("AWSバケットに書き込みが失敗しました。path：").append(conPath);
			errMsg.append(" システムエラー情報：").append(e.getMessage());
			throw new IOException(errMsg.toString());
		} finally {
			Closer.ioClose(bais);
		}
	}

	/**
	 * 読み込み処理
	 * 
	 * データを読み込み、InputStreamとして、返します。
	 * 
	 * @return 読み込みデータのInputStream
	 * @throws FileNotFoundException ファイル非存在エラー情報
	 */
	@Override
	public InputStream read() throws FileNotFoundException {
		S3Object object = null;

		try {
			object = amazonS3.getObject(conBucket, conPath);
		} catch (Exception e) {
			StringBuilder errMsg = new StringBuilder(HybsSystem.BUFFER_MIDDLE);
			errMsg.append("AWSバケットから読み込みが失敗しました。path：").append(conPath);
			errMsg.append(" システムエラー情報：").append(e.getMessage());
			throw new FileNotFoundException(errMsg.toString());
		}
		return object.getObjectContent();
	}

	/**
	 * 削除処理
	 * 
	 * ファイルを削除します。
	 * 
	 * @return 成否フラグ
	 */
	@Override
	public boolean delete() {
		boolean flgRtn = false;

		try {
			if (isFile()) {
				// ファイル削除
				amazonS3.deleteObject(conBucket, conPath);
			} else if (isDirectory()) {
				// ディレクトリ削除
				// 一括削除のapiが無いので、繰り返しで削除を行う
				ObjectListing objectList = amazonS3.listObjects(conBucket, conPath);
				List<S3ObjectSummary> list = objectList.getObjectSummaries();
				for (S3ObjectSummary obj : list) {
					amazonS3.deleteObject(conBucket, obj.getKey());
				}

			}
			flgRtn = true;
		} catch (Exception e) {
			// エラーはスルーして、falseを返す
		}

		return flgRtn;
	}

	/**
	 * コピー処理
	 * 
	 * ファイルを指定先に、コピーします。
	 * 
	 * @param afPath コピー先
	 * @return 成否フラグ
	 */
	@Override
	public boolean copy(String afPath) {
		boolean flgRtn = false;

		try {
			amazonS3.copyObject(conBucket, conPath, conBucket, afPath);
			flgRtn = true;
		} catch (Exception e) {
			// エラーはスルーして、falseを返す
		}

		return flgRtn;
	}

	/**
	 * ファイルサイズ取得
	 * 
	 * ファイルサイズを返します。
	 * 
	 * @return ファイルサイズ
	 */
	@Override
	public long length() {
		long rtn = 0;

		try {
			ObjectMetadata meta = amazonS3.getObjectMetadata(conBucket, conPath);
			rtn = meta.getContentLength();
		} catch (Exception e) {
			// エラーはスルーして、0を返す。
		}
		return rtn;
	}

	/**
	 * 最終更新時刻取得
	 * 
	 * 最終更新時刻を取得します。
	 * 
	 * @return 最終更新時刻
	 */
	@Override
	public long lastModified() {
		long rtn = 0;

		try {
			ObjectMetadata meta = amazonS3.getObjectMetadata(conBucket, conPath);
			rtn = meta.getLastModified().getTime();
		} catch (Exception e) {
			// エラーはスルーして、0を返す
		}
		return rtn;
	}

	/**
	 * ファイル判定
	 * 
	 * ファイルの場合は、trueを返します。
	 * 
	 * @return ファイル判定フラグ
	 */
	@Override
	public boolean isFile() {
		boolean rtn = false;
		
		if(!isDirectory()) {
			rtn = amazonS3.doesObjectExist(conBucket, conPath); 
		}
		
		return rtn;
	}

	/**
	 * ディレクトリ判定
	 * 
	 * ディレクトリの場合は、trueを返します。
	 * 
	 * @return ディレクトリ判定フラグ
	 */
	@Override
	public boolean isDirectory() {
		boolean flgRtn = false;

		if (StringUtils.isEmpty(conPath)) {
			return true;
		}

		// S3にはディレクトリの概念はないので、「/」で続くデータが存在するかで、判定
		ObjectListing objectList = amazonS3.listObjects(conBucket, setDirTail(conPath));
		List<S3ObjectSummary> list = objectList.getObjectSummaries();
		flgRtn = list.size() == 0 ? false : true;

		return flgRtn;
	}

	/**
	 * ファイル一覧取得
	 * 
	 * パスのファイルとディレクトリ一覧を取得します。
	 * 
	 * @param filter フィルタ情報
	 * @return ファイルとティレクトリ一覧
	 */
	@Override
	public File[] listFiles(FileFilter filter) {
		if (!exists()) {
			return new FileOperationInfo[0];
		}

		String search = conPath;
		if (isDirectory()) {
			search = setDirTail(conPath);
		}

		List<File> rtnList = new ArrayList<File>();

		// 検索処理
		ListObjectsV2Request request = new ListObjectsV2Request()
				.withBucketName(conBucket)
				.withPrefix(search)
				.withDelimiter("/");
		ListObjectsV2Result list = amazonS3.listObjectsV2(request);
		List<S3ObjectSummary> objects = list.getObjectSummaries();

		// ファイル情報の取得
		for (S3ObjectSummary obj : objects) {
			String key = obj.getKey();

			FileOperationInfo file = new FileOperationInfo(PLUGIN, conBucket, key);
			file.setLastModifiedValue(obj.getLastModified().getTime());
			file.setFile(true);
			file.setSize(obj.getSize());
			rtnList.add(file);
		}

		// ディレクトリ情報の取得
		List<String> folders = list.getCommonPrefixes();
		for (String str : folders) {
			String key = rTrim(str, '/');

			FileOperationInfo file = new FileOperationInfo(PLUGIN, conBucket, key);
			file.setDirectory(true);
			rtnList.add(file);
		}

		// フィルタ処理
		File[] filterList = filter(rtnList, filter);

		return filterList;
	}

	/**
	 * 親ディレクトリ情報の取得
	 * 
	 * 親のディレクトリを返します。
	 * 
	 * @return 親のディレクトリ情報
	 */
	@Override
	public FileOperation getParentFile() {
		return new FileOperation_AWS(conBucket, this.getParent());
	}

	/** 以下はローカル環境でのテスト用メソッドです。 */
//				/**
//				 * ローカルでのテスト用コンストラクタ
//				 * 
//				 * 要：super.bucketの値は固定値に変更してください。
//				 * 
//				 * @param inPath
//				 */
//				public FileOperation_AWS(String bucket, String inPath, boolean test) {
//					// super( StringUtil.nval( bucket, HybsSystem.sys("CLOUD_BUCKET") ), inPath);
//					super( StringUtil.nval( bucket, "opengiontestbucket" ), inPath);
//			
//					// aws接続情報
//					 String s3AccessKey = "[key]";
//					 String s3SecretKey = "[secret]";
//					String s3ServiceEndPoint = "s3-ap-northeast-1.amazonaws.com";
//					String s3Region = "ap-northeast-1";
//					
//					// proxy環境での設定
//					ClientConfiguration conf = new ClientConfiguration();
//					conf.setProtocol(Protocol.HTTPS);
//					conf.setProxyHost("mtc-px14");
//					conf.setProxyPort(8081);
//			
//					// AWSの認証情報
//					AWSCredentials credentials = new BasicAWSCredentials(s3AccessKey, s3SecretKey);
//			
//					// エンドポイント設定
//					EndpointConfiguration endpointConfiguration = new EndpointConfiguration(s3ServiceEndPoint, s3Region);
//					amazonS3 = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(credentials))
//							.withEndpointConfiguration(endpointConfiguration)
//							.withClientConfiguration(conf) // テスト用にproxy設定
//							.build();
//			
//					try {
//						// S3に指定されたﾊﾞｹｯﾄ(ｺﾝﾃﾅ)が存在しない場合は、作成する
//						if (!amazonS3.doesBucketExistV2(conBucket)) {
//							amazonS3.createBucket(conBucket);
//						}
//					} catch (AmazonS3Exception ase) {
//						StringBuilder errMsg = new StringBuilder(HybsSystem.BUFFER_MIDDLE);
//						if (StringUtils.isEmpty(s3AccessKey)) {
//							errMsg.append("IAMロールによる認証か、バケットの作成が失敗しました。");
//							
//						} else {
//							errMsg.append("アクセスキーによる認証かバケットの作成が失敗しました。");
//							errMsg.append(" CLOUD_STORAGE_S3_ACCESS_KEY：").append(s3AccessKey);
//							errMsg.append(" CLOUD_STORAGE_S3_SECRET_KEY：非表示");
//							errMsg.append(" CLOUD_STORAGE_S3_SERVICE_END_POINT：").append(s3ServiceEndPoint);
//							errMsg.append(" CLOUD_STORAGE_S3_REGION：").append(s3Region);
//						}
//						errMsg.append(" バケット名：").append(conBucket);
//						errMsg.append(" システムエラー情報：").append(ase.getMessage());
//						throw new RuntimeException(errMsg.toString());
//					}
//				}
//			
//				/** テスト用メソッド */
//				public static void main(String[] args) {
////					writeTest();
////					listTest();
////					methodTest();
//					copyTest();
//				}
//				
//				public static void copyTest() {
//					File[] list = new File[] {new File("LIST01.xls"), new File("LIST02.ods")};
//					
////					System.out.println(file.exists());
//					
//					for(File file: list) {
//						FileOperation file2 = new FileOperation_AWS("ootalocaltest", file.getName(), true);
//						FileUtil.copy(file,  file2);
//					}
//				}
//				
//				public static void writeTest() {
//					FileOperation file = new FileOperation_AWS("otlocaltest01", "sample/test.txt", true);
//					try(InputStream is = new ByteArrayInputStream("sample".getBytes())){
//						file.write(is);
//					}catch(Exception e) {
//						System.out.println(e.getMessage());
//					}
//					
////					File file 
//				}
//				
//				public static void listTest() {
//					File file = new FileOperation_AWS("otlocaltest01", "sample", true);
//					
//					// フィルタ設定
//					HybsFileFilter filter = new HybsFileFilter();
//					filter.startsWith("te");
//					
//					File[] list = file.listFiles(filter);
//					
//					System.out.println(list.length);
//					for(File f: list) {
//						FileOperation fo = (FileOperation)f;
//						
//						try(InputStream is = fo.read()){
//							Files.copy(is, Paths.get("out.txt"), StandardCopyOption.REPLACE_EXISTING);
//						}catch(IOException ioe) {
//							System.out.println(ioe);
//						}
//					}
//				}
//				
//				public static void methodTest() {
//					FileOperation file = new FileOperation_AWS("otlocaltest01", "", true);
//					boolean rtn = false;
////					rtn = file.isDirectory();
//					rtn = file.isFile();
//					
//					System.out.println(rtn);
//				}
}
