/*
 * Copyright (C) 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.codeim.youliao.app;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.util.Log;

import com.codeim.youliao.TwitterApplication;
import com.codeim.youliao.http.HttpException;
import com.codeim.youliao.http.Response;

/**
 * Manages retrieval and storage of icon images. Use the put method to download
 * and store images. Use the get method to retrieve images from the manager.
 */
public class ImageManager implements ImageCache {
	private static final String TAG = "ImageManager";

	// 饭否目前最大宽度支持596px, 超过则同比缩小
	// 最大高度为1192px, 超过从中截取
	public static final int DEFAULT_COMPRESS_QUALITY = 90;
	public static final int IMAGE_MAX_WIDTH = 596;
	public static final int IMAGE_MAX_HEIGHT = 1192;

	private Context mContext;
	// In memory cache.
	private Map<String, SoftReference<Bitmap>> mCache;
	// MD5 hasher.
	private MessageDigest mDigest;

	public static Bitmap drawableToBitmap(Drawable drawable) {
		Bitmap bitmap = Bitmap.createBitmap(
						drawable.getIntrinsicWidth(),
						drawable.getIntrinsicHeight(),
						drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
								: Bitmap.Config.RGB_565);
		Canvas canvas = new Canvas(bitmap);
		drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
		drawable.draw(canvas);
		return bitmap;
	}

	public ImageManager(Context context) {
		mContext = context;
		mCache = new HashMap<String, SoftReference<Bitmap>>();

		try {
			mDigest = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			// This shouldn't happen.
			throw new RuntimeException("No MD5 algorithm.");
		}
	}

	public void setContext(Context context) {
		mContext = context;
	}

	private String getHashString(MessageDigest digest) {
		StringBuilder builder = new StringBuilder();

		for (byte b : digest.digest()) {
			builder.append(Integer.toHexString((b >> 4) & 0xf));
			builder.append(Integer.toHexString(b & 0xf));
		}

		return builder.toString();
	}

	// MD5 hases are used to generate filenames based off a URL.
	private String getMd5(String url) {
		mDigest.update(url.getBytes());

		return getHashString(mDigest);
	}

	// Looks to see if an image is in the file system.
	private Bitmap lookupFile(String url) {
		String hashedUrl = getMd5(url);
		FileInputStream fis = null;

		try {
			fis = mContext.openFileInput(hashedUrl);
			return BitmapFactory.decodeStream(fis);
		} catch (FileNotFoundException e) {
			// Not there.
			return null;
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					// Ignore.
				}
			}
		}
	}

	/**
	 * Downloads a file
	 * 
	 * @param url
	 * @return
	 * @throws HttpException
	 */
	public Bitmap downloadImage(String url) throws HttpException {
		Log.d(TAG, "Fetching image: " + url);
		Response res = TwitterApplication.mApi.getHttpClient().get(url);
		return BitmapFactory.decodeStream(new BufferedInputStream(res
				.asStream()));
	}

	public Bitmap downloadImage2(String url) throws HttpException {
		Log.d(TAG, "[NEW]Fetching image: " + url);
		final Response res = TwitterApplication.mApi.getHttpClient().get(url);
		String file = writeToFile(res.asStream(), getMd5(url));
		return BitmapFactory.decodeFile(file);
	}

	/**
	 * 下载远程图片 -> 转换为Bitmap -> 写入缓存器.
	 * 
	 * @param url
	 * @param quality
	 *            image quality 1～100
	 * @throws HttpException
	 */
	public void put(String url, int quality, boolean forceOverride) throws HttpException {
		if (!forceOverride && contains(url)) {
			// Image already exists.
			return;

			// TODO: write to file if not present.
		}

		Bitmap bitmap = downloadImage(url);
		if (bitmap != null) {
			put(url, bitmap, quality); // file cache
		} else {
			Log.w(TAG, "Retrieved bitmap is null.");
		}
	}

	/**
	 * 重载 put(String url, int quality)
	 * 
	 * @param url
	 * @throws HttpException
	 */
	public void put(String url) throws HttpException {
		put(url, DEFAULT_COMPRESS_QUALITY, false);
	}

	/**
	 * 将本地File -> 转换为Bitmap -> 写入缓存器. 如果图片大小超过MAX_WIDTH/MAX_HEIGHT, 则将会对图片缩放.
	 * 
	 * @param file
	 * @param quality
	 *            图片质量(0~100)
	 * @param forceOverride
	 * @throws IOException
	 */
	public void put(File file, int quality, boolean forceOverride) throws IOException {
		if (!file.exists()) {
			Log.w(TAG, file.getName() + " is not exists.");
			return;
		}
		if (!forceOverride && contains(file.getPath())) {
			// Image already exists.
			Log.d(TAG, file.getName() + " is exists");
			return;
			// TODO: write to file if not present.
		}

		Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
		// bitmap = resizeBitmap(bitmap, MAX_WIDTH, MAX_HEIGHT);

		if (bitmap == null) {
			Log.w(TAG, "Retrieved bitmap is null.");
		} else {
			put(file.getPath(), bitmap, quality);
		}
	}

	/**
	 * 将Bitmap写入缓存器.
	 * 
	 * @param filePath
	 *            file path
	 * @param bitmap
	 * @param quality
	 *            1~100
	 */
	public void put(String file, Bitmap bitmap, int quality) {
		synchronized (this) {
			mCache.put(file, new SoftReference<Bitmap>(bitmap));
		}

		writeFile(file, bitmap, quality);
	}

	/**
	 * 重载 put(String file, Bitmap bitmap, int quality)
	 * 
	 * @param filePath
	 *            file path
	 * @param bitmap
	 * @param quality
	 *            1~100
	 */
	@Override
	public void put(String file, Bitmap bitmap) {
		put(file, bitmap, DEFAULT_COMPRESS_QUALITY);
	}

	/**
	 * 将Bitmap写入本地缓存文件.
	 * 
	 * @param file
	 *            URL/PATH
	 * @param bitmap
	 * @param quality
	 */
	private void writeFile(String file, Bitmap bitmap, int quality) {
		if (bitmap == null) {
			Log.w(TAG, "Can't write file. Bitmap is null.");
			return;
		}

		BufferedOutputStream bos = null;
		try {
			String hashedUrl = getMd5(file);
			bos = new BufferedOutputStream(mContext.openFileOutput(hashedUrl,
					Context.MODE_PRIVATE));
			bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos); // PNG
			Log.d(TAG, "Writing file: " + file);
		} catch (IOException ioe) {
			Log.e(TAG, ioe.getMessage());
		} finally {
			try {
				if (bos != null) {
					bitmap.recycle();
					bos.flush();
					bos.close();
				}
				// bitmap.recycle();
			} catch (IOException e) {
				Log.e(TAG, "Could not close file.");
			}
		}
	}

	private String writeToFile(InputStream is, String filename) {
		Log.d("LDS", "new write to file");
		BufferedInputStream in = null;
		BufferedOutputStream out = null;
		try {
			in = new BufferedInputStream(is);
			out = new BufferedOutputStream(mContext.openFileOutput(filename,
					Context.MODE_PRIVATE));
			byte[] buffer = new byte[1024];
			int l;
			while ((l = in.read(buffer)) != -1) {
				out.write(buffer, 0, l);
			}
		} catch (IOException ioe) {
			ioe.printStackTrace();
		} finally {
			try {
				if (in != null)
					in.close();
				if (out != null) {
					Log.d("LDS", "new write to file to -> " + filename);
					out.flush();
					out.close();
				}
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
		return mContext.getFilesDir() + "/" + filename;
	}

	public Bitmap get(File file) {
		return get(file.getPath());
	}

	/**
	 * 判断缓存着中是否存在该文件对应的bitmap
	 */
	public boolean isContains(String file) {
		return mCache.containsKey(file);
	}

	/**
	 * 获得指定file/URL对应的Bitmap，首先找本地文件，如果有直接使用，否则去网上获取
	 * 
	 * @param file
	 *            file URL/file PATH
	 * @param bitmap
	 * @param quality
	 * @throws HttpException
	 */
	public Bitmap safeGet(String file) throws HttpException {
		Bitmap bitmap = lookupFile(file); // first try file.

		if (bitmap != null) {
			synchronized (this) { // memory cache
				mCache.put(file, new SoftReference<Bitmap>(bitmap));
			}
			return bitmap;
		} else { // get from web
			String url = file;
			bitmap = downloadImage2(url);

			// 注释掉以测试新的写入文件方法
			// put(file, bitmap); // file Cache
			return bitmap;
		}
	}

	/**
	 * 从缓存器中读取文件
	 * 
	 * @param file
	 *            file URL/file PATH
	 * @param bitmap
	 * @param quality
	 */
	public Bitmap get(String file) {
		SoftReference<Bitmap> ref;
		Bitmap bitmap;

		// Look in memory first.
		synchronized (this) {
			ref = mCache.get(file);
		}

		if (ref != null) {
			bitmap = ref.get();

			if (bitmap != null) {
				return bitmap;
			}
		}

		// Now try file.
		bitmap = lookupFile(file);

		if (bitmap != null) {
			synchronized (this) {
				mCache.put(file, new SoftReference<Bitmap>(bitmap));
			}

			return bitmap;
		}

		// TODO: why?
		// upload: see profileImageCacheManager line 96
		Log.w(TAG, "Image is missing: " + file);
		// return the default photo
		return mDefaultBitmap;
	}

	public boolean contains(String url) {
		return get(url) != mDefaultBitmap;
	}

	public void clear() {
		String[] files = mContext.fileList();

		for (String file : files) {
			mContext.deleteFile(file);
		}

		synchronized (this) {
			mCache.clear();
		}
	}

	public void cleanup(HashSet<String> keepers) {
		String[] files = mContext.fileList();
		HashSet<String> hashedUrls = new HashSet<String>();

		for (String imageUrl : keepers) {
			hashedUrls.add(getMd5(imageUrl));
		}

		for (String file : files) {
			if (!hashedUrls.contains(file)) {
				Log.d(TAG, "Deleting unused file: " + file);
				mContext.deleteFile(file);
			}
		}
	}

	/**
	 * Compress and resize the Image
	 * 
	 * <br />
	 * 因为不论图片大小和尺寸如何, 饭否都会对图片进行一次有损压缩, 所以本地压缩应该 考虑图片将会被二次压缩所造成的图片质量损耗
	 * 
	 * @param targetFile
	 * @param quality
	 *            , 0~100, recommend 100
	 * @return
	 * @throws IOException
	 */
	public File compressImage(File targetFile, int quality) throws IOException {
		String filepath = targetFile.getAbsolutePath();

		// 1. Calculate scale
		int scale = 1;
		BitmapFactory.Options o = new BitmapFactory.Options();
		o.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(filepath, o);
		if (o.outWidth > IMAGE_MAX_WIDTH || o.outHeight > IMAGE_MAX_HEIGHT) {
			scale = (int) Math.pow(
					2.0,
					(int) Math.round(Math.log(IMAGE_MAX_WIDTH
							/ (double) Math.max(o.outHeight, o.outWidth))
							/ Math.log(0.5)));
			// scale = 2;
		}
		Log.d(TAG, scale + " scale");

		// 2. File -> Bitmap (Returning a smaller image)
		o.inJustDecodeBounds = false;
		o.inSampleSize = scale;
		Bitmap bitmap = BitmapFactory.decodeFile(filepath, o);

		// 2.1. Resize Bitmap
		// bitmap = resizeBitmap(bitmap, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT);

		// 3. Bitmap -> File
		writeFile(filepath, bitmap, quality);

		// 4. Get resized Image File
		String filePath = getMd5(targetFile.getPath());
		File compressedImage = mContext.getFileStreamPath(filePath);
		return compressedImage;
	}

	/**
	 * 保持长宽比缩小Bitmap
	 * 
	 * @param bitmap
	 * @param maxWidth
	 * @param maxHeight
	 * @param quality
	 *            1~100
	 * @return
	 */
	public Bitmap resizeBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {

		int originWidth = bitmap.getWidth();
		int originHeight = bitmap.getHeight();

		// no need to resize
		if (originWidth < maxWidth && originHeight < maxHeight) {
			return bitmap;
		}

		int newWidth = originWidth;
		int newHeight = originHeight;

		// 若图片过宽, 则保持长宽比缩放图片
		if (originWidth > maxWidth) {
			newWidth = maxWidth;

			double i = originWidth * 1.0 / maxWidth;
			newHeight = (int) Math.floor(originHeight / i);

			bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight,
					true);
		}

		// 若图片过长, 则从中部截取
		if (newHeight > maxHeight) {
			newHeight = maxHeight;

			int half_diff = (int) ((originHeight - maxHeight) / 2.0);
			bitmap = Bitmap.createBitmap(bitmap, 0, half_diff, newWidth,
					newHeight);
		}

		Log.d(TAG, newWidth + " width");
		Log.d(TAG, newHeight + " height");

		return bitmap;
	}

}
