package net.argius.filetool;

import java.io.*;

/**
 * 入出力ユーティリティ。
 */
public final class IOUtilities {

    private static int bufferSize = 8192;

    /**
     * IOUtilitiesの生成。
     */
    private IOUtilities() {
        // empty
    }

    /**
     * バッファサイズを設定する。
     * @param size サイズ
     */
    public static void setBufferSize(int size) {
        if (size < 1) {
            throw new IllegalArgumentException("invalid size: " + size);
        }
        bufferSize = size;
    }

    /**
     * ファイルを移動する。
     * @param src 移動元ファイル
     * @param dst 移動先ファイル
     * @param overwrite 上書きする場合は <code>true</code> 、
     *                  上書きしない場合は <code>false</code>
     * @return ファイルの移動に成功した場合は <code>true</code> 、
     *                         失敗した場合は <code>false</code>
     * @throws IOException 入出力エラーが発生した場合
     */
    public static boolean move(File src, File dst, boolean overwrite) throws IOException {
        if (dst.exists()) {
            if (overwrite) {
                dst.delete();
            } else {
                throw new IOException("file already exists: " + dst);
            }
        }
        mkdirsIfNotExists(dst);
        return src.renameTo(dst);
    }

    /**
     * ファイルをコピーする。
     * @param src コピー元ファイル
     * @param dst コピー先ファイル
     * @param overwrite 上書きする場合は <code>true</code> 、
     *                  上書きしない場合は <code>false</code>
     * @throws IOException 入出力エラーが発生した場合
     */
    public static void copy(File src, File dst, boolean overwrite) throws IOException {
        if (dst.exists()) {
            if (!overwrite) {
                throw new IOException("file already exists: " + dst);
            }
        }
        copy(src, dst);
    }

    /**
     * ファイルをコピーする。
     * @param src コピー元ファイル
     * @param dst コピー先ファイル
     * @return コピーされたデータのサイズ
     * @throws IOException 入出力エラーが発生した場合
     */
    public static long copy(File src, File dst) throws IOException {
        long modifiedTime = src.lastModified();
        mkdirsIfNotExists(dst);
        FileOutputStream fos = new FileOutputStream(dst);
        try {
            FileInputStream fis = new FileInputStream(src);
            try {
                long size = transferAll(fis, fos);
                dst.setLastModified(modifiedTime);
                return size;
            } finally {
                fis.close();
            }
        } finally {
            fos.close();
        }
    }

    /**
     * ストリームデータを転送する。
     * @param is 転送元入力ストリーム
     * @param os 転送先出力ストリーム
     * @param length 転送するデータ長
     * @return 転送されたデータのサイズ
     * @throws IOException 入出力エラーが発生した場合
     */
    public static long transfer(InputStream is, OutputStream os, long length) throws IOException {
        long rest = length;
        long totalSize = 0;
        int bufferSize = IOUtilities.bufferSize;
        byte[] buffer = new byte[bufferSize];
        while (rest > 0) {
            int requestLength = Math.min((int)rest, bufferSize);
            int readLength = is.read(buffer, 0, requestLength);
            if (readLength < 0) {
                break;
            }
            os.write(buffer, 0, readLength);
            rest -= readLength;
            totalSize += readLength;
        }
        os.flush();
        return totalSize;
    }

    /**
     * ストリームデータを全て転送する。
     * @param is 転送元入力ストリーム
     * @param os 転送先出力ストリーム
     * @return 転送されたデータのサイズ
     * @throws IOException 入出力エラーが発生した場合
     */
    public static long transferAll(InputStream is, OutputStream os) throws IOException {
        long totalSize = 0;
        byte[] buffer = new byte[bufferSize];
        for (int readLength; (readLength = is.read(buffer)) >= 0;) {
            os.write(buffer, 0, readLength);
            totalSize += readLength;
        }
        os.flush();
        return totalSize;
    }

    /**
     * ファイルのディレクトリが存在しない場合に作成する。
     * @param file ファイル
     * @throws IOException 入出力エラーが発生した場合 
     */
    private static void mkdirsIfNotExists(File file) throws IOException {
        File parent = file.getParentFile();
        if (parent != null && !parent.exists()) {
            if (!parent.mkdirs()) {
                throw new IOException("failed to mkdir: " + parent);
            }
        }
    }

}
