package com.mixpanel.android.util;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.util.LruCache;
import android.util.Base64;

import com.mixpanel.android.mpmetrics.MPConfig;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.SSLSocketFactory;

/**
 * ABSOLUTELY NOT thread, or even process safe.
 * Writes and reads files and directories at known paths, and uses a shared instance of MessageDigest.
 */
public class ImageStore {
    public static class CantGetImageException extends Exception {
        public CantGetImageException(String message) {
            super(message);
        }

        public CantGetImageException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    public ImageStore(Context context, String moduleName) {
        this(context, DEFAULT_DIRECTORY_PREFIX + moduleName, new HttpService());
    }

    public ImageStore(Context context, String directoryName, RemoteService poster) {
        mDirectory = context.getDir(directoryName, Context.MODE_PRIVATE);
        mPoster = poster;
        mConfig = MPConfig.getInstance(context);
        MessageDigest useDigest;
        try {
            useDigest = MessageDigest.getInstance("SHA1");
        } catch (NoSuchAlgorithmException e) {
            MPLog.w(LOGTAG, "Images won't be stored because this platform doesn't supply a SHA1 hash function");
            useDigest = null;
        }

        mDigest = useDigest;

        if (sMemoryCache == null) {
            synchronized (ImageStore.class) {
                if (sMemoryCache == null) {
                    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
                    int cacheSize = maxMemory / mConfig.getImageCacheMaxMemoryFactor();

                    sMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
                        @Override
                        protected int sizeOf(String key, Bitmap bitmap) {
                            return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
                        }
                    };
                }
            }
        }
    }

    public File getImageFile(String url) throws CantGetImageException {
        final File file = storedFile(url);
        byte[] bytes = null;

        if (file == null || !file.exists()) {
            try {
                final SSLSocketFactory factory = mConfig.getSSLSocketFactory();
                bytes = mPoster.performRequest(url, null, factory);
            } catch (IOException e) {
                throw new CantGetImageException("Can't download bitmap", e);
            } catch (RemoteService.ServiceUnavailableException e) {
                throw new CantGetImageException("Couldn't download image due to service availability", e);
            }

            if (null != bytes) {
                if (null != file && bytes.length < MAX_BITMAP_SIZE) {
                    OutputStream out = null;
                    try {
                        out = new FileOutputStream(file);
                        out.write(bytes);
                    } catch (FileNotFoundException e) {
                        throw new CantGetImageException("It appears that ImageStore is misconfigured, or disk storage is unavailable- can't write to bitmap directory", e);
                    } catch (IOException e) {
                        throw new CantGetImageException("Can't store bitmap", e);
                    } finally {
                        if (null != out) {
                            try {
                                out.close();
                            } catch (IOException e) {
                                MPLog.w(LOGTAG, "Problem closing output file", e);
                            }
                        }
                    }
                }
            }
        }

        return file;
    }

    public Bitmap getImage(String url) throws CantGetImageException {
        Bitmap cachedBitmap = getBitmapFromMemCache(url);

        if (cachedBitmap == null) {
            final File imageFile = getImageFile(url);
            cachedBitmap = decodeImage(imageFile);
            addBitmapToMemoryCache(url, cachedBitmap);
        }

        return cachedBitmap;
    }

    private static Bitmap decodeImage(File file) throws CantGetImageException {
        BitmapFactory.Options option = new BitmapFactory.Options();
        option.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(file.getAbsolutePath(), option);
        float imageSize = (float) option.outHeight * option.outWidth;
        if (imageSize > getAvailableMemory()) {
            throw new CantGetImageException("Do not have enough memory for the image");
        }

        Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
        if (null == bitmap) {
            final boolean ignored = file.delete();
            throw new CantGetImageException("Bitmap on disk can't be opened or was corrupt");
        }

        return bitmap;
    }

    private static float getAvailableMemory() {
        Runtime runtime = Runtime.getRuntime();
        float used = runtime.totalMemory() - runtime.freeMemory();  // used      = heap - free
        return runtime.maxMemory() - used;                          // available = max - used
    }

    public void clearStorage() {
        File[] files = mDirectory.listFiles();
        int length = files.length;
        for (int i = 0; i < length; i++) {
            final File file = files[i];
            final String filename = file.getName();
            if (filename.startsWith(FILE_PREFIX)) {
                final boolean ignored = file.delete();
            }
        }

        clearMemCache();
    }

    public void deleteStorage(String url) {
        final File file = storedFile(url);
        if (null != file) {
            final boolean ignored = file.delete();
            removeBitmapFromMemCache(url);
        }
    }

    private File storedFile(String url) {
        if (null == mDigest) {
            return null;
        }

        final byte[] hashed = mDigest.digest(url.getBytes());
        final String safeName = FILE_PREFIX + Base64.encodeToString(hashed, Base64.URL_SAFE | Base64.NO_WRAP);
        return new File(mDirectory, safeName);
    }

    public static void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            synchronized (sMemoryCache) {
                sMemoryCache.put(key, bitmap);
            }
        }
    }

    public static Bitmap getBitmapFromMemCache(String key) {
        synchronized (sMemoryCache) {
            return sMemoryCache.get(key);
        }
    }

    public static void removeBitmapFromMemCache(String key) {
        synchronized (sMemoryCache) {
            sMemoryCache.remove(key);
        }
    }

    public static void clearMemCache() {
        synchronized (sMemoryCache) {
            sMemoryCache.evictAll();
        }
    }

    private final File mDirectory;
    private final RemoteService mPoster;
    private final MessageDigest mDigest;
    private final MPConfig mConfig;
    private static LruCache<String, Bitmap> sMemoryCache;

    private static final String DEFAULT_DIRECTORY_PREFIX = "MixpanelAPI.Images.";
    private static final int MAX_BITMAP_SIZE = 10000000; // 10 MB
    private static final String FILE_PREFIX = "MP_IMG_";

    @SuppressWarnings("unused")
    private static final String LOGTAG = "MixpanelAPI.ImageStore";
}

@@ -151,32 +151,19 @@
 oid.
-support.v4.util.LruCache
+util.Base64
 ;%0Aim
@@ -180,22 +180,24 @@
 id.util.
-Base64
+LruCache
 ;%0A%0Aimpor

Use LruCache from util instead of support lib