package jp.agentec.sinaburocast.common.io;

import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;

import jp.agentec.sinaburocast.common.SinaburoConstant.Formats;
import jp.agentec.sinaburocast.common.util.SinaburoUtil;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.struts.upload.FormFile;
import org.seasar.framework.util.StringUtil;
import org.seasar.struts.util.ResponseUtil;

/**
 * FileUtilクラス<br>
 * ファイル操作に関する基本的なメソッドを集めたクラス。<br>
 * Java標準のFileクラスの結果を修正し、<br>
 * 結果としてほしい状態になった場合はtrueを返し、そうでない場合はfalseを返す。<br>
 * (deleteメソッドですでに削除されている場合もtrueを返す)<br>
 * voidの場合、失敗した場合はExceptionをスローする。<br>
 *
 * @author tsukada
 *
 */
public class FileUtil {
	private static final Logger LOGGER = Logger.getLogger(FileUtil.class);

	/**
	 * patternListにマッチするパス直下のファイルリストを返す。
	 * @param path
	 * @param patternList
	 * @return
	 * @throws FileNotFoundException
	 */
	public static List<String> ls(String path, String[] patternList) throws FileNotFoundException{

		String[] fileList = ls(path);

		List<String> reList = new ArrayList<String>();
		for (String name : fileList) {
			for (String pattern : patternList) {
				if (StringUtil.endsWithIgnoreCase(name, pattern)) {
					reList.add(name);
				}
			}
		}
		return reList;
	}

	/**
	 * patternにマッチするパス直下のファイルリストを返す。
	 * @param path
	 * @param pattern
	 * @return
	 * @throws FileNotFoundException
	 */
	public static List<String> ls(String path, String pattern) throws FileNotFoundException{
		String[] list = ls(path);
		List<String> reList = new ArrayList<String>();
		Pattern p = Pattern.compile(pattern);
		for (String name : list) {
			Matcher m = p.matcher(name);	// LATER: オプションCASE_INSENSITIVEをつけるべき
			if (m.matches()) {
				reList.add(name);
			}
		}
		return reList;
	}

	/**
	 * パス直下のファイルリストを返す。
	 *
	 * @param path
	 * @return
	 * @throws FileNotFoundException
	 */
	public static String[] ls(String path) throws FileNotFoundException {
		check(path);
		File file = new File(path);
		if (file.exists()) {
			if (file.isDirectory()) {
				return file.list();
			}
		}
		else {
			throw new FileNotFoundException("No such file: " + path);
		}
		return new String[0];
	}

	/**
	 * ファイルフィルタを利用してファイルリストを取得します。
	 *
	 * @param path
	 * @param fileNameFilter
	 * @return
	 * @throws FileNotFoundException
	 */
	public static String[] ls(String path, FilenameFilter fileNameFilter, int mode) throws FileNotFoundException {

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("パス: " + path);
		}

		check(path);
		File file = new File(path);
		if (file.exists()) {
			if (file.isDirectory()) {
				String[] list = file.list(fileNameFilter);
				List<String> res = new ArrayList<String>();
				File resFile = null;

				// 対象の設定によって結果を返す。
				// 0: ファイルとディレクトリ、両方を配列に追加する。
				// 1: ファイルだけ配列に追加する。
				// 2: ディレクトリだけ配列に追加する。
				switch (mode) {
					case 1:
						for (String fileName : list) {
							resFile = new File(String.format("%s%s%s", path, File.separator, fileName));
							if (resFile.isFile()) {
								res.add(fileName);
							}
						}
						break;
					case 2:
						for (String fileName : list) {
							resFile = new File(String.format("%s%s%s", path, File.separator, fileName));
							if (resFile.isDirectory()) {
								res.add(fileName);
							}
						}
						break;
					default:
						return list;
				}

				// 取得したリストがなかった場合
				if (res == null || res != null && res.size() == 0) {
					return new String[0];
				}

				return res.toArray(new String[res.size()]);
			}
		} else {
			throw new FileNotFoundException("No such file: " + path);
		}
		return new String[0];
	}

	/**
	 * パスが存在しているかどうかを返す。
	 *
	 * @param path
	 * @return
	 */
	public static boolean exists(String path) {
		check(path);
		return new File(path).exists();
	}

	/**
	 * ディレクトリを作成する<br>
	 * 作成に失敗した場合falseを返す。すでにあった場合はtrue
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static boolean mkdir(String path) {
		check(path);
		File file = new File(path);
		try {
			FileUtils.forceMkdir(file);
		} catch (IOException e) {
			LOGGER.fatal("mkdir failed. dir=" + path, e);
		}
		return file.exists();
	}

	/**
	 * ファイルを移動する<br>
	 * srcPathが存在しない場合に、FileNotFoundExceptionをthrowする。<br>
	 * 移動先にファイルがある場合は上書きする。
	 *
	 * @param srcPath
	 * @param destPath
	 * @return
	 * @throws IOException
	 */
	public static boolean move(String srcPath, String destPath) throws IOException {
		check(srcPath, destPath);
		return move(new File(srcPath), new File(destPath));
	}

	public static boolean move(File srcFile, File dstFile) throws IOException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("file move from " + srcFile.getAbsolutePath() + " to " + dstFile.getAbsolutePath());
		}
		if (!srcFile.exists()) {
			LOGGER.warn("No such file: " + srcFile.getAbsolutePath());
			throw new FileNotFoundException("No such file: " + SinaburoUtil.getFileName(srcFile.getAbsolutePath()));
		}
		if (srcFile.isFile()) {
			FileUtils.copyFile(srcFile, dstFile);
			srcFile.delete();
		}
		else {
			FileUtils.copyDirectory(srcFile, dstFile);
			FileUtils.deleteDirectory(srcFile);
		}
		return !srcFile.exists();
	}

	/**
	 * ファイルを移動する。
	 * 移動先ディレクトリが存在しない場合作成する。
	 *
	 * @param srcFile
	 * @param dstFile
	 * @return
	 * @throws IOException
	 */
	public static boolean moveFile(File srcFile, File dstFile) throws IOException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("file move from " + srcFile.getAbsolutePath() + " to " + dstFile.getAbsolutePath());
		}
		if (!srcFile.exists()) {
			LOGGER.warn("No such file: " + srcFile.getAbsolutePath());
			throw new FileNotFoundException("No such file: " + SinaburoUtil.getFileName(srcFile.getAbsolutePath()));
		}
		if (!srcFile.isFile()) {
        	throw new IOException("source file is not a file.");
		}

        if (dstFile.getParentFile() != null && !dstFile.getParentFile().exists()) {
        	LOGGER.info("create dest dir. [" + dstFile.getParentFile().getAbsolutePath() +"]");
        	dstFile.getParentFile().mkdirs();
        }
        if (dstFile.exists() && dstFile.isDirectory()) {
            throw new IOException("Destination '" + dstFile + "' exists but is a directory");
        }

        FileInputStream input = new FileInputStream(srcFile);
        try {
            FileOutputStream output = new FileOutputStream(dstFile);
            try {
                IOUtils.copy(input, output);
            } finally {
                IOUtils.closeQuietly(output);
            }
        } finally {
            IOUtils.closeQuietly(input);
        }

        if (srcFile.length() != dstFile.length()) {
            throw new IOException("Failed to copy full contents from '" +
                    srcFile + "' to '" + dstFile + "'");
        }
        dstFile.setLastModified(srcFile.lastModified());

		return srcFile.delete();
	}

	/**
	 * ファイルを移動する<br>
	 * srcPathが存在しない場合に、FileNotFoundExceptionをthrowする。<br>
	 * ただし、そのチェック後remaveTo実行時までに別プロセスにより削除された場合は<br>
	 * falseが返却される。
	 *
	 * @param srcPath
	 * @param destPath
	 * @return
	 * @throws FileNotFoundException
	 */
	public static boolean rename(String srcPath, String destPath) throws FileNotFoundException {
		check(srcPath, destPath);
		return rename(new File(srcPath), new File(destPath));
	}

	public static boolean rename(File srcFile, File dstFile) throws FileNotFoundException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("file rename from " + srcFile.getAbsolutePath() + " to " + dstFile.getAbsolutePath());
		}
		if (!srcFile.exists()) {
			LOGGER.warn("No such file: " + srcFile.getAbsolutePath());
			throw new FileNotFoundException("No such file: " + SinaburoUtil.getFileName(srcFile.getAbsolutePath()));
		}
		if (dstFile.exists()) {
			dstFile.delete();
		}
		return srcFile.renameTo(dstFile);
	}

	/**
	 * ディレクトリか否かを返す。
	 *
	 * @param path
	 * @return
	 */
	public static boolean isDirectory(String path) {
		check(path);
		File file = new File(path);
		if (!file.exists()) return false;
		return file.isDirectory();
	}

	/**
	 * ファイルか否かを返す。
	 *
	 * @param path
	 * @return
	 */
	public static boolean isFile(String path) {
		check(path);
		File file = new File(path);
		if (!file.exists()) return false;
		return file.isFile();
	}

	/**
	 * ファイルを削除する<br>
	 *
	 * @param path
	 * @return 削除成功時true 存在しない場合もtrueを返す
	 */
	public static boolean deleteFile(String path) {
		check(path);
		File file = new File(path);
		if (!file.exists()) {
			return true;
		}
		return file.delete();
	}

	/**
	 * ディレクトリ内のファイル・サブディレクトリを削除する.
	 *
	 * NFSのシステム一時ファイルが残存している場合は無視して処理を続行する。エラーとはしない。
	 *
	 * @param dir
	 * @return 削除成功ならtrue, 削除できなかった場合はfalse
	 * @throws IOException NFS一時ファイル以外の原因で削除に失敗した場合
	 */
	private static boolean cleanDirectoryGracefully(File dir) throws IOException {
		boolean result = true;

        if (!dir.exists()) {
            throw new IllegalArgumentException(dir.getPath() + " does not exist");
        }

        if (!dir.isDirectory()) {
            throw new IllegalArgumentException(dir.getPath() + " is not a directory");
        }

        File[] files = dir.listFiles();
        if (files == null) {  // null if security restricted
            throw new IOException("Failed to list contents of " + dir);
        }

        IOException exception = null;
        for (File file : files) {
            try {
            	if (file.isDirectory()) {
            		if (!deleteDirectoryGracefully(file)) {
            			result = false;
            		}
            	} else {
        			if (file.getName().startsWith(".nfs")) {
        				result = false;
        			} else if (!file.delete()) {
        				throw new IOException("Unable to delete file: " + file);
            		}
            	}
            } catch (IOException ioe) {
                exception = ioe;
            }
        }

        if (exception != null) {
            throw exception;
        }
        return	result;
	}

	/**
	 * ディレクトリを削除する.
	 *
	 * NFSのシステム一時ファイルが残存している場合は無視して処理を続行する。エラーとはしない。
	 *
	 * @param dir
	 * @return 削除成功ならtrue, 削除できなかった場合はfalse
	 * @throws IOException NFS一時ファイル以外の原因で削除に失敗した場合
	 */
	public static boolean deleteDirectoryGracefully(File dir) throws IOException {
		if (!dir.exists() || !dir.isDirectory())	return true;

		if (!cleanDirectoryGracefully(dir)) {
			return	false;
		}

		if (dir.listFiles().length > 0){
			return false;
		} else {
			if (dir.delete()) {
				return	true;
			} else {
				throw new IOException("Failed to delete directory " + dir.getPath() + " gracefully.");
			}
		}
	}

	/**
	 * ディレクトリを削除する
	 * @param dir
	 * @throws IOException
	 */
	public static void deleteDirectory(String dir) throws IOException {
		deleteDirectory(dir, true);
	}

	/**
	 * ディレクトリを削除する
	 *
	 * @param dir
	 * @param force falseのときからの場合削除しない
	 * @throws IOException
	 */
	public static void deleteDirectory(String dir, boolean force) throws IOException {
		File file = new File(dir);
		if (force ||
				file.exists() && file.isDirectory() && file.list().length == 0) {
			FileUtils.deleteDirectory(file);
		}
	}

	/**
	 * 指定されたパスを削除する<br>
	 * ファイルかディレクトリを自動的に判別して削除する。<br>
	 *
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static boolean delete(String path) throws IOException {
		File file = new File(path);
		if (!file.exists()) {
			return true;
		}
		if (file.isDirectory()) {
			FileUtils.deleteDirectory(file);
		}
		else {
			if (!file.delete())	return	false;
		}
		return !file.exists();
	}


	/**
	 * 指定されたパスを削除する.
	 *
	 * ファイルかディレクトリを自動的に判別して削除する。<br>
	 * NFSのシステム一時ファイルが残存している場合は無視して処理を続行する。エラーとはしない。
	 *
	 * @param path
	 * @return 削除成功ならtrue, 削除できなかった場合はfalse
	 * @throws IOException NFS一時ファイル以外の原因で削除に失敗した場合
	 */
	public static boolean deleteGracefully(String path) throws IOException {
		File file = new File(path);
		if (!file.exists())	return true;

		if (file.isDirectory()) {
			return	deleteDirectoryGracefully(file);
		} else {
			if (!file.delete())	return	false;
		}
		return !file.exists();
	}


	/**
	 * ファイルサイズを返す。
	 *
	 * @param path
	 * @return  ファイルが存在しない場合 0L
	 */
	public static long length(String path) {
		check(path);
		return new File(path).length();
	}

	/**
	 * テキストファイルであるか否かを返す。<br>
	 * x08未満のバイトが存在するかどうかで判定
	 *
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static boolean isTextFile(String path) throws IOException {
		check(path);
		File file = new File(path);
		if (file.isDirectory()) {
			return false;
		}
		long CHECK_LENGTH = 1000;
		FileInputStream fis = new FileInputStream(file);
		int c;
		int i = 0;
		while ((c = fis.read()) != -1) {
			i++;
			if (c < 8) {
				fis.close();
				return false;
			}
			else if (i > CHECK_LENGTH) {
				break;
			}
		}
		fis.close();
		return true;
	}


	/**
	 * ファイルをコピーする。
	 *
	 * @param srcPath
	 * @param destPath
	 * @throws IOException
	 */
	public static void copy(String srcPath, String destPath) throws IOException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("file copy from " + srcPath + " to " + destPath);
		}
		check(srcPath, destPath);
		FileUtils.copyFile(new File(srcPath), new File(destPath));
	}

	/**
	 * 空ファイルを作成する。<br>
	 * ファイルがすでに存在する場合何もしない。
	 *
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static boolean createFile(String path) throws IOException {
		check(path);
		File file = new File(path);
		if (!file.exists()) {
			File parent = new File(SinaburoUtil.getParentPath(path));
			if (!parent.exists()) {
				parent.mkdirs();
			}
			file.createNewFile();
		}
		return file.exists();
	}

	/**
	 * 指定内容のテキストファイルを作成する。
	 *
	 * @param path
	 * @param content
	 * @throws IOException
	 */
	public static void createFile(String path, String content) throws IOException {
		check(path);
		FileOutputStream out = null;
		try {
			File file = new File(path);
			if (!file.getParentFile().exists()) {
				file.getParentFile().mkdirs();
			}
			out = new FileOutputStream(file);
			out.write(content.getBytes());
			out.flush();
		}
		finally {
			if (out != null) {
				out.close();
			}
		}
	}
	
	/**
	 * 指定内容のテキストファイルを作成する。
	 *
	 * @param path
	 * @param content
	 * @throws IOException
	 */
	public static void writeFile(String path, String content,String charSet,boolean append) throws IOException {
		
		check(path);
		if(StringUtils.isBlank(charSet)){
			charSet="UTF-8";
		}
		OutputStreamWriter out = null;
		try {
			File file = new File(path);
			if (!file.getParentFile().exists()) {
				file.getParentFile().mkdirs();
			}
			out = new OutputStreamWriter(new FileOutputStream(file,append),charSet);
			out.write(content);
			out.flush();
		}
		finally {
			if (out != null) {
				out.close();
			}
		}
	}	
	private static void check(String... path) {
		for (String str : path) {
			check(str);
		}
	}

	private static void check(String path) {
		if (StringUtils.isEmpty(path)) {
			throw new RuntimeException("path is required.");
		}
	}

	public static String getCurrentPath() {
		return new File(".").getAbsoluteFile().getParent();
	}

	public static boolean isImageFile(String path) throws IOException{
		return isImageFile(new File(path));
	}

	/**
	 * ファイルがイメージファイルかどうかを判定します。<br>
	 * イメージファイルではない場合はfalseを返却します。
	 * ファイルが存在しない場合、ファイルではない場合、イメージとして読み込めなかった場合は例外をthrowします。
	 * @param file
	 * @return
	 * @throws IOException
	 */
	public static boolean isImageFile(File file) throws IOException{
		if(!file.exists() || file.isDirectory()){
			throw new IOException();
		}
		BufferedImage readImage = null;
		readImage = ImageIO.read(file);

		return readImage != null;
	}


	/**
	 * ファイルサイズ取得
	 * @param filePath ファイルパス
	 * @return
	 */
	public static long getFileSize(String filePath){
		File file = new File(filePath);
		return file.length();
	}

	/**
	 * ファイルの存在チェック<br>
	 * ファイルが存在しないか、ファイルではない場合にfalseを返却します
	 * @param filePath
	 * @return
	 */
	public static boolean checkFileExists(String filePath){
		//ファイルの存在チェック
		File file = new File(filePath);
		if (!file.exists() || !file.isFile()) {
			return false;
		}
		return true;
	}

	/**
	 * ファイルアップロード<br>
	 * 指定されたディレクトリにファイルをアップロードします。
	 * @param path アップロード先パス(ファイル名まで)
	 * @param file ファイル
	 * @return 結果(true:アップロード成功  false:アップロード失敗)
	 */
	public static boolean upload(String path, FormFile file){
		if (file.getFileSize() == 0) {
            return false;
        }
        try {
            OutputStream out = new BufferedOutputStream(
            		new FileOutputStream(path));
            try {
                out.write(file.getFileData(), 0, file.getFileSize());
            } finally {
                out.close();
            }
        } catch (IOException e) {
			LOGGER.error("upload error.", e);
        	return false;
        }
        return true;
    }

	/**
	 * ファイルの内容を返す。
	 * trim指定のとき、改行・前後の空白を削除。
	 *
	 * @param reader
	 * @param trim
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static StringBuffer readFileAsStringBuffer(Reader reader, boolean trim) throws FileNotFoundException, IOException {
        StringBuffer sb = new StringBuffer();
		BufferedReader br = new BufferedReader(reader);
        String line = null;
        while ((line = br.readLine()) != null) {
        	sb.append(trim? line.trim(): line + Formats.RETURN);
        }
        return sb;
	}

	/**
	 * パラメータで貰ったディレクトリやファイル名を繋いで経路を返す。
	 * @param basePath
	 * @param middlePath
	 * @param fileName
	 * @param delimiter
	 * @return
	 */
	public static String manipulatePath(String basePath, String middlePath, String fileName, String delimiter) {

		String path = basePath;

		if (StringUtils.isNotEmpty(middlePath)) {
			path = String.format("%s%s%s", path, delimiter, middlePath);
		}

		if (StringUtils.isNotEmpty(fileName)) {
			path = String.format("%s%s%s", path, delimiter, fileName);
		}

		return path;
	}

	/**
	 * 配列で貰ったディレクトリを繋いで経路を返す。
	 *
	 * @param add
	 * @param delimiter
	 * @param descIdx
	 * @return
	 */
	public static String concatPathByRecursive(String[] dir, String delimiter, int descIdx, int endIdx) {
		if (descIdx == endIdx) {
			return dir[descIdx];
		}

		return String.format("%s%s%s", concatPathByRecursive(dir, delimiter, descIdx - 1, endIdx), delimiter, dir[descIdx]);
	}

	/**
	 * シンボリックリンクかどうかを返す。
	 * (Linuxでのみ有効)
	 *
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static boolean isSymbolicLink(String path) throws IOException {
		if (path == null) {
			throw new FileNotFoundException(path);
		}

		File file = new File(path);
		if (!file.exists()) {
			LOGGER.warn("file is already deleted. [" + path + "]");
			return true;
		}

		return !file.getAbsolutePath().equals(file.getCanonicalPath());
	}

	public static byte[] convertFileToByteArray(File file) throws IOException {
		byte[] byteArray = new byte[(int) file.length()];
		FileInputStream fileInputStream = new FileInputStream(file);
		fileInputStream.read(byteArray);
		return byteArray;
	}

	public static File convertByteArrayToFile(byte[] byteArray, File file) {
		try {
			FileOutputStream fileOutputStream = new FileOutputStream(file);
			fileOutputStream.write(byteArray);
			fileOutputStream.flush();
			fileOutputStream.close();
		} catch (Exception e){
			LOGGER.error("convertByteArrayToFile error" + file.getAbsolutePath(), e);
			return null;
		}
		return file;
	}

	/**
	 * 拡張子がリストに含まれるかどうかを返す。
	 *
	 * @param filename
	 * @param list
	 * @return
	 */
	public static boolean containsExtension(String filename, List<String> list) {
		String ext = SinaburoUtil.getExtension(filename);
		return list.contains(ext.toLowerCase());
	}
	
	/**
	 * ファイルダウンロード
	 * @param filename   指定したファイルをダウンロードする。
	 * @param encodeing  指定したencodeで読み込む。
	 * @throws UnsupportedEncodingException
	 * @throws IOException
	 */
	public static void fileDownload(String filename,String encodeing) throws UnsupportedEncodingException, IOException{
		
		encodeing = StringUtil.isBlank(encodeing) ? "UTF-8":encodeing;
		File file = new File(filename);
		InputStream in = new FileInputStream(file);
		 
//		StringBuffer content = FileUtil.readFileAsStringBuffer(new InputStreamReader(in,encodeing), false);
//		ResponseUtil.download(URLEncoder.encode(file.getName(), "UTF-8"), FileUtil.convertFileToByteArray(file));
		
		ResponseUtil.download(URLEncoder.encode(file.getName(), encodeing), in);

		
	}
	
}