/*
 * 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.fukurou.model;
package org.opengion.cloud;						// 8.0.0.2 (2021/10/15) fukurou.model → cloud にパッケージ移動

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.opengion.fukurou.system.Closer;
import org.opengion.fukurou.util.StringUtil;

import org.opengion.fukurou.model.FileOperation;		// 8.0.0.2 (2021/10/15)

/**
 * クラウドストレージ対応用の抽象クラスです。
 * 各ベンダーのストレージに対応したプラグインを作成する場合はこのクラスを継承してください。
 *
 *
 * @og.group ファイル操作
 *
 * @og.rev 5.10.8.0 (2019/02/01) 新規作成
 * @og.rev 5.10.9.0 (2019/03/01) 変更対応
 * @og.rev 8.0.0.2 (2021/10/15) fukurou.model → cloud にパッケージ移動
 * @author oota
 * @since JDK7.0
 */
public abstract class CloudFileOperation extends FileOperation {
	//* このプログラムのVERSION文字列を設定します。{@VALUE} */
	private static final String VERSION = "8.0.0.1 (2021/10/08)" ;
	private static final long serialVersionUID = 800120211008L ;

	/** バッファサイズ {@value} */
	private static final int BUFFER_SIZE = 1024 * 4;

	private static final String UNIMPLEMNTED_ERR="このクラスでは未実装のメソッドです。";
	private static final char   FS = '/' ;
	// 5.10.12.2 (2019/06/17) 相対パス対応「../」と１つ前のディレクトリ情報を抽出(１つ前が先頭の場合は、/ではなく^)
	// 7.2.9.4 (2020/11/20) PMD:Variables that are final and static should be all capitals, 'ptnPreDir' is not all capitals.
//	private static final Pattern ptnPreDir = Pattern.compile("(?<=/|^)[^/]+/\\.\\./");
	private static final Pattern PTN_PRE_DIR = Pattern.compile("(?<=/|^)[^/]+/\\.\\./");

	/** パス */
	protected final String conPath;
	/** バケット名 */
	protected final String conBucket;

	/**
	 * コンストラクタ
	 *
	 * @param bucket バケット名
	 * @param inPath ファイルパス
	 */
	public CloudFileOperation( final String bucket, final String inPath ) {
		super(inPath);

		conPath   = editPath(replaceFileSeparetor(inPath));
		conBucket = bucket;

		if (StringUtil.isNull(conBucket)) {
			final String errMsg = "バケット未指定です。hayabusa利用ではシステム変数の「CLOUD_BUCKET」にバケット名を設定して下さい。";
			throw new RuntimeException(errMsg);
		}
	}

	/**
	 * 書き込み処理(評価用)
	 *
	 * Fileを書き込みます。
	 *
	 * @og.rev 8.0.0.1 (2021/10/08) 新規追加
	 * @param inFile 書き込みFile
	 * @throws IOException ファイル関連エラー情報
	 */
	@Override
	public abstract void write(final File inFile) throws IOException ;

	/**
	 * データ書き込み
	 *
	 * InputStreamのデータを書き込みます。
	 *
	 * @param is 書き込みデータのInputStream
	 * @throws IOException IO関連のエラー情報
	 */
	@Override
	public abstract void write(InputStream is) throws IOException;

	/**
	 * データ読み込み
	 *
	 * データを読み込み、InputStreamを返します。
	 *
	 * @return 読み込みデータのInputStream
	 * @throws FileNotFoundException ファイル非存在エラー情報
	 */
	@Override
	public abstract InputStream read() throws FileNotFoundException;

	/**
	 * ファイル削除
	 *
	 * ファイルを削除します。
	 *
	 * @return 成否フラグ
	 */
	@Override
	public abstract boolean delete();

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

	/**
	 * ファイル移動
	 *
	 * ファイルを指定先に移動します。
	 *
	 * @param afPath 移動先
	 * @return 成否フラグ
	 */
	@Override
	public boolean move(final String afPath) {
		return copy(afPath) && delete();

//		boolean flgRtn = false;
//
//		flgRtn = copy(afPath);
//		if (flgRtn) {
//			flgRtn = delete();
//		}
//
//		return flgRtn;
	}

	/**
	 * ファイルサイズ取得(Fileｸﾗｽ)
	 *
	 * ファイルサイズを返します。
	 *
	 * @return ファイルサイズ
	 * @see java.io.File#isFile()
	 */
	@Override
	public abstract long length();

	/**
	 * 最終更新時刻取得(Fileｸﾗｽ)
	 *
	 * 最終更新時刻を返します。
	 *
	 * @return 最終更新時刻
	 * @see java.io.File#lastModified()
	 */
	@Override
	public abstract long lastModified();

	/**
	 * ファイル判定(Fileｸﾗｽ)
	 *
	 * ファイルの場合は、trueを返します。
	 *
	 * @return ファイルフラグ
	 * @see java.io.File#isFile()
	 */
	@Override
	public abstract boolean isFile();

	/**
	 * ディレクトリ判定(Fileｸﾗｽ)
	 *
	 * ディレクトリの場合は、trueを返します。
	 *
	 * @return ディレクトリフラグ
	 * @see java.io.File#isDirectory()
	 */
	@Override
	public abstract boolean isDirectory();

	/**
	 * 一覧取得(Fileｸﾗｽ)
	 *
	 * パスのファイルと、ディレクトリ一覧を取得します。
	 *
	 * @param filter ﾌｧｲﾙﾌｨﾙﾀｰ
	 * @return ファイルとティレクトリ一覧
	 * @see java.io.File#listFiles(FileFilter)
	 */
	@Override
	public abstract File[] listFiles(FileFilter filter);

	/**
	 * 親ディレクトリの取得(Fileｸﾗｽ)
	 *
	 * 親のディレクトリ情報を返します。
	 *
	 * @return 親のディレクトリ
	 * @see java.io.File#getParentFile()
	 */
	@Override
	public abstract File getParentFile();

	/**
	 * ファイルパス取得(Fileｸﾗｽ)
	 *
	 * ファイルパスを取得します。
	 *
	 * @return 設定パス
	 * @see java.io.File#getPath()
	 */
	@Override
	public String getPath() {
		return conPath;
	}

	/**
	 * 絶対パス取得(Fileｸﾗｽ)
	 *
	 * 絶対パスを取得します。
	 *
	 * @return 絶対パス
	 * @see java.io.File#getAbsolutePath()
	 */
	@Override
	public String getAbsolutePath() {
		return conPath;
	}

	/**
	 * ファイル名取得(Fileｸﾗｽ)
	 *
	 * ファイル名を取得します。
	 *
	 * @return 名称
	 * @see java.io.File#getName()
	 */
	@Override
	public String getName() {
		return drawName(conPath);
	}

	/**
	 * 親のパス取得(Fileｸﾗｽ)
	 *
	 * 親のパスを取得します。
	 *
	 * @return 親のパス
	 * @see java.io.File#getParent()
	 */
	@Override
	public String getParent() {
		return drawParent(conPath);
	}

	/**
	 * 存在チェック(Fileｸﾗｽ)
	 *
	 * 存在する場合は、trueを返します。
	 *
	 * @return 存在フラグ
	 * @see java.io.File#exists()
	 */
	@Override
	public boolean exists() {
		return isDirectory() | isFile();
	}

	/**
	 * ディレクトリの作成(Fileｸﾗｽ)
	 *
	 * ※１つのディレクトリのみ作成します。
	 * クラウドストレージにはディレクトリの概念が無いため、
	 * 作成は行わず、trueを返します。
	 *
	 * @return 成否フラグ
	 * @see java.io.File#mkdir()
	 */
	@Override
	public boolean mkdir() {
		return true;
	}

	/**
	 * ディレクトリの作成(複数)(Fileｸﾗｽ)
	 *
	 * ※複数のディレクトリを作成します。
	 * クラウドストレージにはディレクトリの概念が無いため、
	 * 作成は行わず、trueを返します。
	 *
	 * @return 成否フラグ
	 * @see java.io.File#mkdirs()
	 */
	@Override
	public boolean mkdirs() {
		return true;
	}

	/**
	 * ファイル名変更(Fileｸﾗｽ)
	 *
	 * 指定のファイル情報のファイル名に変更します。
	 *
	 * @param dest 変更後のファイル情報
	 * @return 成否フラグ
	 * @see java.io.File#renameTo(File)
	 */
	@Override
	public boolean renameTo(final File dest) {
		return move(dest.getPath());
	}

	/**
	 * 書き込み可能フラグ(Fileｸﾗｽ)
	 *
	 * ※クラウドストレージの場合は、
	 * 存在すればtrueを返します。
	 *
	 * @return 書き込み可能フラグ
	 * @see java.io.File#canWrite()
	 */
	@Override
	public boolean canWrite() {
		return exists();
	}

	/**
	 * 読み取り可能フラグ(Fileｸﾗｽ)
	 *
	 * ※クラウドストレージの場合は、
	 * 存在すればtrueを返します。
	 *
	 * @return 読み取り可能フラグ
	 * @see java.io.File#canRead()
	 */
	@Override
	public boolean canRead() {
		return exists();
	}

	/**
	 * 隠しファイルフラグ(Fileｸﾗｽ)
	 *
	 * ※クラウドストレージの場合は、
	 * 必ずfalseを返します。
	 *
	 * @return 隠しファイルフラグ
	 * @see java.io.File#isHidden()
	 */
	@Override
	public boolean isHidden() {
		return false;
	}

	/**
	 * 新規ファイル作成(Fileｸﾗｽ)
	 *
	 * 既にファイルが存在しない場合のみ、
	 * 空のファイルを作成します。
	 *
	 * @return 指定されたファイルが存在せず、ファイルの生成に成功した場合はtrue、示されたファイルがすでに存在する場合はfalse
	 * @throws IOException ファイル関連エラー情報
	 * @see java.io.File#createNewFile()
	 */
	@Override
	public boolean createNewFile() throws IOException {
		boolean rtn = false;

		if (!exists()) {
			InputStream is = null;
			try {
				is = new ByteArrayInputStream(new byte[0]);
				write(is);
				rtn = true;
			} finally {
				Closer.ioClose(is);
			}
		}

		return rtn;
	}

	/**
	 * 最終更新時刻の更新(Fileｸﾗｽ)
	 *
	 * 最終更新時刻の更新を行います。
	 * ※クラウドストレージの場合は、
	 * 最終更新時刻の更新を行えません。
	 *
	 * @param time 更新する最終更新時刻
	 * @return 成否フラグ
	 * @see java.io.File#setLastModified(long)
	 */
	@Override
	public boolean setLastModified(final long time) {
		// クラウドストレージでは、setLastModifiedによる、
		// 最終更新時刻の設定はできないので、
		// 処理を行わずにtrueを返します。
		return true;
	}

	/**
	 * カノニカルファイル情報の取得
	 *
	 * ※ローカルサーバのみ通常ファイルと、
	 * カノニカルファイルで異なります。
	 *
	 * @return カノニカルファイル情報
	 * @throws IOException ファイル関連エラー情報
	 * @see java.io.File#getCanonicalFile()
	 */
	@Override
	public FileOperation getCanonicalFile() throws IOException {
		return this;
	}

	/**
	 * toString(Fileｸﾗｽ)
	 *
	 * パスを返します。
	 * Fileクラスの拡張なので、path のみを返します。
	 *
	 * @return ファイルパス
	 * @see java.io.File#toString()
	 */
	@Override
	public String toString() {
		return conPath;
	}

	/** 共通関数 **/

	/**
	 * ファイルパスの編集
	 *
	 * パスの先頭が「/」の場合は「/」の除去と、「//」を「/」に置換処理の追加。
	 *
	 * @og.rev 5.10.12.2 (2019/06/17) 相対パス対応
	 * @og.rev 8.0.0.1 (2021/10/08) protected → private
	 *
	 * @param path ファイルパス
	 * @return 変更後パス
	 */
//	protected String editPath(final String path) {
	private String editPath(final String path) {
		if (StringUtil.isNull(path)) {
			return "";
		}
		String rtn = path;

		// 「//+」は「/」に置換
		rtn = rtn.replaceAll("//+", "/");
		// 先頭が「/」の場合は除去
//		if ("/".equals(rtn.substring(0, 1))) {
		if( FS == rtn.charAt(0) ) {
			rtn = rtn.substring(1);
		}
		// 後尾の「.」は除去
		rtn = rTrim(rtn, '.');
		// 後尾の「/」は除去
		rtn = rTrim(rtn, FS);

		// 5.10.12.2 (2019/06/17)
		// 「../」の文字列は１つ上のディレクトリに変換を行います。
		Matcher mtc = PTN_PRE_DIR.matcher(rtn);

		// 「../」が無くなるまで、１つずづ変換します。
		while(mtc.find()) {
			rtn = mtc.replaceFirst("");
			mtc = PTN_PRE_DIR.matcher(rtn);
		}

		return rtn;
	}

	/**
	 * 親のパスを抽出
	 *
	 * キーから親のパスを抽出します。
	 *
	 * @og.rev 8.0.0.1 (2021/10/08) protected → private
	 *
	 * @param key キー
	 * @return 親のパス
	 */
//	protected String drawParent(final String key) {
	private String drawParent(final String key) {
		final int k = key.lastIndexOf(FS);

		String rtn = "";
		if (k > 0) {
			rtn = key.substring(0, key.lastIndexOf(FS));
		}
		if ("/".equals(File.separator)) {
			rtn = File.separator + rtn;
		}

		return rtn;
	}

	/**
	 * 名称の抽出
	 *
	 * 引数のkeyから名称を抽出します。
	 *
	 * @og.rev 8.0.0.1 (2021/10/08) protected → private
	 *
	 * @param key キー(パス)
	 * @return 名称
	 */
//	protected String drawName(final String key) {
	private String drawName(final String key) {
		final int k = key.lastIndexOf(FS);

		String rtn = key;
		if (k > 0) {
			rtn = key.substring(key.lastIndexOf(FS) + 1);
		}
		return rtn;
	}

	/**
	 * ディレクトリ用のパス編集
	 *
	 * 後尾に「/」がない場合は、付与します。
	 *
	 * @param path パス
	 * @return 後尾に「/」ありのパス
	 */
	protected String setDirTail(final String path) {
		if (StringUtil.isNull(path)) {
			return path;
		}

		final StringBuilder sb = new StringBuilder(path);
//		if (!"/".equals(path.substring(path.length() - 1))) {
		if ( FS != path.charAt(path.length() - 1) ) {
			sb.append(FS);
		}
		return sb.toString();
	}

	/**
	 * 右側トリム処理
	 *
	 * 右側の文字が、指定の文字の場合、除去します。
	 *
	 * @param str 対象文字列
	 * @param chr 指定文字
	 * @return 右側から指定文字を除去後の文字列
	 */
	protected String rTrim(final String str, final char chr) {
		String rtn = str;
		int trgPos = 0;
		for( int i = str.length() - 1; i >= 0; i--) {
			if (str.charAt(i) == chr) {
				trgPos = i;
				// すべて合致した場合は、から文字を返す
				if (trgPos == 0) {
					rtn = "";
				}
			} else {
				break;
			}
		}

		if (trgPos > 0) {
			rtn = str.substring(0, trgPos);
		}

		return rtn;
	}

	/**
	 * ファイル区切り文字変換
	 *
	 * ファイル区切り文字を変換します。
	 *
	 * @og.rev 8.0.0.1 (2021/10/08) protected → private
	 *
	 * @param path 変換前文字列
	 * @return 返還後文字列
	 */
//	protected String replaceFileSeparetor(final String path) {
	private String replaceFileSeparetor(final String path) {
		if (StringUtil.isNull(path)) {
			return "";
		}

		return path.replaceAll("\\\\", "/");
	}

	/**
	 * フィルター処理
	 *
	 * フィルター処理を行います。
	 *
	 * @param list フィルタを行うリスト
	 * @param filter フィルタ情報
	 * @return フィルタ後のリスト
	 */
	protected File[] filter(final List<File> list, final FileFilter filter) {
		final List<File> files = new ArrayList<File>();
		for( final File file : list ) {
			if (filter.accept(file)) {
				files.add(file);
			}
		}
		return files.toArray(new File[files.size()]);
	}

	/**
	 * ストリームの変換処理
	 *
	 * InputStreamをbyte[]に変換。
	 * InputStreamのサイズ計算に利用。
	 *
	 * @param is byte配列変換するInputStream
	 * @return InpusStreamをbyte配列に変換した値
	 * @throws IOException ファイル関連エラー情報
	 */
	protected byte[] toByteArray(final InputStream is) throws IOException {
		final ByteArrayOutputStream output = new ByteArrayOutputStream();
		try {
			// 7.2.9.4 (2020/11/20) Avoid variables with short names like b   b → bt , n → no
			final byte[] bt = new byte[BUFFER_SIZE];
			int no = 0;
			while ((no = is.read(bt)) != -1) {
				output.write(bt, 0, no);
			}
			return output.toByteArray();
		} finally {
			output.close();
		}
	}

//	/**
//	 * ローカル実行フラグ判定
//	 *
//	 * このabstract クラスの継承クラスはクラウド上で実行されるため、
//	 * falseを返します。
//	 *
//	 * @return ローカル実行フラグ
//	 */
//	@Override
//	public boolean isLocal() {
//		return false;
//	}

	/**
	 * 保存先のクラウド判定。
	 *
	 * 判定結果を返します。
	 * trueの場合は、クラウドストレージ保存。
	 * falseの場合は、ローカルに保存です。
	 *
	 * @return クラウドならtrue
	 */
	public boolean isCloud() {
		return true;
	}

	/** java.io.Fileに実装されており、クラウド用ファイルクラスに未実装のメソッドの対応 */

	/**
	 * canExecuteの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @return フラグ
	 * @see java.io.File#canExecute()
	 */
	@Override
	public boolean canExecute() {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * deleteOnExitの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @see java.io.File#deleteOnExit()
	 */
	@Override
	public void deleteOnExit() {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * getAbsoluteFileの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @return Fileｵﾌﾞｼﾞｪｸﾄ
	 * @see java.io.File#getAbsoluteFile()
	 */
	@Override
	public File getAbsoluteFile() {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * getFreeSpaceの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @return 数値
	 * @see java.io.File#getFreeSpace()
	 */
	@Override
	public long getFreeSpace() {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * getTotalSpaceの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @return 数値
	 * @see java.io.File#getTotalSpace()
	 */
	@Override
	public long getTotalSpace() {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * getUsableSpaceの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @return 数値
	 * @see java.io.File#getUsableSpace()
	 */
	@Override
	public long getUsableSpace() {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * isAbsoluteの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @return フラグ
	 * @see java.io.File#isAbsolute()
	 */
	@Override
	public boolean isAbsolute() {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * setReadableの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @param readable フラグ
	 * @return フラグ
	 * @see java.io.File#setReadable(boolean)
	 */
	@Override
	public boolean setReadable(final boolean readable) {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * setReadableの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @param readable フラグ
	 * @param ownerOnly フラグ
	 * @return フラグ
	 * @see java.io.File#setReadable(boolean,boolean)
	 */
	@Override
	public boolean setReadable(final boolean readable, final boolean ownerOnly) {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * setWritableの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @param writable フラグ
	 * @return フラグ
	 * @see java.io.File#setWritable(boolean)
	 */
	@Override
	public boolean setWritable(final boolean writable) {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * canExecuteの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @param writable フラグ
	 * @param ownerOnly フラグ
	 * @return フラグ
	 * @see java.io.File#setWritable(boolean,boolean)
	 */
	@Override
	public boolean setWritable(final boolean writable, final boolean ownerOnly) {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}

	/**
	 * canExecuteの実行(Fileｸﾗｽ)
	 *
	 * クラウド側では未実装のメソッドです。
	 *
	 * @return URI情報
	 * @see java.io.File#toURI()
	 */
	@Override
	public URI toURI() {
		throw new RuntimeException(UNIMPLEMNTED_ERR);
	}
}
