package jp.agentec.abook.abv.cl.util;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.adf.util.FileUtil;
import jp.agentec.adf.util.RuntimeUtil;
import jp.agentec.adf.util.StringUtil;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;

public class BitmapUtil {
	private static final String TAG = "BitmapUtil";

	/**
	 * 画像を元のサイズのままで読み込みます。
	 * @param imagePath 画像のフルパスです。
	 * @param config 画像のdecode設定です。
	 * @return 画像の{@link Bitmap}インスタンスを返します。
	 * @since 1.0.0
	 */
	public static Bitmap getBitmap(String imagePath, Config config) {
		if (StringUtil.isNullOrWhiteSpace(imagePath)) {
			return null;
		}
		
		Bitmap bitmap = null;
		try {
			BitmapFactory.Options options = new BitmapFactory.Options();
			options.inSampleSize = 1;
			options.inPreferredConfig = config;
			bitmap = BitmapFactory.decodeFile(imagePath, options);
		} catch (OutOfMemoryError e) {
			Logger.e("getThumbnailResizedBitmap failed.", e);
		}
		
		return bitmap;
	}
	/**
	 * 画像を元のサイズのままで読み込みます。
	 * @param resource 画像のフルパスです。
	 * @param config 画像のdecode設定です。
	 * @return 画像の{@link Bitmap}インスタンスを返します。
	 * @since 1.0.0
	 */
	public static Bitmap getBitmapResource(Resources resource, int id, Config config) {
		if (resource == null) {
			return null;
		}
		
		Bitmap bitmap = null;
		try {
			BitmapFactory.Options options = new BitmapFactory.Options();
			options.inSampleSize = 1;
			options.inPreferredConfig = config;
			bitmap = BitmapFactory.decodeResource(resource, id, options);
		} catch (OutOfMemoryError e) {
			Logger.e("getThumbnailResizedBitmap failed.", e);
		}
		
		return bitmap;
	}
	
	
	/**
	 * 指定イメージファイルリストの想定ビットマップ合計サイズを返す
	 * 
	 * @param pathList
	 * @param config
	 * @return
	 */
	public static long getTotalBitmapSize(List<String> pathList, Bitmap.Config config) {
		// 1ピクセルあたりのバイト数
		int bytePerPixel = (config == Config.ARGB_4444 || config == Config.RGB_565)? 2: (config == Config.ARGB_8888)? 4: 1;
		long totalBytes = 0;
		for (String path : pathList) {
			try {
				BitmapFactory.Options options = getBitmapDimensions(path);
				if (options.outWidth != -1 && options.outHeight != -1) {
					totalBytes += options.outWidth * options.outHeight * bytePerPixel;
				}
			} catch (Exception e) { // エラーは無視
				Logger.e(TAG, "getTotalBitmapSize", e);
			}
		}
		return totalBytes;
	}

	/**
	 * 画像をリサイズして返す
	 * 
	 * @param path
	 * @param resizeWidth
	 * @param resizeHeight
	 * @param config
	 * @return
	 */
	public static Bitmap getResizedBitmap(String path, int resizeWidth, int resizeHeight, Bitmap.Config config) {
		return getResizedBitmap(path, resizeWidth, resizeHeight, config, true);
	}

	/**
	 * 画像をリサイズして返す
	 * 
	 * @param path
	 * @param resizeWidth
	 * @param resizeHeight
	 * @param config
	 * @param keepAspect
	 * @return
	 */
	public static Bitmap getResizedBitmap(String path, int resizeWidth, int resizeHeight, Bitmap.Config config, boolean keepAspect) {
	    Bitmap resizedBitmap = null;
	    try {
	        BitmapFactory.Options options = getBitmapDimensions(path);
	        int sampleSize = calculateSampleSize(options.outWidth, options.outHeight, resizeWidth, resizeHeight);
	        Logger.v(TAG, "getResizedBitmap outWidth=%s outHeight=%s resizeWidth=%s resizeHeight=%s sampleSize=%s", options.outWidth, options.outHeight, resizeWidth, resizeHeight, sampleSize);

	        BitmapFactory.Options outOptions = new BitmapFactory.Options();
		    outOptions.inJustDecodeBounds = false;
		    outOptions.inSampleSize = sampleSize;
		    outOptions.inPreferredConfig = config;
		    Bitmap bitmap =  BitmapFactory.decodeFile(path, outOptions);
		    resizedBitmap = resizeBitmap(bitmap, resizeWidth, resizeHeight, options.outWidth, options.outHeight, keepAspect);
	    } catch (OutOfMemoryError e) {
	    	Logger.e("BitmapUtil", "getResizedBitmap", e);
	        //handle the exception(s)
	    }

	    return resizedBitmap;
	}
	
	/**
	 * 画像をリサイズして返す
	 * 
	 * @param resource
	 * @param id
	 * @param resizeWidth
	 * @param resizeHeight
	 * @param config
	 * @return
	 */
	public static Bitmap getResizedBitmapResource(Resources resource, int id, int resizeWidth, int resizeHeight, Config config) {
		return getResizedBitmapResource(resource, id, resizeWidth, resizeHeight, config, true);
	}

	/**
	 * 画像をリサイズして返す
	 * 
	 * @param resource
	 * @param id
	 * @param resizeWidth
	 * @param resizeHeight
	 * @param config
	 * @param keepAspect
	 * @return
	 */
	public static Bitmap getResizedBitmapResource(Resources resource, int id, int resizeWidth, int resizeHeight, Config config, boolean keepAspect) {
    	Logger.v(TAG, "getResizedBitmap resizeWidth=%s resizeHeight=%s", resizeWidth, resizeHeight);
	    Bitmap resizedBitmap = null;
	    try {
	        BitmapFactory.Options options = getBitmapDimensions(resource, id);
	        int sampleSize = calculateSampleSize(options.outWidth, options.outHeight, resizeWidth, resizeHeight);
	        Logger.v(TAG, "getResizedBitmap outWidth=%s outHeight=%s resizeWidth=%s resizeHeight=%s sampleSize=%s", options.outWidth, options.outHeight, resizeWidth, resizeHeight, sampleSize);

	        BitmapFactory.Options outOptions = new BitmapFactory.Options();
		    outOptions.inJustDecodeBounds = false;
		    outOptions.inSampleSize = sampleSize;
		    outOptions.inPreferredConfig = config;
		    Bitmap bitmap =  BitmapFactory.decodeResource(resource, id, outOptions);
		    resizedBitmap = resizeBitmap(bitmap, resizeWidth, resizeHeight, options.outWidth, options.outHeight, keepAspect);
	    } catch (OutOfMemoryError e) {
	    	Logger.e("BitmapUtil", "getResizedBitmap", e);
	        //handle the exception(s)
	    }

	    return resizedBitmap;
	}

	public static Bitmap resizeBitmap(Bitmap bitmap, int resizeWidth, int resizeHeight, int orgWidth, int orgHeight, boolean keepAspect) {
		Bitmap resizedBitmap = null;
		if (bitmap != null) {
			if (keepAspect) {
				float scale = Math.min((float)resizeWidth / orgWidth, (float)resizeHeight / orgHeight);
				resizeWidth = (int) (orgWidth * scale);
				resizeHeight = (int) (orgHeight * scale);
			}
			
			resizedBitmap  = Bitmap.createScaledBitmap(bitmap, resizeWidth, resizeHeight, true);
			if (!resizedBitmap.equals(bitmap)) {
				bitmap.recycle();
			}			
		}
		return resizedBitmap;
	}

	public static BitmapFactory.Options getBitmapDimensions(String path) {
	    BitmapFactory.Options options = new BitmapFactory.Options();
	    options.inJustDecodeBounds = true;
	    BitmapFactory.decodeFile(path, options);
	    return options;
	}

	private static BitmapFactory.Options getBitmapDimensions(Resources resource, int id) {
	    BitmapFactory.Options options = new BitmapFactory.Options();
	    options.inJustDecodeBounds = true;
	    BitmapFactory.decodeResource(resource, id, options);
	    return options;
	}

	/**
	 * サンプルサイズを計算する
	 * 
	 * @param width
	 * @param height
	 * @param targetWidth
	 * @param targetHeight
	 * @return
	 */
	private static int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
	    if (height < targetHeight || width < targetWidth) {
	        return 1;
	    }

	    return Math.min(Math.round((float) height / targetHeight), Math.round((float) width / targetWidth));
	}

	/**
	 * QRCodeを生成して返す。
	 * 
	 * @param url
	 * @param size
	 * @return
	 * @throws WriterException
	 */
	public static Bitmap createQrCode(String url, int size) throws WriterException {
		// エンコード
		BitMatrix bMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, size, size);
		
		// ピクセル作成
		int width = bMatrix.getWidth();
		int height = bMatrix.getHeight();
		int[] pixels = new int[width * height];
		// データがあるところだけ黒にする
		for (int y = 0; y < height; y++) {
		    int offset = y * width;
		    for (int x = 0; x < width; x++) {
		        pixels[offset + x] = bMatrix.get(x, y) ? Color.BLACK : Color.WHITE;
		    }
		}
		Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
		bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
		return bitmap;
	}

	/**
	 * Bitmapをディレクトリに出力する
	 * @param recycle 
	 */
	public static boolean outputFile(Bitmap bm, String imagePath, boolean recycle) {
		FileOutputStream output = null;
		boolean ret = false;
		try {
			FileUtil.createParentDirectory(imagePath);
			output = new FileOutputStream(imagePath);
			if (bm.isRecycled()) {
				return false;
			}
			bm.compress(Bitmap.CompressFormat.JPEG, 100, output);
			ret = true;
		} catch (FileNotFoundException e) {
			Logger.e(TAG, "outputFile failed. imagePath=" + imagePath, e);
		} catch (IllegalStateException e) { // すでにrecycleされていた場合、compressで失敗する。無視。
			Logger.w(TAG, "outputFile failed. imagePath=" + imagePath, e);
		}
		finally {
			if (output != null) {
				try {
					output.close();
				} catch (IOException e) {}
			}
			if (recycle && !bm.isRecycled()) {
				bm.recycle();
				Logger.d(TAG, "Bitmap recycled. imagePath=%s", imagePath);
			}
		}
		return ret;
	}

	/**
	 * サムネイルを作成して、指定パスに保存する
	 * 
	 * @param moviePath
	 * @param time
	 * @param paramMp
	 * @param imagePath
	 * @return
	 * @throws IOException 
	 */
	public static boolean createMovieThumbnail(String moviePath, long time, MediaPlayer paramMp, String imagePath) throws IOException {
		try {
			RuntimeUtil.exec("chmod 755 "+ FileUtil.getParentPath(moviePath));
			RuntimeUtil.exec("chmod 755 "+ moviePath);
			Bitmap bitmap = BitmapUtil.getMovieThumbnail(moviePath, time, paramMp);
			if (bitmap != null) {
				return outputFile(bitmap, imagePath, true);
			}
			return false;
		}
		finally {
			RuntimeUtil.exec("chmod 751 "+ moviePath); // 戻す
			RuntimeUtil.exec("chmod 751 "+ FileUtil.getParentPath(moviePath)); // 戻す
		}
	}
	
	/**
	 * 指定メディアのサムネイル・ビットマップを返す
	 * 注意：特定の端末で取得失敗したりMedia service deadでOS自体が固まる恐れがある。
	 * 
	 * @param mediaPath
	 * @param time 開始時間からの秒数 0未満の場合最後の時間とする
	 * @param paramMp
	 * @return
	 */
    public static Bitmap getMovieThumbnail(String mediaPath, long time, MediaPlayer paramMp) {
		Bitmap thumbnail = null;
		MediaMetadataRetriever retriever = null;
		MediaPlayer mp = paramMp;
		try {
			retriever = new MediaMetadataRetriever();
			// IllegalArgumentExceptionがthrowされるときがある[Build.VERSION.SDK_INT >= 14でsetDataSource(path, new HashMap<String, String>())を使えとあるが更に悪くなる
//			if (Build.VERSION.SDK_INT >= 14) {
//				retriever.setDataSource(mediaPath, new HashMap<String, String>());
//			}
//			else {
				retriever.setDataSource(mediaPath);
//			}

			if (mp == null) {
				mp = new MediaPlayer(); // 実際の再生のサイズを取得する（prepareまででサイズは取得可能）
			}
			mp.setDataSource(mediaPath);
			mp.prepare();
			int videoWidth = mp.getVideoWidth();
			int videoHeight = mp.getVideoHeight();

			//秒単位で指定
			if (time < 0) {
				time = mp.getDuration(); // timeがマイナスの場合最終秒数にする
			}
			thumbnail = retriever.getFrameAtTime(1000 * time); // 単位はマイクロ秒
			if (thumbnail != null) {
				int thumWidth = thumbnail.getWidth();
				int thumHeight = thumbnail.getHeight();

				Logger.d(TAG, "path=%s, tw=%s, th=%s, vw=%s, vh=%s", mediaPath, thumWidth, thumHeight, videoWidth, videoHeight);
				float aspectDiff = ((float)videoWidth/videoHeight) / ((float)thumWidth/thumHeight);
				if (aspectDiff > 1.05 || aspectDiff < 0.95) { // 5%以上ずれている場合変換する
					Logger.w(TAG, "Video thumbnail aspect ratio is not correct. execute resize. aspectDiff=%s path=%s", aspectDiff, mediaPath);
					thumbnail = resizeBitmap(thumbnail, videoWidth, videoHeight, thumWidth, thumHeight, false);
				}
			}
		} catch (Exception e) {
			Logger.e(TAG, "[getCustomThumbnail]: error. path=" + mediaPath, e);
		}
		finally {
			if (mp != null && paramMp == null) { // MediaPlayerをパラメータで渡されなかった場合
				try {
					mp.reset();
					mp.release();
				} catch (Exception e2) {
					Logger.e(TAG, "[getCustomThumbnail]:mp.release() error.", e2);
				}
			}
			if (retriever != null) {
				try {
					retriever.release();
				} catch (Exception e2) {
					Logger.e(TAG, "[getCustomThumbnail]:retriever.release() error.", e2);
				}
			}
		}

		return thumbnail;
	}

    /**
     * テロップの横の長さが表示できる範囲を超えた場合、表示できる範囲に合わせてBitmapを割る
     * 横の長さが表示できる範囲未満の場合は、List<Bitmap>のインデックス0のみで返す。
     * bitmapがnullの場合はnullで返す。
     *
     * @param bitmap　テロップのbitmap
     * @param displayBitmapWidth　CanvasのmaxBitmapWidth
     * @param bitmapWidth　引数bitmapの横の長さ
     * @return
     */
    public static List<Bitmap> splitWidthBitmap(Bitmap bitmap, int displayBitmapWidth, int bitmapWidth) {
        if (bitmap == null) {
            return null;
        }
        List<Bitmap> bitmapList = new ArrayList<>();
        if (bitmapWidth > displayBitmapWidth) {
            int bitmapHeight = bitmap.getHeight();
            int splitBitmapSize = (int) Math.ceil((float) bitmapWidth / displayBitmapWidth);
            int fromWidth = 0;
            int toWidth = displayBitmapWidth;

            for (int i = 0; i < splitBitmapSize; i++) {
                bitmapList.add(Bitmap.createBitmap(bitmap, fromWidth, 0, toWidth, bitmapHeight));
                fromWidth += displayBitmapWidth;
                if (displayBitmapWidth > bitmapWidth - fromWidth) {
                    toWidth = bitmapWidth - fromWidth;
                }
            }
            bitmap.recycle();
        } else {
            bitmapList.add(bitmap);
        }

        return bitmapList;
    }
}
