package com.heaven7.third.core;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.File;

/**
 * Created by heaven7 on 2015/9/15.
 */
public class ImageParser {

    public interface IDecoder{
        Bitmap decode(DecodeParam param, BitmapFactory.Options options);
    }
    /** decode param */
    public class DecodeParam{
        public String pathName;
        public byte[] imageArray;
        public int resId ; //image resource id
        public Resources resources;

        public DecodeParam(String pathName) {
            this.pathName = pathName;
        }
        public DecodeParam(byte[] imageArray) {
            this.imageArray = imageArray;
        }
        public DecodeParam(Resources resources,int resId) {
            this.resId = resId;
            this.resources = resources;
        }
    }
    private static final IDecoder sPathDecoder = new IDecoder() {
        @Override
        public Bitmap decode(DecodeParam param, BitmapFactory.Options options) {
            return BitmapFactory.decodeFile(param.pathName,options);
        }
    };
    private static final IDecoder sResourceDecoder = new IDecoder() {
        @Override
        public Bitmap decode(DecodeParam param, BitmapFactory.Options options) {
            return BitmapFactory.decodeResource(param.resources,param.resId,options);
        }
    };
    private static final IDecoder sByteArrayDecoder = new IDecoder() {
        @Override
        public Bitmap decode(DecodeParam param, BitmapFactory.Options options) {
            final byte[] bytes = param.imageArray;
            return BitmapFactory.decodeByteArray(bytes,0,bytes.length,options);
        }
    };

    /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */
    private static final Object sDecodeLock = new Object();

    private final int mMaxWidth;
    private final int mMaxHeight;
    private final Bitmap.Config mDecodeConfig;

    public ImageParser(int mMaxWidth, int mMaxHeight){
        this(mMaxWidth,mMaxHeight, Bitmap.Config.RGB_565);
    }
    public ImageParser(int mMaxWidth, int mMaxHeight, Bitmap.Config config) {
        this.mMaxWidth = mMaxWidth;
        this.mMaxHeight = mMaxHeight;
        this.mDecodeConfig = config;
    }

    public Bitmap parseToBitmap(String pathName){
        File file = new File(pathName);
        if (!file.exists()) {
            return null;
        }
        return decodeToBitmap(sPathDecoder,new DecodeParam(pathName));
    }
    private Bitmap decodeToBitmap(IDecoder decoder ,DecodeParam param){
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap;
        if (mMaxWidth == 0 && mMaxHeight == 0) {
            decodeOptions.inPreferredConfig = mDecodeConfig;
            synchronized (sDecodeLock) {
                bitmap = decoder.decode(param, decodeOptions);
            }
        } else {
            // If we have to resize this image, first get the natural bounds.
            decodeOptions.inJustDecodeBounds = true;
            synchronized (sDecodeLock) {
                decoder.decode(param, decodeOptions); //just decode bounds
            }
            int actualWidth = decodeOptions.outWidth;
            int actualHeight = decodeOptions.outHeight;

            // Then compute the dimensions we would ideally like to decode to.
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight);
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                    actualHeight, actualWidth);

            // Decode to the nearest power of two scaling factor.
            decodeOptions.inJustDecodeBounds = false;
            // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
            // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
            decodeOptions.inSampleSize =
                    findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
            Bitmap tempBitmap;
            synchronized (sDecodeLock) {
                tempBitmap = decoder.decode(param, decodeOptions);
            }
            // If necessary, scale down to the maximal acceptable size.
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desiredHeight)) {
                bitmap = Bitmap.createScaledBitmap(tempBitmap,
                        desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }
        return bitmap;
    }

    public Bitmap parseToBitmap(byte[] data){
        return decodeToBitmap(sByteArrayDecoder,new DecodeParam(data));
    }
    public Bitmap parseToBitmap(Context context, int resId){
        return decodeToBitmap(sResourceDecoder,new DecodeParam(context.getResources(),resId));
    }

    /**
     * Scales one side of a rectangle to fit aspect ratio.
     *
     * @param maxPrimary Maximum size of the primary dimension (i.e. width for
     *        max width), or zero to maintain aspect ratio with secondary
     *        dimension
     * @param maxSecondary Maximum size of the secondary dimension, or zero to
     *        maintain aspect ratio with primary dimension
     * @param actualPrimary Actual size of the primary dimension
     * @param actualSecondary Actual size of the secondary dimension
     */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
                                           int actualSecondary) {
        // If no dominant value at all, just return the actual.
        if (maxPrimary == 0 && maxSecondary == 0) {
            return actualPrimary;
        }

        // If primary is unspecified, scale primary to match secondary's scaling ratio.
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        if (maxSecondary == 0) {
            return maxPrimary;
        }

        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

    /**
     * Returns the largest power-of-two divisor for use in downscaling a bitmap
     * that will not result in the scaling past the desired dimensions.
     *
     * @param actualWidth Actual width of the bitmap
     * @param actualHeight Actual height of the bitmap
     * @param desiredWidth Desired width of the bitmap
     * @param desiredHeight Desired height of the bitmap
     */
    // Visible for testing.
    static int findBestSampleSize(
            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }

        return (int) n;
    }
}