package cn.qqtheme.framework.util;

import android.support.annotation.IntDef;
import android.webkit.MimeTypeMap;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.regex.Pattern;

/**
 * 文件处理
 * <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 * <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 *
 * @author 李玉江[QQ:1023694760]
 * @since 2014-4-18
 */
public final class FileUtils {
    public static final int BY_NAME_ASC = 0;
    public static final int BY_NAME_DESC = 1;
    public static final int BY_TIME_ASC = 2;
    public static final int BY_TIME_DESC = 3;
    public static final int BY_SIZE_ASC = 4;
    public static final int BY_SIZE_DESC = 5;
    public static final int BY_EXTENSION_ASC = 6;
    public static final int BY_EXTENSION_DESC = 7;

    @IntDef(value = {
            BY_NAME_ASC,
            BY_NAME_DESC,
            BY_TIME_ASC,
            BY_TIME_DESC,
            BY_SIZE_ASC,
            BY_SIZE_DESC,
            BY_EXTENSION_ASC,
            BY_EXTENSION_DESC
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface SortType {
    }

    /**
     * 将目录分隔符统一为平台默认的分隔符,并为目录结尾添加分隔符
     */
    public static String separator(String path) {
        String separator = File.separator;
        path = path.replace("\\", separator);
        if (!path.endsWith(separator)) {
            path += separator;
        }
        return path;
    }

    public static void closeSilently(Closeable c) {
        if (c == null) {
            return;
        }
        try {
            c.close();
        } catch (IOException e) {
            // do nothing
            LogUtils.warn(e);
        }
    }

    /**
     * 列出指定目录下的所有子目录
     */
    public static File[] listDirs(String startDirPath, String[] excludeDirs, @SortType int sortType) {
        LogUtils.verbose(String.format("list dir %s", startDirPath));
        ArrayList<File> dirList = new ArrayList<File>();
        File startDir = new File(startDirPath);
        if (!startDir.isDirectory()) {
            return new File[0];
        }
        File[] dirs = startDir.listFiles(new FileFilter() {
            public boolean accept(File f) {
                if (f == null) {
                    return false;
                }
                //noinspection RedundantIfStatement
                if (f.isDirectory()) {
                    return true;
                }
                return false;
            }
        });
        if (dirs == null) {
            return new File[0];
        }
        if (excludeDirs == null) {
            excludeDirs = new String[0];
        }
        for (File dir : dirs) {
            File file = dir.getAbsoluteFile();
            if (!ConvertUtils.toString(excludeDirs).contains(file.getName())) {
                dirList.add(file);
            }
        }
        if (sortType == BY_NAME_ASC) {
            Collections.sort(dirList, new SortByName());
        } else if (sortType == BY_NAME_DESC) {
            Collections.sort(dirList, new SortByName());
            Collections.reverse(dirList);
        } else if (sortType == BY_TIME_ASC) {
            Collections.sort(dirList, new SortByTime());
        } else if (sortType == BY_TIME_DESC) {
            Collections.sort(dirList, new SortByTime());
            Collections.reverse(dirList);
        } else if (sortType == BY_SIZE_ASC) {
            Collections.sort(dirList, new SortBySize());
        } else if (sortType == BY_SIZE_DESC) {
            Collections.sort(dirList, new SortBySize());
            Collections.reverse(dirList);
        } else if (sortType == BY_EXTENSION_ASC) {
            Collections.sort(dirList, new SortByExtension());
        } else if (sortType == BY_EXTENSION_DESC) {
            Collections.sort(dirList, new SortByExtension());
            Collections.reverse(dirList);
        }
        return dirList.toArray(new File[dirList.size()]);
    }

    /**
     * 列出指定目录下的所有子目录
     */
    public static File[] listDirs(String startDirPath, String[] excludeDirs) {
        return listDirs(startDirPath, excludeDirs, BY_NAME_ASC);
    }

    /**
     * 列出指定目录下的所有子目录
     */
    public static File[] listDirs(String startDirPath) {
        return listDirs(startDirPath, null, BY_NAME_ASC);
    }

    /**
     * 列出指定目录下的所有子目录及所有文件
     */
    public static File[] listDirsAndFiles(String startDirPath, String[] allowExtensions) {
        File[] dirs, files, dirsAndFiles;
        dirs = listDirs(startDirPath);
        if (allowExtensions == null) {
            files = listFiles(startDirPath);
        } else {
            files = listFiles(startDirPath, allowExtensions);
        }
        if (dirs == null || files == null) {
            return null;
        }
        dirsAndFiles = new File[dirs.length + files.length];
        System.arraycopy(dirs, 0, dirsAndFiles, 0, dirs.length);
        System.arraycopy(files, 0, dirsAndFiles, dirs.length, files.length);
        return dirsAndFiles;
    }

    /**
     * 列出指定目录下的所有子目录及所有文件
     */
    public static File[] listDirsAndFiles(String startDirPath) {
        return listDirsAndFiles(startDirPath, null);
    }

    /**
     * 列出指定目录下的所有文件
     */
    public static File[] listFiles(String startDirPath, final Pattern filterPattern, @SortType int sortType) {
        LogUtils.verbose(String.format("list file %s", startDirPath));
        ArrayList<File> fileList = new ArrayList<File>();
        File f = new File(startDirPath);
        if (!f.isDirectory()) {
            return new File[0];
        }
        File[] files = f.listFiles(new FileFilter() {
            public boolean accept(File f) {
                if (f == null) {
                    return false;
                }
                if (f.isDirectory()) {
                    return false;
                }
                //noinspection SimplifiableIfStatement
                if (filterPattern == null) {
                    return true;
                }
                return filterPattern.matcher(f.getName()).find();
            }
        });
        if (files == null) {
            return new File[0];
        }
        for (File file : files) {
            fileList.add(file.getAbsoluteFile());
        }
        if (sortType == BY_NAME_ASC) {
            Collections.sort(fileList, new SortByName());
        } else if (sortType == BY_NAME_DESC) {
            Collections.sort(fileList, new SortByName());
            Collections.reverse(fileList);
        } else if (sortType == BY_TIME_ASC) {
            Collections.sort(fileList, new SortByTime());
        } else if (sortType == BY_TIME_DESC) {
            Collections.sort(fileList, new SortByTime());
            Collections.reverse(fileList);
        } else if (sortType == BY_SIZE_ASC) {
            Collections.sort(fileList, new SortBySize());
        } else if (sortType == BY_SIZE_DESC) {
            Collections.sort(fileList, new SortBySize());
            Collections.reverse(fileList);
        } else if (sortType == BY_EXTENSION_ASC) {
            Collections.sort(fileList, new SortByExtension());
        } else if (sortType == BY_EXTENSION_DESC) {
            Collections.sort(fileList, new SortByExtension());
            Collections.reverse(fileList);
        }
        return fileList.toArray(new File[fileList.size()]);
    }

    /**
     * 列出指定目录下的所有文件
     */
    public static File[] listFiles(String startDirPath, Pattern filterPattern) {
        return listFiles(startDirPath, filterPattern, BY_NAME_ASC);
    }

    /**
     * 列出指定目录下的所有文件
     */
    public static File[] listFiles(String startDirPath) {
        return listFiles(startDirPath, null, BY_NAME_ASC);
    }

    /**
     * 列出指定目录下的所有文件
     */
    public static File[] listFiles(String startDirPath, final String[] allowExtensions) {
        LogUtils.verbose(String.format("list file %s", startDirPath));
        File file = new File(startDirPath);
        return file.listFiles(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                //返回当前目录所有以某些扩展名结尾的文件
                String extension = FileUtils.getExtension(name);
                return ConvertUtils.toString(allowExtensions).contains(extension);
            }

        });
    }

    /**
     * 列出指定目录下的所有文件
     */
    public static File[] listFiles(String startDirPath, String allowExtension) {
        return listFiles(startDirPath, new String[]{allowExtension});
    }

    /**
     * 判断文件或目录是否存在
     */
    public static boolean exist(String path) {
        File file = new File(path);
        return file.exists();
    }

    /**
     * 删除文件或目录
     */
    public static boolean delete(File file, boolean deleteRootDir) {
        LogUtils.verbose(String.format("delete file %s", file.getAbsolutePath()));
        boolean result = false;
        if (file.isFile()) {
            //是文件
            result = deleteResolveEBUSY(file);
        } else {
            //是目录
            File[] files = file.listFiles();
            if (files == null) {
                return false;
            }
            if (files.length == 0) {
                result = deleteRootDir && deleteResolveEBUSY(file);
            } else {
                for (File f : files) {
                    delete(f, deleteRootDir);
                    result = deleteResolveEBUSY(f);
                }
            }
            if (deleteRootDir) {
                result = deleteResolveEBUSY(file);
            }
        }
        return result;
    }

    /**
     * bug: open failed: EBUSY (Device or resource busy)
     * fix: http://stackoverflow.com/questions/11539657/open-failed-ebusy-device-or-resource-busy
     */
    private static boolean deleteResolveEBUSY(File file) {
        // Before you delete a Directory or File: rename it!
        final File to = new File(file.getAbsolutePath() + System.currentTimeMillis());
        //noinspection ResultOfMethodCallIgnored
        file.renameTo(to);
        return to.delete();
    }

    /**
     * 删除文件或目录
     */
    public static boolean delete(String path, boolean deleteRootDir) {
        File file = new File(path);
        //noinspection SimplifiableIfStatement
        if (file.exists()) {
            return delete(file, deleteRootDir);
        }
        return false;
    }

    /**
     * 删除文件或目录, 不删除最顶层目录
     */
    public static boolean delete(String path) {
        return delete(path, false);
    }

    /**
     * 删除文件或目录, 不删除最顶层目录
     */
    public static boolean delete(File file) {
        return delete(file, false);
    }

    /**
     * 复制文件为另一个文件,或复制某目录下的所有文件及目录到另一个目录下
     */
    public static boolean copy(String src, String tar) {
        File srcFile = new File(src);
        return srcFile.exists() && copy(srcFile, new File(tar));
    }

    /**
     * 复制文件或目录
     */
    public static boolean copy(File src, File tar) {
        try {
            LogUtils.verbose(String.format("copy %s to %s", src.getAbsolutePath(), tar.getAbsolutePath()));
            if (src.isFile()) {
                InputStream is = new FileInputStream(src);
                OutputStream op = new FileOutputStream(tar);
                BufferedInputStream bis = new BufferedInputStream(is);
                BufferedOutputStream bos = new BufferedOutputStream(op);
                byte[] bt = new byte[1024 * 8];
                while (true) {
                    int len = bis.read(bt);
                    if (len == -1) {
                        break;
                    } else {
                        bos.write(bt, 0, len);
                    }
                }
                bis.close();
                bos.close();
            } else if (src.isDirectory()) {
                File[] files = src.listFiles();
                //noinspection ResultOfMethodCallIgnored
                tar.mkdirs();
                for (File file : files) {
                    copy(file.getAbsoluteFile(), new File(tar.getAbsoluteFile(), file.getName()));
                }
            }
            return true;
        } catch (Exception e) {
            LogUtils.error(e);
            return false;
        }
    }

    /**
     * 移动文件或目录
     */
    public static boolean move(String src, String tar) {
        return move(new File(src), new File(tar));
    }

    /**
     * 移动文件或目录
     */
    public static boolean move(File src, File tar) {
        return rename(src, tar);
    }

    /**
     * 文件重命名
     */
    public static boolean rename(String oldPath, String newPath) {
        return rename(new File(oldPath), new File(newPath));
    }

    /**
     * 文件重命名
     */
    public static boolean rename(File src, File tar) {
        LogUtils.verbose(String.format("rename %s to %s", src.getAbsolutePath(), tar.getAbsolutePath()));
        return src.renameTo(tar);
    }

    /**
     * 读取文本文件, 失败将返回空串
     */
    public static String readText(String filepath, String charset) {
        LogUtils.verbose(String.format("read %s use %s", filepath, charset));
        try {
            byte[] data = readBytes(filepath);
            if (data != null) {
                return new String(data, charset).trim();
            }
        } catch (UnsupportedEncodingException e) {
            LogUtils.warn(e);
        }
        return "";
    }

    /**
     * 读取文本文件, 失败将返回空串
     */
    public static String readText(String filepath) {
        return readText(filepath, "utf-8");
    }

    /**
     * 读取文件内容, 失败将返回空串
     */
    public static byte[] readBytes(String filepath) {
        LogUtils.verbose(String.format("read %s", filepath));
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(filepath);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            while (true) {
                int len = fis.read(buffer, 0, buffer.length);
                if (len == -1) {
                    break;
                } else {
                    baos.write(buffer, 0, len);
                }
            }
            byte[] data = baos.toByteArray();
            baos.close();
            return data;
        } catch (IOException e) {
            LogUtils.warn(e);
            return null;
        } finally {
            closeSilently(fis);
        }
    }

    /**
     * 保存文本内容
     */
    public static boolean writeText(String filepath, String content, String charset) {
        try {
            writeBytes(filepath, content.getBytes(charset));
            return true;
        } catch (UnsupportedEncodingException e) {
            LogUtils.warn(e);
            return false;
        }
    }

    /**
     * 保存文本内容
     */
    public static boolean writeText(String filepath, String content) {
        return writeText(filepath, content, "utf-8");
    }

    /**
     * 保存文件内容
     */
    public static boolean writeBytes(String filepath, byte[] data) {
        LogUtils.verbose(String.format("write %s", filepath));
        File file = new File(filepath);
        FileOutputStream fos = null;
        try {
            if (!file.exists()) {
                //noinspection ResultOfMethodCallIgnored
                file.getParentFile().mkdirs();
                //noinspection ResultOfMethodCallIgnored
                file.createNewFile();
            }
            fos = new FileOutputStream(filepath);
            fos.write(data);
            return true;
        } catch (IOException e) {
            LogUtils.warn(e);
            return false;
        } finally {
            closeSilently(fos);
        }
    }

    /**
     * 追加文本内容
     */
    public static boolean appendText(String path, String content) {
        LogUtils.verbose(String.format("append %s", path));
        File file = new File(path);
        FileWriter writer = null;
        try {
            if (!file.exists()) {
                //noinspection ResultOfMethodCallIgnored
                file.createNewFile();
            }
            writer = new FileWriter(file, true);
            writer.write(content);
            return true;
        } catch (IOException e) {
            LogUtils.warn(e);
            return false;
        } finally {
            closeSilently(writer);
        }
    }

    /**
     * 获取文件大小
     */
    public static long getLength(String path) {
        File file = new File(path);
        if (!file.isFile() || !file.exists()) {
            return 0;
        }
        return file.length();
    }

    /**
     * 获取文件或网址的名称(包括后缀)
     */
    public static String getName(String pathOrUrl) {
        if (pathOrUrl == null) {
            return "";
        }
        int pos = pathOrUrl.lastIndexOf('/');
        if (0 <= pos) {
            return pathOrUrl.substring(pos + 1);
        } else {
            return String.valueOf(System.currentTimeMillis()) + "." + getExtension(pathOrUrl);
        }
    }

    /**
     * 获取文件名(不包括扩展名)
     */
    public static String getNameExcludeExtension(String path) {
        try {
            String fileName = (new File(path)).getName();
            int lastIndexOf = fileName.lastIndexOf(".");
            if (lastIndexOf != -1) {
                fileName = fileName.substring(0, lastIndexOf);
            }
            return fileName;
        } catch (Exception e) {
            LogUtils.warn(e);
            return "";
        }
    }

    /**
     * 获取格式化后的文件大小
     */
    public static String getSize(String path) {
        long fileSize = getLength(path);
        return ConvertUtils.toFileSizeString(fileSize);
    }

    /**
     * 获取文件后缀,不包括“.”
     */
    public static String getExtension(String pathOrUrl) {
        int dotPos = pathOrUrl.lastIndexOf('.');
        if (0 <= dotPos) {
            return pathOrUrl.substring(dotPos + 1);
        } else {
            return "ext";
        }
    }

    /**
     * 获取文件的MIME类型
     */
    public static String getMimeType(String pathOrUrl) {
        String ext = getExtension(pathOrUrl);
        MimeTypeMap map = MimeTypeMap.getSingleton();
        String mimeType;
        if (map.hasExtension(ext)) {
            mimeType = map.getMimeTypeFromExtension(ext);
        } else {
            mimeType = "*/*";
        }
        LogUtils.verbose(pathOrUrl + ": " + mimeType);
        return mimeType;
    }

    /**
     * 获取格式化后的文件/目录创建或最后修改时间
     */
    public static String getDateTime(String path) {
        return getDateTime(path, "yyyy年MM月dd日HH:mm");
    }

    /**
     * 获取格式化后的文件/目录创建或最后修改时间
     */
    public static String getDateTime(String path, String format) {
        File file = new File(path);
        return getDateTime(file, format);
    }

    /**
     * 获取格式化后的文件/目录创建或最后修改时间
     */
    public static String getDateTime(File file, String format) {
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(file.lastModified());
        return DateUtils.formatDate(cal.getTime(), format);
    }

    /**
     * 比较两个文件的最后修改时间
     */
    public static int compareLastModified(String path1, String path2) {
        long stamp1 = (new File(path1)).lastModified();
        long stamp2 = (new File(path2)).lastModified();
        if (stamp1 > stamp2) {
            return 1;
        } else if (stamp1 < stamp2) {
            return -1;
        } else {
            return 0;
        }
    }

    /**
     * 创建多级别的目录
     */
    public static boolean makeDirs(String path) {
        return makeDirs(new File(path));
    }

    /**
     * 创建多级别的目录
     */
    public static boolean makeDirs(File file) {
        return file.mkdirs();
    }

    public static class SortByExtension implements Comparator<File> {

        public SortByExtension() {
            super();
        }

        public int compare(File f1, File f2) {
            if (f1 == null || f2 == null) {
                if (f1 == null) {
                    return -1;
                } else {
                    return 1;
                }
            } else {
                if (f1.isDirectory() && f2.isFile()) {
                    return -1;
                } else if (f1.isFile() && f2.isDirectory()) {
                    return 1;
                } else {
                    return f1.getName().compareToIgnoreCase(f2.getName());
                }
            }
        }

    }

    public static class SortByName implements Comparator<File> {
        private boolean caseSensitive;

        public SortByName(boolean caseSensitive) {
            this.caseSensitive = caseSensitive;
        }

        public SortByName() {
            this.caseSensitive = false;
        }

        public int compare(File f1, File f2) {
            if (f1 == null || f2 == null) {
                if (f1 == null) {
                    return -1;
                } else {
                    return 1;
                }
            } else {
                if (f1.isDirectory() && f2.isFile()) {
                    return -1;
                } else if (f1.isFile() && f2.isDirectory()) {
                    return 1;
                } else {
                    String s1 = f1.getName();
                    String s2 = f2.getName();
                    if (caseSensitive) {
                        return s1.compareTo(s2);
                    } else {
                        return s1.compareToIgnoreCase(s2);
                    }
                }
            }
        }

    }

    public static class SortBySize implements Comparator<File> {

        public SortBySize() {
            super();
        }

        public int compare(File f1, File f2) {
            if (f1 == null || f2 == null) {
                if (f1 == null) {
                    return -1;
                } else {
                    return 1;
                }
            } else {
                if (f1.isDirectory() && f2.isFile()) {
                    return -1;
                } else if (f1.isFile() && f2.isDirectory()) {
                    return 1;
                } else {
                    if (f1.length() < f2.length()) {
                        return -1;
                    } else {
                        return 1;
                    }
                }
            }
        }

    }

    public static class SortByTime implements Comparator<File> {

        public SortByTime() {
            super();
        }

        public int compare(File f1, File f2) {
            if (f1 == null || f2 == null) {
                if (f1 == null) {
                    return -1;
                } else {
                    return 1;
                }
            } else {
                if (f1.isDirectory() && f2.isFile()) {
                    return -1;
                } else if (f1.isFile() && f2.isDirectory()) {
                    return 1;
                } else {
                    if (f1.lastModified() > f2.lastModified()) {
                        return -1;
                    } else {
                        return 1;
                    }
                }
            }
        }

    }

}