package pers.hai.util.commons.io;

import pers.hai.util.commons.str.StringUtils;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 文件读写工具类
 *
 * Create Time: 18:18 2019/05/15
 * Last Modify: 2019/05/15
 *
 * @author Q-WHai
 * @see <a href="https://github.com/qwhai">https://github.com/qwhai</a>
 */
public final class FileIO {

    /**
     * 按行读取文件
     *
     * @param   path
     *          文件路径
     *
     * @return  文件中的所有行
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static List<String> readLines(String path) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
        String line;

        List<String> result = new ArrayList<>();
        while ((line = in.readLine()) != null) {
            result.add(line);
        }

        in.close();

        return result;
    }

    /**
     * 读取文件中所有字节
     *
     * @param   path
     *          文件路径
     *
     * @return  字节数据
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static byte[] read(String path) throws IOException {

        BufferedInputStream in = new BufferedInputStream(new FileInputStream(path));

        byte[] bytes = new byte[2048];
        int n;
        List<Byte> data = new ArrayList<>();
        while ((n = in.read(bytes, 0, bytes.length)) != -1) {
            for (int i = 0; i < n; i++) {
                data.add(bytes[i]);
            }
        }

        in.close();

        byte[] result = new byte[data.size()];
        for (int i = 0; i < data.size(); i++) {
            result[i] = data.get(i);
        }

        return result;
    }

    /**
     * 读取文件中的内容,以字符串形式保存
     *
     * @param   path
     *          文件路径
     *
     * @return  文件文件信息
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static String read2String(String path) throws IOException {
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(path));

        byte[] bytes = new byte[2048];
        int n;
        StringBuilder sb = new StringBuilder();
        while ((n = in.read(bytes, 0, bytes.length)) != -1) {
            sb.append(new String(bytes, 0, n, StandardCharsets.UTF_8));
        }

        in.close();

        return sb.toString();
    }

    /**
     * 以覆盖形式写文件
     *
     * @param   path
     *          文件目录
     *
     * @param   data
     *          写入数据
     *
     * @return  true:写入成功 / false:写入失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean write(String path, byte[] data) throws IOException {
        if (!exists(path))
            createNewFile(path);

        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(path));
        for (int i = 0; i < data.length; i += 1024) {
            out.write(Arrays.copyOfRange(data, i * 1024, Math.min((i + 1) * 1024, data.length)));
        }

        out.flush();
        out.close();

        return true;
    }

    /**
     * 以覆盖形式写文件
     *
     * @param   path
     *          文件目录
     *
     * @param   content
     *          写入内容
     *
     * @return  true:写入成功 / false:写入失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean write(String path, String content) throws IOException {
        return write(path, content.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 以追加形式写文件
     *
     * @param   path
     *          文件路径
     *
     * @param   data
     *          追加数据
     *
     * @return  true:追加成功 / false:追加失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean append(String path, byte[] data) throws IOException {
        if (!exists(path))
            createNewFile(path);

        try (FileOutputStream fos = new FileOutputStream(path, true)) {
            fos.write(data);
        }

        return true;
    }

    /**
     * 以追加形式写文件
     *
     * @param   path
     *          文件路径
     *
     * @param   content
     *          追加内容
     *
     * @return  true:追加成功 / false:追加失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean append(String path, String content) throws IOException {
        return append(path, content.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 在文件指定位置插入内容
     *
     * @param   path
     *          文件路径
     *
     * @param   data
     *          待插入数据
     *
     * @param   pos
     *          待插入位置
     *
     * @return  true:追加成功 / false:追加失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean insert(String path, byte[] data, int pos) throws IOException {
        // 参数校验
        File file = new File(path);
        // 判断文件是否存在
        if (!(file.exists() && file.isFile()))
            throw new IOException("文件不存在");

        // 判断pos是否合法
        if ((pos < 0) || (pos > file.length()))
            throw new IOException("position不合法");

        // 创建临时文件
        File tempFile = File.createTempFile("sss", ".temp", new File("d:/"));
        // 用文件输入流、文件输出流对文件进行操作
        FileOutputStream outputStream = new FileOutputStream(tempFile);
        FileInputStream inputStream = new FileInputStream(tempFile);
        // 在退出JVM退出时自动删除
        tempFile.deleteOnExit();

        // 创建RandomAccessFile流
        RandomAccessFile raf = new RandomAccessFile(file, "rw");
        // 文件指定位置到 pos
        raf.seek(pos);

        int tmp;
        // 将pos位置后的内容写入临时文件
        while (-1 != (tmp = raf.read()))
            outputStream.write(tmp);

        // 将追加内容 contents 写入 pos 位置
        raf.seek(pos);
        raf.write(data);

        // 将临时文件写回文件,并将创建的流关闭
        while ((tmp = inputStream.read()) != -1)
            raf.write(tmp);

        raf.close();
        outputStream.close();
        inputStream.close();

        return true;
    }

    /**
     * 在文件指定位置插入内容
     *
     * @param   path
     *          文件路径
     *
     * @param   content
     *          待插入内容
     *
     * @param   pos
     *          待插入位置
     *
     * @return  true:追加成功 / false:追加失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean insert(String path, String content, int pos) throws IOException {
        return insert(path, content.getBytes(StandardCharsets.UTF_8), pos);
    }

    /**
     * 替换文件中的内容
     *
     * @param   path
     *          文件路径
     *
     * @param   data
     *          待替换的数据
     *
     * @param   pos
     *          起始位置
     *
     * @return  true: 成功 / false:失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean replace(String path, byte[] data, int pos) throws IOException {
        RandomAccessFile file = new RandomAccessFile(path, "rw");
        file.seek(pos);
        file.write(data);
        file.close();

        return true;
    }

    /**
     * 替换文件中的内容
     *
     * @param   path
     *          文件路径
     *
     * @param   data
     *          待替换的数据
     *
     * @return  true: 成功 / false:失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean replace(String path, byte[] data) throws IOException {
        return replace(path, data, 0);
    }

    /**
     * 复制文件
     *
     * @param   source
     *          原文件路径
     *
     * @param   target
     *          目标文件路径
     *
     * @param   type
     *          复制方法类型
     *
     * @return  true: 成功 / false:失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean copy(String source, String target, CopyType type) throws IOException {
        switch (type) {
            case BUFFER:
                return copyByBuffer(source,target);
            case TRANSFER:
                return copyByTransfer(source, target);
            case MAPPING:
                return copyByMapped(source,target);
            default:
                return copyByBuffer(source,target);
        }
    }

    /**
     * 复制文件
     *
     * @param   source
     *          原文件
     *
     * @param   target
     *          目标文件
     *
     * @return  true: 成功 / false:失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean copy(String source, String target) throws IOException {
        return copy(source, target, CopyType.BUFFER);
    }

    /**
     * 判别文件是否存在
     *
     * @param   path
     *          文件目录或文件夹目录
     *
     * @return  true:存在 / false:不存在
     */
    public static boolean exists(String path) {
        return new File(path).exists();
    }

    /**
     * 创建一个新文件
     *
     * @param   path
     *          文件路径
     *
     * @return  true:创建成功 / false:创建失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean createNewFile(String path) throws IOException {
        return new File(path).createNewFile();
    }

    /**
     * 创建一个文件夹目录
     * 这个是一个绝对的创建过程,包括父目录不存在的情况(如果父目录不存在,父目录也会被创建)
     *
     * @param   dir
     *          文件夹名称
     *
     * @return  是否创建成功
     */
    public static boolean createNewFolder(String dir) {
        File folder = new File(dir);
        return (folder.exists() && folder.isDirectory()) || folder.mkdirs();
    }

    /**
     * 文件最后一次被修改的时间
     *
     * @param   path
     *          文件路径
     *
     * @return  毫秒时间戳
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static long lastModified(String path) throws IOException {
        File file = new File(path);
        if (!file.exists())
            throw new IOException(String.format("文件%s不存在", path));

        return file.lastModified();
    }

    /**
     * 获得文件夹下的所有一级文件列表
     *
     * @param   dir
     *          文件目录
     *
     * @return  文件目录下的所有文件
     */
    public static File[] listFiles(String dir) {
        File folder = new File(dir);
        return !folder.exists() ? null : folder.isFile() ? null : folder.listFiles();
    }

    /**
     * 判断文件内容是否为空
     *
     * @param   path
     *          文件路径
     *
     * @return  是否为空文件
     *
     * @throws  IOException
     *          文件读写异常
     */
    public static boolean isEmptyFile(String path) throws IOException {
        File file = new File(path);
        if (!file.exists())
            throw new IOException(String.format("文件%s不存在", path));

        return 0 == file.length();
    }

    /**
     * 获得文件扩展名
     *
     * @param   path
     *          完整文件名
     *
     * @return  后缀名
     */
    public static String extension(String path) {
        return StringUtils.RegexUtils.getSub(path, "\\.[\\w]+$");
    }

    /**
     * 替换文件的扩展名
     *
     * @param   path
     *          完整的文件名
     *
     * @param   extension
     *          扩展名(包含'.'号)
     *
     * @return  文件名
     */
    public static String replaceExtension(String path, String extension) {
        return path.replace(extension(path), extension);
    }

    /**
     * 删除文件
     *
     * @param   path
     *          文件路径
     *
     * @return  true:成功 / false:失败
     */
    public static boolean deleteFile(String path) {
        File file = new File(path);
        if (!file.exists())
            return false;

        return file.delete();
    }

    /**
     * 删除一个非空的文件夹
     *
     * @param   dir
     *          待删除文件夹
     *
     * @return  true:删除成功 / false:删除失败
     */
    public static boolean deleteFolder(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            if (null == children)
                return false;

            // 递归删除目录中的子目录下
            for (String child : children) {
                boolean success = deleteFolder(new File(dir, child));
                if (!success)
                    return false;
            }
        }

        // 目录此时为空,可以删除
        return dir.delete();
    }

    /**
     * 对文件进行重命名
     *
     * @param   oldName
     *          原名称
     *
     * @param   newName
     *          新名称
     *
     * @return  true:重命名成功 / false:重命名失败
     */
    public static boolean rename(String oldName, String newName) {
        File file = new File(oldName);
        return file.renameTo(new File(newName));
    }

    // ------------------------------------------------- 内部方法分隔线 --------------------------------------------------

    /**
     * 复制文件
     *
     * @param   source
     *          原文件
     *
     * @param   target
     *          目标文件
     *
     * @throws  IOException
     *          读写异常
     */
    private static boolean copyByTransfer(File source, File target) throws IOException {
        if (null == source || null == target)
            throw new IOException("文件对象为空");

        if (!source.exists())
            throw new IOException("原文件不存在");

        FileChannel in = new FileInputStream(source).getChannel();
        FileChannel out = new FileOutputStream(target).getChannel();

        in.transferTo(0, in.size(), out);

        in.close();
        out.close();

        return true;
    }

    /**
     * 复制文件(Transfer)
     *
     * @param   source
     *          原文件
     *
     * @param   target
     *          目标文件
     *
     * @return  true:成功 / false:失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    private static boolean copyByTransfer(String source, String target) throws IOException {
        if (StringUtils.isEmpty(source) || StringUtils.isEmpty(target))
            throw new IOException("文件名不能为空");

        return copyByTransfer(new File(source), new File(target));
    }

    /**
     * 复制文件(内存映射)
     *
     * @param   source
     *          原文件
     *
     * @param   target
     *          目标文件
     *
     * @return  true:成功 / false:失败
     *
     * @throws  IOException
     *          文件读写异常
     */
    private static boolean copyByMapped(String source, String target) throws IOException {
        RandomAccessFile readFile = new RandomAccessFile(source, "r");
        RandomAccessFile writeFile = new RandomAccessFile(target, "rw");

        long fileLength = readFile.length();

        MappedByteBuffer in = readFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileLength);
        MappedByteBuffer out = writeFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, fileLength);

        for (int i = 0; i < fileLength; i++) {
            out.put(in.get());
        }

        readFile.close();
        writeFile.close();
        in.clear();
        out.clear();

        return true;
    }

    /**
     * 复制文件(Buffer)
     *
     * @param   source
     *          原文件
     *
     * @param   target
     *          目标文件
     *
     * @return  true:成功 / false:失败
     *
     * @throws  IOException
     *          读写异常
     */
    private static boolean copyByBuffer(File source, File target) throws IOException {
        if (null == source || null == target)
            throw new IOException("文件对象为空");

        if (!source.exists())
            throw new IOException("原文件不存在");

        FileInputStream inStream = new FileInputStream(source);
        FileOutputStream outStream = new FileOutputStream(target);
        FileChannel in = inStream.getChannel();
        FileChannel out = outStream.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(4096);
        while (in.read(buffer) != -1) {
            buffer.flip();
            out.write(buffer);
            buffer.clear();
        }

        inStream.close();
        in.close();
        outStream.close();
        out.close();

        return true;
    }

    /**
     * 复制文件(Buffer)
     *
     * @param   source
     *          原文件
     *
     * @param   target
     *          目标文件
     *
     * @return  true:成功 / false:失败
     *
     * @throws  IOException
     *          读写异常
     */
    private static boolean copyByBuffer(String source, String target) throws IOException {
        if (StringUtils.isEmpty(source) || StringUtils.isEmpty(target))
            throw new IOException("文件名不能为空");

        return copyByBuffer(new File(source), new File(target));
    }
}