package org.opengion.fukurou.model;

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.util.ArrayList;
import java.util.List;

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

/**
 * ファイル操作の抽象クラス
 * 
 * 共通の処理等を実装しています。
 * 
 * @og.group ファイル操作
 * 
 * @og.rev 5.10.8.0 (2019/02/01) 新規作成
 * @og.auth oota
 * @since JDK7.0
 */
// public abstract class AbstractFileOperation implements FileOperation {
public abstract class AbstractFileOperation extends FileOperation {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "7.0.1.9 (2019/02/04)" ;
	private static final long serialVersionUID = 701920190204L ;

	/* クラス定数 */
	private static final int BUFFER_SIZE = 1024 * 4;
	/* クラス変数 */
	// パス
	protected final String path;
	// バケット名
	protected final String bucket;

	/**
	 * コンストラクタ
	 * ローカルサーバ用
	 */
	public AbstractFileOperation() {
		super( "","" );
		path = "";
		bucket = "";
	}

	/**
	 * コンストラクタ
	 * クラウドストレージ用
	 * 
	 * @param buket バケット
	 * @param inPath パス
	 */
	public AbstractFileOperation(final String buket , final String inPath) {
		super( buket,inPath );

		path = editPath(replaceFileSeparetor(inPath));
		this.bucket = buket;

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

	/**
	 * InputStreamのデータを書き込みます。
	 * 
	 * @param is 書き込みデータのInputStream
	 * @throws IOException
	 */
//	@Override
	public void write(InputStream is) throws IOException {
		String errMsg = "このクラスでは実装されていません。write(InputStream is)";
		throw new UnsupportedOperationException(errMsg);
	}

	/**
	 * データを読み込み、InputStreamとして、返します。
	 * 
	 * @return 読み込みデータのInputStream
	 * @throws FileNotFoundException
	 */
//	@Override
	public InputStream read() throws FileNotFoundException {
		String errMsg = "このクラスでは実装されていません。read()";
		throw new UnsupportedOperationException(errMsg);
	}

	/**
	 * ファイルを削除します。
	 * 
	 * @return 成否フラグ
	 */
	@Override
	public boolean delete() {
		String errMsg = "このクラスでは実装されていません。delete()";
		throw new UnsupportedOperationException(errMsg);
	}

	/**
	 * ファイルを指定先に、コピーします。
	 * 
	 * @param afPath コピー先
	 * @return 成否フラグ
	 */
//	@Override
	public boolean copy(String afPath) {
		String errMsg = "このクラスでは実装されていません。copy(String)";
		throw new UnsupportedOperationException(errMsg);
	}

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

		flgRtn = copy(afPath);
		if (flgRtn) {
			flgRtn = delete();
		}

		return flgRtn;
	}

	/**
	 * 設定パスを取得します。
	 * 
	 * @return 設定パス
	 */
	@Override
	public String getPath() {
		return path;
	}

	/**
	 * 絶対パスを取得します。
	 * 
	 * @return 絶対パス
	 */
	@Override
	public String getAbsolutePath() {
		return path;
	}

	/**
	 * 名称を取得します。
	 * 
	 * @return 名称
	 */
	@Override
	public String getName() {
		return drawName(path);
	}

	/**
	 * 親のパスを取得します。
	 * 
	 * @return 親のパス
	 */
	@Override
	public String getParent() {
		return drawParent(path);
	}

	/**
	 * ファイルサイズを返します
	 * 
	 * @return ファイルサイズ
	 */
	@Override
	public long length() {
		// TODO 自動生成されたメソッド・スタブ
		return 0;
	}

	/**
	 * 最終更新時刻を取得します。
	 * 
	 * @return 最終更新時刻
	 */
	@Override
	public long lastModified() {
		// TODO 自動生成されたメソッド・スタブ
		return 0;
	}

	/**
	 * ファイルの場合は、trueを返します。
	 * 
	 * @return ファイルフラグ
	 */
	@Override
	public boolean isFile() {
		// TODO 自動生成されたメソッド・スタブ
		return false;
	}

	/**
	 * ディレクトリの場合は、trueを返します。
	 * 
	 * @return ディレクトリフラグ
	 */
	@Override
	public boolean isDirectory() {
		// TODO 自動生成されたメソッド・スタブ
		return false;
	}

	/**
	 * 存在する場合は、trueを返します。
	 * 
	 * @return 存在フラグ
	 */
	@Override
	public boolean exists() {
		return isDirectory() | isFile();
	}

	/**
	 * パスのファイルとディレクトリ一覧を取得します。
	 * 
	 * @return ファイルとティレクトリ一覧
	 */
	@Override
//	public FileOperation[] listFiles() {
	public File[] listFiles() {
		return listFiles(new HybsFileFilter());
	}

	/**
	 * パスのファイルとディレクトリ一覧を取得して、
	 * 引数でフィルターを行います。
	 * 
	 * @param filter フィルター
	 * @return	ファイルとディレクトリ一覧
	 */
//	@Override
//	public FileOperation[] listFiles(FileOperationFileFilter filter) {
	public File[] listFiles(FileOperationFileFilter filter) {
		String errMsg = "このクラスでは実装されていません。listFiles(FileOperationFileFilter)";
		throw new UnsupportedOperationException(errMsg);
	}
	
	/**
	 * ディレクトリを作成します。
	 * 
	 * ※１つのディレクトリのみ作成します。
	 * (クラウドストレージにはディレクトリの概念が無いため、
	 * 作成は行わず、trueを返します)
	 * 
	 * @return 成否フラグ
	 */
	@Override
	public boolean mkdir() {
		return true;
	}

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

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

	/**
	 * 親のディレクトリを返します。
	 * 
	 * @return 親のディレクトリ
	 */
	@Override
	public FileOperation getParentFile() {
		return null;
	}

	/**
	 * 書き込み可能フラグ
	 * 
	 * ※クラウドストレージの場合は、
	 * 必ずtrueを返します。
	 * 
	 * @return 書き込み可能フラグ
	 */
	@Override
	public boolean canWrite() {
		return true;
	}

	/**
	 * 読み取り可能フラグ
	 * 
	 * ※クラウドストレージの場合は、
	 * 必ずtrueを返します。
	 * 
	 * @return 読み取り可能フラグ
	 */
	@Override
	public boolean canRead() {
		return true;
	}

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

	/**
	 * 新規ファイル作成
	 * 
	 * 既にファイルが存在しない場合のみ、
	 * 空のファイルを作成します。
	 *
	 * @return 成否フラグ
	 * @throws IOException
	 */
	@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;
	}

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

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

	/**
	 * toStringメソッド
	 * パスを返します。
	 */
	@Override
	public String toString() {
		return path;
	}

	/** 共通関数 **/
	/**
	 * ファイルパスの編集 2018/05/07 ADD
	 * パスの先頭が「/」の場合は「/」の除去と、「//」を「/」に置換処理の追加。
	 * 
	 * @og.rev 5.9.32.1 (2018/05/11)
	 * @param path
	 * @return 変更後パス
	 */
	protected String editPath(final String path) {
		if (StringUtil.isNull(path)) {
			return "";
		}

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

		return rtn;
	}

	/**
	 * キーから親のパスを抽出します。 
	 * 
	 * @param key キー
	 * @return 親のパス
	 */
	protected String drawParent(String key) {
		int k = key.lastIndexOf("/");

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

		return rtn;
	}

	/**
	 * 引数のkeyから名称を抽出します。
	 * 
	 * @param key キー(パス)
	 * @return 名称
	 */
	protected String drawName(String key) {
		int k = key.lastIndexOf("/");

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

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

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

	/**
	 * 右側の文字が、指定の文字の場合、除去します。
	 * 
	 * @param str 対象文字列
	 * @param chr 指定文字
	 * @return 右側から指定文字を除去後の文字列
	 */
	protected String rTrim(final String str, 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;
	}

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

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

	/**
	 * フィルター処理を行う
	 * 
	 * @param list フィルタを行うリスト
	 * @param filter フィルタ情報
	 * @return フィルタ後のリスト
	 */
//	protected FileOperation[] filter(List<FileOperationInfo> list, FileOperationFileFilter filter) {
	protected File[] filter(List<FileOperationInfo> list, FileFilter filter) {
		ArrayList<FileOperationInfo> files = new ArrayList<FileOperationInfo>();
		for (int i = 0; i < list.size(); i++) {
			if (filter.accept(list.get(i))) {
				files.add(list.get(i));
			}
		}
		return files.toArray(new FileOperationInfo[files.size()]);
	}

	/**
	 * InputStreamをbyte[]に変換。
	 * InputStreamのサイズ計算に利用。
	 * 
	 * @param is byte[]変換するInputStream
	 * @return InpusStreamをbyte[]に変換した値
	 */
	protected byte[] toByteArray(InputStream is) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		try {
			byte[] b = new byte[BUFFER_SIZE];
			int n = 0;
			while ((n = is.read(b)) != -1) {
				output.write(b, 0, n);
			}
			return output.toByteArray();
		} finally {
			output.close();
		}
	}
}

