/*
 * Copyright (c) 2009 The openGion Project.
 *
 * 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.opengion.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.opengion.fukurou.model.FileOperation;
import org.opengion.fukurou.system.Closer;							// 8.0.0.0 (2021/09/30) util.Closer → system.Closer
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;

import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// HybsSystem.BUFFER_MIDDLE は fukurou に移動
import static org.opengion.fukurou.system.HybsConst.CR ;			// 8.0.0.1 (2021/10/08)

import com.amazonaws.auth.InstanceProfileCredentialsProvider;
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ｽﾄﾚｰｼﾞのｱｸｾｽ許可を付与する
 * <del> ２）ｼｽﾃﾑﾘｿｰｽにｱｸｾｽｷｰ・ｼｰｸﾚｯﾄｷｰ・ｴﾝﾄﾞﾎﾟｲﾝﾄ・ﾚｷﾞｵﾝを登録する
 * （CLOUD_STORAGE_S3_ACCESS_KEY、CLOUD_STORAGE_S3_SECRET_KEY、CLOUD_STORAGE_S3_SERVICE_END_POINT、CLOUD_STORAGE_S3_REGION）
 * </del>
 *
 * 注意：
 * ﾊﾞｹｯﾄ名は全ﾕｰｻﾞで共有のため、自身のﾊﾞｹｯﾄ名か、作成されていないﾊﾞｹｯﾄ名を指定する必要があります。
 *
 * @og.rev 5.10.8.0 (2019/02/01) 新規作成
 *
 * @version 5
 * @author  oota
 * @since   JDK7.0
 */
public class FileOperation_AWS extends CloudFileOperation {
	private static final long serialVersionUID = 5108020190201L ;

	private static final String PLUGIN = "AWS";			// 8.0.0.1 (2021/10/08) staticと大文字化

	/** ｸﾗｽ変数 */
	private final AmazonS3 amazonS3;

	/**
	 * ｺﾝｽﾄﾗｸﾀｰ
	 *
	 * 初期化処理です。
	 * AWSの認証処理を行います。
	 *
	 * @og.rev 8.0.0.1 (2021/10/08) CLOUD_STORAGE_S3… 関連の方法廃止
	 *
	 * @param bucket ﾊﾞｹｯﾄ
	 * @param inPath ﾊﾟｽ
	 */
	public FileOperation_AWS(final String bucket, final String inPath) {
		super(StringUtil.nval(bucket, HybsSystem.sys("CLOUD_BUCKET")), inPath);

		// IAMﾛｰﾙによる認証
		amazonS3 = AmazonS3ClientBuilder.standard()
				.withCredentials(new InstanceProfileCredentialsProvider(false))
				.build();

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

			throw new HybsSystemException(errMsg,ase);
		}
	}

	/**
	 * 書き込み処理(評価用)
	 *
	 * Fileを書き込みます。
	 *
	 * @og.rev 8.0.0.1 (2021/10/08) 新規追加
	 * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
	 *
	 * @param inFile 書き込みFile
	 * @throws IOException ﾌｧｲﾙ関連ｴﾗｰ情報
	 */
	@Override
	public void write(final File inFile) throws IOException {
		try {
			amazonS3.putObject(conBucket, conPath, inFile);
//		} catch (final Exception ex) {
		} catch( final Throwable th ) {		// 8.0.2.0 (2021/11/30)
			final String errMsg = new StringBuilder(BUFFER_MIDDLE)
						.append("AWSﾊﾞｹｯﾄに(File)書き込みが失敗しました。").append( CR )
						.append(conPath).toString();
			throw new IOException(errMsg,th);
		}
	}

	/**
	 * 書き込み処理
	 *
	 * InputStreamのﾃﾞｰﾀを書き込みます。
	 *
	 * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
	 *
	 * @param is 書き込みﾃﾞｰﾀのInputStream
	 * @throws IOException ﾌｧｲﾙ関連ｴﾗｰ情報
	 */
	@Override
	public void write(final InputStream is) throws IOException {
		ByteArrayInputStream bais = null;
		try {
			final ObjectMetadata om = new ObjectMetadata();

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

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

			amazonS3.putObject(request);
//		} catch (final Exception ex) {
		} catch( final Throwable th ) {		// 8.0.2.0 (2021/11/30)
			final String errMsg = new StringBuilder(BUFFER_MIDDLE)
						.append("AWSﾊﾞｹｯﾄに(InputStream)書き込みが失敗しました。").append( CR )
						.append(conPath).toString();
			throw new IOException(errMsg,th);
		} finally {
			Closer.ioClose(bais);
		}
	}

	/**
	 * 読み込み処理
	 *
	 * ﾃﾞｰﾀを読み込み、InputStreamとして、返します。
	 *
	 * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
	 *
	 * @return 読み込みﾃﾞｰﾀのInputStream
	 * @throws FileNotFoundException ﾌｧｲﾙ非存在ｴﾗｰ情報
	 */
	@Override
	public InputStream read() throws FileNotFoundException {
		S3Object object = null;

		try {
			object = amazonS3.getObject(conBucket, conPath);
//		} catch (final Exception ex) {
		} catch( final Throwable th ) {		// 8.0.2.0 (2021/11/30)
			final String errMsg = new StringBuilder(BUFFER_MIDDLE)
						.append("AWSﾊﾞｹｯﾄから読み込みが失敗しました。").append( CR )
						.append(conPath).append( CR )
						.append( th.getMessage() ).toString();
			throw new FileNotFoundException(errMsg);				// FileNotFoundException は、Throwable 引数を持つコンストラクタはなない。
		}
		return object.getObjectContent();	// com.amazonaws.services.s3.model.S3ObjectInputStream
	}

	/**
	 * 削除処理
	 *
	 * ﾌｧｲﾙを削除します。
	 *
	 * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
	 *
	 * @return 成否ﾌﾗｸﾞ
	 */
	@Override
	public boolean delete() {
		boolean flgRtn = false;

		try {
			if (isFile()) {
				// ﾌｧｲﾙ削除
				amazonS3.deleteObject(conBucket, conPath);
			} else if (isDirectory()) {
				// ﾃﾞｨﾚｸﾄﾘ削除
				// 一括削除のapiが無いので、繰り返しで削除を行う
				final ObjectListing objectList = amazonS3.listObjects(conBucket, conPath);
				final List<S3ObjectSummary> list = objectList.getObjectSummaries();
				for (final S3ObjectSummary obj : list) {
					amazonS3.deleteObject(conBucket, obj.getKey());
				}
			}
			flgRtn = true;
//		} catch (final Exception ex) {
		} catch( final Throwable th ) {		// 8.0.2.0 (2021/11/30)
			// ｴﾗｰはｽﾙｰして、falseを返す
			System.out.println( th.getMessage() );
		}

		return flgRtn;
	}

	/**
	 * ｺﾋﾟｰ処理
	 *
	 * ﾌｧｲﾙを指定先に、ｺﾋﾟｰします。
	 *
	 * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
	 *
	 * @param afPath ｺﾋﾟｰ先
	 * @return 成否ﾌﾗｸﾞ
	 */
	@Override
	public boolean copy(final String afPath) {
		boolean flgRtn = false;

		try {
			amazonS3.copyObject(conBucket, conPath, conBucket, afPath);
			flgRtn = true;
//		} catch (final Exception ex) {
		} catch( final Throwable th ) {		// 8.0.2.0 (2021/11/30)
			// ｴﾗｰはｽﾙｰして、falseを返す
			System.out.println( th.getMessage() );
		}

		return flgRtn;
	}

	/**
	 * ﾌｧｲﾙｻｲｽﾞ取得
	 *
	 * ﾌｧｲﾙｻｲｽﾞを返します。
	 *
	 * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
	 *
	 * @return ﾌｧｲﾙｻｲｽﾞ
	 */
	@Override
	public long length() {
		long rtn = 0;

		try {
			final ObjectMetadata meta = amazonS3.getObjectMetadata(conBucket, conPath);
			rtn = meta.getContentLength();
//		} catch (final Exception ex) {
		} catch( final Throwable th ) {		// 8.0.2.0 (2021/11/30)
			// ｴﾗｰはｽﾙｰして、0を返す。
			System.out.println( th.getMessage() );
		}
		return rtn;
	}

	/**
	 * 最終更新時刻取得
	 *
	 * 最終更新時刻を取得します。
	 *
	 * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
	 *
	 * @return 最終更新時刻
	 */
	@Override
	public long lastModified() {
		long rtn = 0;

		try {
			final ObjectMetadata meta = amazonS3.getObjectMetadata(conBucket, conPath);
			rtn = meta.getLastModified().getTime();
//		} catch (final Exception ex) {
		} catch( final Throwable th ) {		// 8.0.2.0 (2021/11/30)
			// ｴﾗｰはｽﾙｰして、0を返す
			System.out.println( th.getMessage() );
		}
		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() {
		if (StringUtil.isEmpty(conPath)) {		// 8.0.1.0 (2021/10/29) org.apache.commons.lang3.StringUtils 置換
			return true;
		}

		// S3にはﾃﾞｨﾚｸﾄﾘの概念はないので、｢/｣で続くﾃﾞｰﾀが存在するかで、判定
		final ObjectListing objectList = amazonS3.listObjects(conBucket, setDirTail(conPath));
		final List<S3ObjectSummary> list = objectList.getObjectSummaries();

//		return list.size() != 0 ;
		return ! list.isEmpty() ;
	}

	/**
	 * ﾌｧｲﾙ一覧取得
	 *
	 * ﾊﾟｽのﾌｧｲﾙとﾃﾞｨﾚｸﾄﾘ一覧を取得します。
	 *
	 * @og.rev 8.0.2.0 (2021/11/30) fukurou.util.rTrim(String,char) 使用
	 *
	 * @param filter ﾌｨﾙﾀ情報
	 * @return ﾌｧｲﾙとﾃｨﾚｸﾄﾘ一覧
	 */
	@Override
	public File[] listFiles(final FileFilter filter) {
		if (!exists()) {
			return new FileOperationInfo[0];
		}

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

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

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

		// ﾌｧｲﾙ情報の取得
		for (final S3ObjectSummary obj : objects) {
			final String key = obj.getKey();

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

		// ﾃﾞｨﾚｸﾄﾘ情報の取得
		final List<String> folders = list.getCommonPrefixes();
		for (final String str : folders) {
//			final String key = rTrim(str, '/');
			final String key = StringUtil.rTrim(str, '/');			// 8.0.2.0 (2021/11/30)

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

		// ﾌｨﾙﾀ処理
		return filter(rtnList, filter);
	}

	/**
	 * 親ﾃﾞｨﾚｸﾄﾘ情報の取得
	 *
	 * 親のﾃﾞｨﾚｸﾄﾘを返します。
	 *
	 * @return 親のﾃﾞｨﾚｸﾄﾘ情報
	 */
	@Override
	public FileOperation getParentFile() {
		return new FileOperation_AWS(conBucket, this.getParent());
	}
}
