package jp.gr.java_conf.shiseissi.commonlib;

import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

/**
 *
 * @author shisei
 */
public abstract class FileUtil {
    private static final String LOG_TAG = "FileUtil";

    private static final boolean USE_FILE_CHANNEL = false;

    /**
     * ファイルをコピーする
     * @param src コピー元のファイル
     * @param dst コピー先のファイル
     * @throws IOException
     */
    public static void copyFile(File src, File dst) throws IOException {
        InputStream srcStream = null;
        OutputStream dstStream = null;
        try {
            FileInputStream srcFileStream = new FileInputStream(src);
            srcStream = srcFileStream;
            FileChannel srcChannel = srcFileStream.getChannel();
            long srcChannelSize = srcChannel.size();
            if (USE_FILE_CHANNEL) {
                FileOutputStream dstFileStream = new FileOutputStream(dst);
                dstStream = dstFileStream;
                FileChannel dstChannel = dstFileStream.getChannel();
                if (srcChannelSize > 0) {
                    // XXX 中でMappedByteBuffer使うから重い？
                    dstChannel.transferFrom(srcChannel, 0, srcChannelSize);
                }
//                dstFileStream.getFD().sync();
            } else {
                byte[] buf = new byte[8024];
                srcStream = new BufferedInputStream(srcStream);
                RandomAccessFile dstFile = new RandomAccessFile(dst, "rw");
                dstFile.setLength(srcChannelSize);
                dstFile.seek(0);
                while (true) {
                    int readSize = srcStream.read(buf);
                    if (readSize < 0) {
                        break;
                    }
                    dstFile.write(buf, 0, readSize);
                }
            }
        } finally {
            // StreamのcloseでChannelも閉じる
            if (srcStream != null) {
                closeIgnoreException(srcStream);
            }
            if (dstStream != null) {
                closeIgnoreException(dstStream);
            }
        }
    }

    /**
     * ファイルを移動する
     * @param src 移動元のファイル
     * @param dst 移動先のファイル
     */
    public static void moveFile(File src, File dst) {
        if (src.renameTo(dst)) {
            return;
        }

        // リネーム失敗なら実際にコピーしてから元ファイルを削除
        try {
            copyFile(src, dst);
            if (!src.delete()) {
                Log.w(LOG_TAG, "Delete failed: " + dst.getAbsolutePath());
            }
        } catch (IOException e) {
            Log.w(LOG_TAG, e.toString(), e);
        }
    }

    /**
     * {@link Closeable#close} で発生する例外を無視する
     * @param stream
     */
    public static void closeIgnoreException(Closeable stream) {
        try {
            stream.close();
        } catch (IOException e) {
            Log.w(LOG_TAG, Log.buf().append(stream.toString())
                    .append("#close failed: ")
                    .append(e.toString()).toString());
        }
    }

    /**
     * 入力バッファが一杯になるかファイル終端に到達するまで読み込む
     * @see InputStream#read(byte[] buffer, int offset, int length)
     * @param in
     * @param b
     * @param offset
     * @param length
     * @return
     * @throws IOException
     */
    public static int readComplete(InputStream in, byte[] b, int offset, int length) throws IOException {
        int readSize = 0;
        while (readSize < length) {
            int ret = in.read(b, offset + readSize, length - readSize);
            if (ret < 0) {
                return ret;
            }
            readSize += ret;
        }
        assert readSize == length;
        return readSize;
    }

    /**
     * @see FileUtil#readComplete(InputStream in, byte[] b, int offset, int length)
     * @see InputStream#read(byte[] buffer)
     * @param in
     * @param b
     * @return
     * @throws IOException
     */
    public static int readComplete(InputStream in, byte[] b) throws IOException {
        return readComplete(in, b, 0, b.length);
    }

    /**
     * 入力バッファが一杯になるかファイル終端に到達するまで読み込む
     * @see RandomAccessFile#read(byte[] buffer, int offset, int length)
     * @param in
     * @param b
     * @param offset
     * @param length
     * @return
     * @throws IOException
     */
    public static int readComplete(RandomAccessFile in, byte[] b, int offset, int length) throws IOException {
        int readSize = 0;
        while (readSize < length) {
            int ret = in.read(b, offset + readSize, length - readSize);
            if (ret < 0) {
                return ret;
            }
            readSize += ret;
        }
        assert readSize == length;
        return readSize;
    }

    /**
     * @see FileUtil#readComplete(RandomAccessFile in, byte[] b, int offset, int length)
     * @see RandomAccessFile#read(byte[] buffer)
     * @param in
     * @param b
     * @return
     * @throws IOException
     */
    public static int readComplete(RandomAccessFile in, byte[] b) throws IOException {
        return readComplete(in, b, 0, b.length);
    }

    public static int readByte(InputStream in) throws EOFException, IOException {
        int temp = in.read();
        if (temp < 0) {
            throw new EOFException("read byte failed");
        }
        return temp;
    }

    public static int readShortLE(InputStream in) throws EOFException, IOException {
        int temp = in.read();
        if (temp < 0) {
            throw new EOFException("read short failed");
        }
        int ret = (temp & 0xff);
        temp = in.read();
        if (temp < 0) {
            throw new EOFException("read short failed");
        }
        ret |= ((temp & 0xff) << 8);

        return ret;
    }

    public static int readIntLE(InputStream in) throws EOFException, IOException {
        int temp = in.read();
        if (temp < 0) {
            throw new EOFException("read int failed");
        }
        int ret = (temp & 0xff);
        temp = in.read();
        if (temp < 0) {
            throw new EOFException("read int failed");
        }
        ret |= ((temp & 0xff) << 8);
        temp = in.read();
        if (temp < 0) {
            throw new EOFException("read int failed");
        }
        ret |= ((temp & 0xff) << 16);
        temp = in.read();
        if (temp < 0) {
            throw new EOFException("read int failed");
        }
        ret |= ((temp & 0xff) << 24);

        return ret;
    }

    public static int readByte(RandomAccessFile in) throws EOFException, IOException {
        int temp = in.read();
        if (temp < 0) {
            throw new EOFException("read byte failed");
        }
        return temp;
    }

    public static int readShortLE(RandomAccessFile in) throws EOFException, IOException {
        int temp = in.read();
        if (temp < 0) {
            throw new EOFException("read short failed");
        }
        int ret = (temp & 0xff);
        temp = in.read();
        if (temp < 0) {
            throw new EOFException("read short failed");
        }
        ret |= ((temp & 0xff) << 8);

        return ret;
    }

    public static int readIntLE(RandomAccessFile in) throws EOFException, IOException {
        int temp = in.read();
        if (temp < 0) {
            throw new EOFException("read int failed");
        }
        int ret = (temp & 0xff);
        temp = in.read();
        if (temp < 0) {
            throw new EOFException("read int failed");
        }
        ret |= ((temp & 0xff) << 8);
        temp = in.read();
        if (temp < 0) {
            throw new EOFException("read int failed");
        }
        ret |= ((temp & 0xff) << 16);
        temp = in.read();
        if (temp < 0) {
            throw new EOFException("read int failed");
        }
        ret |= ((temp & 0xff) << 24);

        return ret;
    }

}
