/*
 * LruBitmapCache      2015-11-23
 * Copyright (c) 2015 hujiang Co.Ltd. All right reserved(http://www.hujiang.com).
 * 
 */

package com.hujiang.restvolley.image;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.LruCache;
import android.widget.ImageView;

import com.android.volley.VolleyLog;
import com.hujiang.restvolley.MD5Utils;
import com.hujiang.restvolley.TaskScheduler;
import com.jakewharton.disklrucache.DiskLruCache;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

/**
 * image cache that contains memory cache and disk cache.
 *
 * @author simon
 * @version 1.0.0
 * @since 2015-11-23
 */
public class RestVolleyImageCache extends ImageLoaderCompat.ImageCache {

    private static final int BYTES_IN_PIX = 4;
    private static final int PAGE_CACHE_COUNT = 3;


    private static final long DEF_DISK_CACHE_SIZE = 128 * 1024 * 1024;
    private static final String DISK_CACHE_DIR = "restvolley_image";

    public static long MEM_CACHE_SIZE = 0;
    public static long DISK_CACHE_SIZE = 0;
    public static long MAX_BITMAP_CACHE_SIZE = 0;

    LruCache<String, Bitmap> mMemCache;
    DiskLruCache mDiskCache;
    File mCacheDir;

    /**
     * constructor with default cache config.
     * @param context {@link Context}
     */
    public RestVolleyImageCache(Context context) {
        this(context, getDefaultCacheSize(context, PAGE_CACHE_COUNT), DEF_DISK_CACHE_SIZE, DISK_CACHE_DIR);
    }

    /**
     * constructor with the specified config.
     * @param context {@link Context}
     * @param maxMemCacheSize max memory cache size.
     * @param maxDiskCacheSize max disk cache size.
     * @param diskCacheDir disk cache dir.
     */
    public RestVolleyImageCache(Context context, long maxMemCacheSize, long maxDiskCacheSize, String diskCacheDir) {
        MEM_CACHE_SIZE = maxMemCacheSize > 0 ? maxMemCacheSize : getDefaultCacheSize(context, PAGE_CACHE_COUNT);
        MAX_BITMAP_CACHE_SIZE = getMaxBitmapCacheSize(context);
        DISK_CACHE_SIZE = maxDiskCacheSize > 0 ? maxDiskCacheSize : DEF_DISK_CACHE_SIZE;
        String cacheDirStr = TextUtils.isEmpty(diskCacheDir) ? DISK_CACHE_DIR : diskCacheDir;

        //create mem cache
        mMemCache = new LruCache<String, Bitmap>((int) MEM_CACHE_SIZE) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return getBitmapSize(value);
            }
        };

        //create disk cache dir
        mCacheDir = getDiskCacheDir(context, cacheDirStr);
        if (!mCacheDir.exists()) {
            mCacheDir.mkdirs();
        }

        //create disk cache
        try {
            mDiskCache = DiskLruCache.open(mCacheDir, 1, 1, DISK_CACHE_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public DiskLruCache getDiskCache() {
        if (mDiskCache == null) {
            //create disk cache
            try {
                mDiskCache = DiskLruCache.open(mCacheDir, 1, 1, DISK_CACHE_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return mDiskCache;
    }

    public String getDiskCachePath() {
        if (getDiskCache() != null) {
            return getDiskCache().getDirectory().getAbsolutePath();
        }

        return "";
    }

    public File getDiskCacheDir() {
        if (getDiskCache() != null) {
            return getDiskCache().getDirectory();
        }

        return null;
    }

    @Override
    public boolean isCached(String cacheKey) {
        String key = generateKey(cacheKey);
        boolean isInMem = mMemCache.get(key) != null;
        boolean isInDisk = false;
        if (getDiskCache() != null) {
            try {
                DiskLruCache.Snapshot snapshot = getDiskCache().get(key);
                if (snapshot != null) {
                    isInDisk = snapshot.getInputStream(0) != null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return isInMem && isInDisk;
    }

    @Override
    public Bitmap getBitmap(String cacheKey) {
        String key = generateKey(cacheKey);
        Bitmap bmp = mMemCache.get(key);
        if (bmp == null) {
            bmp = getBitmapFromDiskLruCache(key);
            //从磁盘读出后,放入内存
            if (bmp != null) {
                mMemCache.put(key, bmp);
            }
        }

        return bmp;
    }

    @Override
    public CacheItem getCache(String cacheKey) {
        String key = generateKey(cacheKey);
        LoadFrom lf = LoadFrom.UNKNOWN;
        Bitmap bmp = mMemCache.get(key);
        if (bmp == null) {
            bmp = getBitmapFromDiskLruCache(key);
            //从磁盘读出后,放入内存
            if (bmp != null) {
                lf = LoadFrom.DISC_CACHE;
                mMemCache.put(key, bmp);
            }
        } else {
            lf = LoadFrom.MEMORY_CACHE;
        }

        return new CacheItem(cacheKey, bmp, lf);
    }

    @Override
    public void putBitmap(String cacheKey, Bitmap bitmap) {
        String key = generateKey(cacheKey);
        mMemCache.put(cacheKey, bitmap);
        putBitmapToDiskLruCache(key, bitmap);
    }

    @Override
    public boolean remove(String url) {
        String key = generateKey(url);
        mMemCache.remove(key);
        if (getDiskCache() != null) {
            try {
                return getDiskCache().remove(key);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * get the disk cache path with the specified cache key
     * @param cacheKey cache key generated by {@link ImageLoaderCompat#generateCacheKey(String, int, int, ImageView.ScaleType)}
     * @return disk cache path
     */
    public String getDiskCachePath(String cacheKey) {
        return new StringBuilder(getDiskCachePath())
                .append(File.separator)
                .append(generateKey(cacheKey))
                .append(".0")
                .toString();
    }

    /**
     * remove all mem cache and disk cache
     * @return remove successful or not
     */
    public boolean removeAll() {
        boolean isRemoved = true;

        mMemCache.evictAll();
        try {
            if (getDiskCache() != null) {
                getDiskCache().delete();
            }
        } catch (Exception e) {
            e.printStackTrace();
            isRemoved = false;
        }

        return isRemoved;
    }

    /**
     * just remove disk cache only
     * @return remove successful or not
     */
    public boolean removeDiskCache() {
        boolean isRemoved = true;
        try {
            if (getDiskCache() != null) {
                getDiskCache().delete();
            }
        } catch (Exception e) {
            e.printStackTrace();
            isRemoved = false;
        }

        return isRemoved;
    }

    /**
     * get default memory cache size.
     *
     * @param context Context
     * @return cacheSize
     */
    public static int getDefaultCacheSize(Context context, int pageCacheCount) {
        pageCacheCount = pageCacheCount <= 0 ? PAGE_CACHE_COUNT : pageCacheCount;

        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        int screenWith = displayMetrics.widthPixels;
        int screenHeight = displayMetrics.heightPixels;
        int screenBytes = screenWith * screenHeight * BYTES_IN_PIX;
        return screenBytes * pageCacheCount;
    }

    public static int getMaxBitmapCacheSize(Context context) {
        return getDefaultCacheSize(context, 2);
    }

    /**
     * get bitmap size.
     *
     * @param bitmap Bitmap
     * @return bitmapSize
     */
    public static int getBitmapSize(Bitmap bitmap) {
        if (bitmap == null) {
            return 0;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //API 19
            return bitmap.getAllocationByteCount();
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
            //API 12
            return bitmap.getByteCount();
        }
        //earlier version
        return bitmap.getRowBytes() * bitmap.getHeight();
    }

    private void putBitmapToDiskLruCache(final String key, final Bitmap bitmap) {
        TaskScheduler.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (getDiskCache() != null) {
                        DiskLruCache.Editor editor = getDiskCache().edit(key);
                        if (editor != null) {
                            OutputStream outputStream = editor.newOutputStream(0);
                            bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
                            editor.commit();

                            outputStream.close();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private Bitmap getBitmapFromDiskLruCache(String key) {
        if (getDiskCache() == null) {
            return null;
        }

        String cachePath = new StringBuilder(getDiskCachePath()).append(File.separator).append(key).append(".0").toString();
        File cacheFile = new File(cachePath);
        try {
            if (cacheFile.exists()) {
                return ImageProcessor.decode(cachePath);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } catch (OutOfMemoryError error) {
            VolleyLog.e("Caught OOM for %d byte image, uri=%s", cacheFile.length(), cachePath);
        }

        return null;
    }

    private String generateKey(String target) {
        return MD5Utils.hashKeyForDisk(target);
    }

    /**
     * get the disk cache dir with the unique name.
     * @param context {@link Context}
     * @param uniqueName unique dir name
     * @return dir file
     */
    public static File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);

    }

}