package com.qozix.tileview.tiles;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

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

import com.jakewharton.DiskLruCache;

public class TileCache {
	
	private static final int DISK_CACHE_CAPACITY = 8 * 1024;
	private static final int IO_BUFFER_SIZE = 8 * 1024;
	
	private static final int COMPRESSION_QUALITY = 40;
	
	private static final BitmapFactory.Options BITMAPFACTORY_OPTIONS = new BitmapFactory.Options();
	static {
		BITMAPFACTORY_OPTIONS.inPreferredConfig = Bitmap.Config.RGB_565;
	}

	private MessageDigest digest;
	
	private LruCache<String, Bitmap> memoryCache;
	private DiskLruCache diskCache;

	// TODO: register local broadcast receiver to destroy the cache during onDestroy of containing Activity
	public TileCache( final Context context ) {
		try {
			digest = MessageDigest.getInstance( "MD5" );
		} catch ( NoSuchAlgorithmException e1 ) {
			
		}
		// in memory cache
		final int memory = (int) ( Runtime.getRuntime().maxMemory() / 1024 );
		final int size = memory / 8;
		memoryCache = new LruCache<String, Bitmap>( size ) {
			@Override
			protected int sizeOf( String key, Bitmap bitmap ) {
				// The cache size will be measured in kilobytes rather than number of items.
				// emulate bitmap.getByteCount for APIs less than 12
				int byteCount = bitmap.getRowBytes() * bitmap.getHeight();
				return byteCount / 1024;
			}
		};
		// disk cache
		new Thread(new Runnable(){
			@Override
			public void run(){
				File cacheDirectory = new File( context.getCacheDir().getPath() + File.separator + "com/qozix/mapview" );
				try {
					diskCache = DiskLruCache.open( cacheDirectory, 1, 1, DISK_CACHE_CAPACITY );
				} catch ( IOException e ) {
					
				}
			}
		}).start();
	}

	public void addBitmap( String key, Bitmap bitmap ) {
		addBitmapToMemoryCache( key, bitmap );
		addBitmapToDiskCache( key, bitmap );
	}
	
	public Bitmap getBitmap( String key ) {
		Bitmap bitmap = getBitmapFromMemoryCache( key );
		if ( bitmap == null ) {
			bitmap = getBitmapFromDiskCache( key );
		}
		return bitmap;
	}
	
	public void destroy(){
		memoryCache.evictAll();
		memoryCache = null;
		new Thread(new Runnable(){
			@Override
			public void run(){
				try {
					diskCache.delete();
					diskCache = null;
				} catch ( IOException e ) {
					
				}
			}
		}).start();		
	}
	
	public void clear() {
		memoryCache.evictAll();
		new Thread(new Runnable(){
			@Override
			public void run(){
				try {
					diskCache.delete();
				} catch ( IOException e ) {
					
				}
			}
		}).start();		
	}
	
	private void addBitmapToMemoryCache( String key, Bitmap bitmap ) {
		if ( key == null || bitmap == null ) {
	        return;
	    }
		if ( getBitmapFromMemoryCache( key ) == null ) {
			memoryCache.put( key, bitmap );
		}
	}

	private Bitmap getBitmapFromMemoryCache( String key ) {
		return memoryCache.get( key );
	}
	
	private void addBitmapToDiskCache( String key, Bitmap bitmap ) {
		if ( diskCache == null ) {
			return;
		}
		key = getMD5( key );
		DiskLruCache.Editor editor = null;
		try {
			editor = diskCache.edit( key );
			if ( editor == null ) {
				return;
			}
			OutputStream output = null;
			try {
				output = new BufferedOutputStream( editor.newOutputStream( 0 ), IO_BUFFER_SIZE );
				boolean compressed = bitmap.compress( CompressFormat.JPEG, COMPRESSION_QUALITY, output );
				if ( compressed ) {
					diskCache.flush();
					editor.commit();
				} else {
					editor.abort();
				}
			} finally {
				if ( output != null ) {
					output.close();
				}
			}
		} catch ( IOException e ) {
			try {
				if ( editor != null ) {
					editor.abort();
				}
			} catch ( IOException io ) {
				
			}
		}
	}
	
	private Bitmap getBitmapFromDiskCache( String key ) {
		if ( diskCache == null ) {
			return null;
		}
		key = getMD5( key );
		Bitmap bitmap = null;
		DiskLruCache.Snapshot snapshot = null;
		try {
			snapshot = diskCache.get( key );
			if ( snapshot == null ) {
				return null;
			}
			final InputStream input = snapshot.getInputStream( 0 );
			if ( input != null ) {
				BufferedInputStream buffered = new BufferedInputStream( input, IO_BUFFER_SIZE );
				bitmap = BitmapFactory.decodeStream( buffered, null, BITMAPFACTORY_OPTIONS );
			}
		} catch ( IOException e ) {
			
		} finally {
			if ( snapshot != null ) {
				snapshot.close();
			}
		}
		return bitmap;
	}
	
	private String getMD5( String fileName ) {
		if ( digest != null ) {
			digest.update( fileName.getBytes(), 0, fileName.length() );
			return new BigInteger( 1, digest.digest() ).toString( 16 );
		}
		// if digest is unavailable, at least make some attempt at an acceptable filename
		return fileName.replace("[^a-z0-9_-]", "_");
	}
}

@@ -28,16 +28,246 @@
 tiles;%0A%0A
+import android.content.Context;%0Aimport android.graphics.Bitmap;%0Aimport android.graphics.Bitmap.CompressFormat;%0Aimport android.graphics.BitmapFactory;%0Aimport android.support.v4.util.LruCache;%0A%0Aimport com.jakewharton.DiskLruCache;%0A%0A
 import j
@@ -554,238 +554,8 @@
 n;%0A%0A
-import android.content.Context;%0Aimport android.graphics.Bitmap;%0Aimport android.graphics.BitmapFactory;%0Aimport android.graphics.Bitmap.CompressFormat;%0Aimport android.support.v4.util.LruCache;%0A%0Aimport com.jakewharton.DiskLruCache;%0A%0A
 publ
@@ -3925,34 +3925,32 @@
 %09%09%7D%0A%09%09%7D catch ( 
-IO
 Exception e ) %7B%0A
@@ -4612,34 +4612,32 @@
 %09%09%7D%0A%09%09%7D catch ( 
-IO
 Exception e ) %7B%0A

use Exception instead of IOException on LRU cache methods