package in.srain.cube.cache;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.text.TextUtils;
import in.srain.cube.util.CLog;
import in.srain.cube.util.Version;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.InputStream;

public class DiskFileUtils {

    /**
     * @param context
     * @param absolutePath         if it's not absolutePath, will be path under cache dir
     * @param sizeInKB
     * @param fallbackRelativePath
     * @return
     */
    public static CacheDirInfo getDiskCacheDir(
            Context context, String absolutePath, int sizeInKB,
            String fallbackRelativePath) {

        long size = (long) sizeInKB * 1024;
        boolean done = false;

        CacheDirInfo dirInfo = new CacheDirInfo();
        dirInfo.requireSize = size;

        // treat as absolute path
        if (!TextUtils.isEmpty(absolutePath)) {
            File cachePath = new File(absolutePath);
            // is not exist, try to make parent directory
            if (cachePath.exists() || cachePath.mkdirs()) {
                long free = getUsableSpace(cachePath);
                size = Math.min(size, free);
                dirInfo.realSize = size;
                dirInfo.path = cachePath;
                done = true;
            }
        }

        // it's relative path
        if (!done) {
            if (TextUtils.isEmpty(fallbackRelativePath)) {
                fallbackRelativePath = absolutePath;
            }
            dirInfo = getDiskCacheDir(context, fallbackRelativePath, size);
        }
        return dirInfo;
    }

    /**
     * Get a usable cache directory (external if available, internal otherwise).
     * .
     * Check if media is mounted or storage is built-in, if so, try and use external cache folder
     * otherwise use internal cache folder
     * .
     * If both of them can not meet the requirement, use the bigger one.
     *
     * @param context    The context to use
     * @param uniqueName A unique folder name to append to the cache folder
     * @return The cache folder
     */
    public static CacheDirInfo getDiskCacheDir(Context context, String uniqueName, long requireSpace) {
        File sdPath = null;
        File internalPath = null;
        Long sdCardFree = 0L;

        boolean usingInternal = false;

        if (hasSDCardMounted()) {
            sdPath = getExternalCacheDir(context);
            if (!sdPath.exists()) {
                sdPath.mkdirs();
            }
            sdCardFree = getUsableSpace(sdPath);
        }

        CacheDirInfo cacheDirInfo = new CacheDirInfo();
        cacheDirInfo.requireSize = requireSpace;

        // sd card can not meet the requirement
        // try to use the build-in storage
        if (sdPath == null || sdCardFree < requireSpace) {
            internalPath = context.getCacheDir();
            long internalFree = getUsableSpace(internalPath);

            // both lower then requirement, choose the bigger one
            if (internalFree < requireSpace) {
                if (internalFree > sdCardFree) {
                    usingInternal = true;
                    cacheDirInfo.realSize = internalFree;
                } else {
                    usingInternal = false;
                    cacheDirInfo.realSize = sdCardFree;
                }
                cacheDirInfo.isNotEnough = true;
            } else {
                usingInternal = true;
                cacheDirInfo.realSize = requireSpace;
            }
        } else {
            usingInternal = false;
            cacheDirInfo.realSize = requireSpace;
        }

        cacheDirInfo.isInternal = usingInternal;
        if (usingInternal) {
            cacheDirInfo.path = new File(internalPath.getPath() + File.separator + uniqueName);
        } else {
            cacheDirInfo.path = new File(sdPath.getPath() + File.separator + uniqueName);
        }
        if (!cacheDirInfo.path.exists() && !cacheDirInfo.path.mkdirs()) {
            CLog.e("cube-cache", "can not create directory for: %s", cacheDirInfo.path);
        }
        return cacheDirInfo;
    }

    /**
     * Get the external application cache directory.
     *
     * @param context The context to use
     * @return The external cache folder : /storage/sdcard0/Android/data/com.srain.sdk/cache
     */
    @TargetApi(Build.VERSION_CODES.FROYO)
    public static File getExternalCacheDir(Context context) {
        if (Version.hasFroyo()) {
            File path = context.getExternalCacheDir();

            // In some case, even the sd card is mounted, getExternalCacheDir will return null, may be it is nearly full.
            if (path != null) {
                return path;
            }
        }

        // Before Froyo or the path is null, we need to construct the external cache folder ourselves
        final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
        return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
    }

    /**
     * Check how much usable space is available at a given path.
     *
     * @param path The path to check
     * @return The space available in bytes by user, not by root, -1 means path is null, 0 means path is not exist.
     */
    @SuppressWarnings("deprecation")
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    public static long getUsableSpace(File path) {
        if (path == null) {
            return -1;
        }
        if (Version.hasGingerbread()) {
            return path.getUsableSpace();
        } else {
            if (!path.exists()) {
                return 0;
            } else {
                final StatFs stats = new StatFs(path.getPath());
                return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
            }
        }
    }

    @SuppressWarnings("deprecation")
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    public static long getUsedSpace(File path) {
        if (path == null) {
            return -1;
        }

        if (Version.hasGingerbread()) {
            return path.getTotalSpace() - path.getUsableSpace();
        } else {
            if (!path.exists()) {
                return -1;
            } else {
                final StatFs stats = new StatFs(path.getPath());
                return (long) stats.getBlockSize() * (stats.getBlockCount() - stats.getAvailableBlocks());
            }
        }
    }

    /**
     * @param path
     * @return -1 means path is null, 0 means path is not exist.
     */
    @SuppressWarnings("deprecation")
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    public static long getTotalSpace(File path) {
        if (path == null) {
            return -1;
        }
        if (Version.hasGingerbread()) {
            return path.getTotalSpace();
        } else {
            if (!path.exists()) {
                return 0;
            } else {
                final StatFs stats = new StatFs(path.getPath());
                return (long) stats.getBlockSize() * (long) stats.getBlockCount();
            }
        }
    }

    public static boolean hasSDCardMounted() {
        String state = Environment.getExternalStorageState();
        if (state != null && state.equals(Environment.MEDIA_MOUNTED)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * external: "/storage/emulated/0/Android/data/in.srain.sample/files"
     * internal: "/data/data/in.srain.sample/files"
     */
    public static String wantFilesPath(Context context, boolean externalStorageFirst) {
        String path = null;
        File f = null;
        if (externalStorageFirst && DiskFileUtils.hasSDCardMounted() && (f = context.getExternalFilesDir("xxx")) != null) {
            path = f.getAbsolutePath();
        } else {
            path = context.getFilesDir().getAbsolutePath();
        }
        return path;
    }

    /**
     * @param context
     * @param filePath file path relative to assets, like request_init1/search_index.json
     * @return
     */
    public static String readAssert(Context context, String filePath) {
        try {
            if (filePath.startsWith(File.separator)) {
                filePath = filePath.substring(File.separator.length());
            }
            AssetManager assetManager = context.getAssets();
            InputStream inputStream = assetManager.open(filePath);
            DataInputStream stream = new DataInputStream(inputStream);
            int length = stream.available();
            byte[] buffer = new byte[length];
            stream.readFully(buffer);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byteArrayOutputStream.write(buffer);
            stream.close();
            return byteArrayOutputStream.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static class CacheDirInfo {
        public File path;
        public boolean isInternal = false;
        public boolean isNotEnough = false;
        public long realSize;
        public long requireSize;
    }
}