/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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.android.volley.toolbox;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.widget.ImageView.ScaleType;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyLog;

/**
 * A canned request for getting an image at a given URL and calling
 * back with a decoded Bitmap.
 */

/*
 * 继承扩展了 Request,指定了泛型为 <Bitmap>
 * 会将请求结果解析成 Bitmap 类型数据
 * 并且需要你传入:
 * 1. Response.Listener<Bitmap>:将解析结果数据 进行回调
 * 2. maxWidth:最大宽度
 * 3. maxHeight:最大高度
 * 4. ScaleType:ImageView 的 ScaleType( CENTER_CROP, FIT_XY ... )
 * 5. Config:Bitmap 的 Config ( ALPHA_8, RGB_565, ARGB_4444, ARGB_8888 )
 * 6. Response.ErrorListener:请求结果 Response 发生错误的回调接口
 */
public class ImageRequest extends Request<Bitmap> {
    /** Socket timeout in milliseconds for image requests */
    // ImageRequest 请求的 Socket 超时时间 -> 1000ms
    public static final int DEFAULT_IMAGE_TIMEOUT_MS = 1000;

    /** Default number of retries for image requests */
    // ImageRequest 的默认重试次数 -> 2
    public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;

    /** Default backoff multiplier for image requests */
    // ImageRequest 默认的 重试策略类的 退避乘数
    public static final float DEFAULT_IMAGE_BACKOFF_MULT = 2f;
    /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */
    /*
     * 全局解码锁
     * 所有 ImageRequest 都被这一个锁控制
     * 一次只能解析一张图片,避免 OOM
     */
    private static final Object sDecodeLock = new Object();
    // 解析结果数据 的回调接口
    private final Response.Listener<Bitmap> mListener;
    // Bitmap 的解析配置
    private final Config mDecodeConfig;
    // Bitmap 的最大宽度
    private final int mMaxWidth;
    // Bitmap 的最大高度
    private final int mMaxHeight;
    // ImageView 的 ScaleType( CENTER_CROP, FIT_XY ... )
    private ScaleType mScaleType;


    /**
     * Creates a new image request, decoding to a maximum specified width and
     * height. If both width and height are zero, the image will be decoded to
     * its natural size. If one of the two is nonzero, that dimension will be
     * clamped and the other one will be set to preserve the image's aspect
     * ratio. If both width and height are nonzero, the image will be decoded to
     * be fit in the rectangle of dimensions width x height while keeping its
     * aspect ratio.
     *
     * @param url URL of the image
     * @param listener Listener to receive the decoded bitmap
     * @param maxWidth Maximum width to decode this bitmap to, or zero for none
     * @param maxHeight Maximum height to decode this bitmap to, or zero for
     * none
     * @param scaleType The ImageViews ScaleType used to calculate the needed image size.
     * @param decodeConfig Format to decode the bitmap to
     * @param errorListener Error listener, or null to ignore errors
     */
    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight, ScaleType scaleType, Config decodeConfig, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        setRetryPolicy(new DefaultRetryPolicy(DEFAULT_IMAGE_TIMEOUT_MS, DEFAULT_IMAGE_MAX_RETRIES,
                DEFAULT_IMAGE_BACKOFF_MULT));
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxWidth = maxWidth;
        mMaxHeight = maxHeight;
        mScaleType = scaleType;
    }


    /**
     * For API compatibility with the pre-ScaleType variant of the constructor. Equivalent to
     * the normal constructor with {@code ScaleType.CENTER_INSIDE}.
     */
    /*
     * 默认 ScaleType 参数:ScaleType.CENTER_INSIDE
     */
    @Deprecated
    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight, Config decodeConfig, Response.ErrorListener errorListener) {
        this(url, listener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE, decodeConfig,
                errorListener);
    }


    /**
     * 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
     * @param scaleType The ScaleType used to calculate the needed image size.
     */
    /*
     * maxPrimary:最大宽度 或 最大高度,如果 getResizedDimension(...) 用于计算宽度,则传宽度;反之,传高度
     * maxSecondary:最大高度 或 最大宽度,如果 getResizedDimension(...) 用于计算高度,则传高度;反之,传宽度
     * actualPrimary:Bitmap 的实际宽度 或 最大高度
     *                如果 getResizedDimension(...) 用于计算宽度,则传Bitmap 的实际宽度;反之,传Bitmap 的实际高度
     * actualSecondary:Bitmap 的实际高度
     *                如果 getResizedDimension(...) 用于计算高度,则传Bitmap 的实际高度;反之,传Bitmap 的实际宽度
     * ScaleType:ImageView 的 ScaleType( CENTER_CROP, FIT_XY ... ),默认值:ScaleType.CENTER_INSIDE
     */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, int actualSecondary, ScaleType scaleType) {

        // If no dominant value at all, just return the actual.
        /*
         * 没有设置 最大宽度 和 最大高度
         * 如果 getResizedDimension(...) 用于计算宽度,则返回 Bitmap 的实际宽度
         * 如果 getResizedDimension(...) 用于计算高度,则返回 Bitmap 的实际高度
         */
        if ((maxPrimary == 0) && (maxSecondary == 0)) {
            return actualPrimary;
        }

        // If ScaleType.FIT_XY fill the whole rectangle, ignore ratio.
        /*
         * 如果设置了 ScaleType.FIT_XY,则进行填充
         */
        if (scaleType == ScaleType.FIT_XY) {
            /*
             * 如果 getResizedDimension(...) 用于计算宽度,返回 Bitmap 的实际宽度
             * 如果 getResizedDimension(...) 用于计算高度,返回 Bitmap 的实际高度
             */
            if (maxPrimary == 0) {
                return actualPrimary;
            }
            /*
             * 如果 getResizedDimension(...) 用于计算宽度,返回 设置好的 最大宽度
             * 如果 getResizedDimension(...) 用于计算高度,返回 设置好的 最大高度
             */
            return maxPrimary;
        }

        // If primary is unspecified, scale primary to match secondary's scaling ratio.
        /*
         * 1. 如果 getResizedDimension(...) 用于计算宽度:
         *      那么,这里缺少 最大宽度
         *      则,计算 最大高度 / Bitmap 实际高度 的比例
         *      然后,再让 Bitmap 实际宽度 * 上述比例 = 这样返回 需要的宽度
         *
         * 2. 如果 getResizedDimension(...) 用于计算高度:
         *      那么,这里缺少 最大高度
         *      则,计算 最大宽度 / Bitmap 实际宽度 的比例
         *      然后,再让 Bitmap 实际高度 * 上述比例 = 这样返回 需要的高度
         */
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        /*
         * 1. 如果 getResizedDimension(...) 用于计算宽度:
         *      那么,这里缺少 最大高度
         *      返回 最大宽度 即可
         *
         * 2. 如果 getResizedDimension(...) 用于计算高度:
         *      那么,这里缺少 最大宽度
         *      返回 最大高度 即可
         */
        if (maxSecondary == 0) {
            return maxPrimary;
        }

        /*****************************************
         * 以下为:最大宽度 和 最大高度 都存在的情况下 *
         *****************************************/

        /*
         * 1. 如果 getResizedDimension(...) 用于计算宽度:
         *      那么,这么计算的是:Bitmap 实际高度 / Bitmap 实际宽度
         *      得到的是 实际高宽比
         *
         * 2. 如果 getResizedDimension(...) 用于计算高度:
         *      那么,这么计算的是:Bitmap 实际宽度 / Bitmap 实际高度
         *      得到的是 实际宽高比
         */
        double ratio = (double) actualSecondary / (double) actualPrimary;

        /*
         * 1. 如果 getResizedDimension(...) 用于计算宽度:
         *      那么 最大宽度,记录在 resized
         *
         * 2. 如果 getResizedDimension(...) 用于计算高度:
         *      那么 最大高度,记录在 resized
         */
        int resized = maxPrimary;

        // If ScaleType.CENTER_CROP fill the whole rectangle, preserve aspect ratio.

        /*
         * 如果设置了 ScaleType.CENTER_CROP
         */
        if (scaleType == ScaleType.CENTER_CROP) {
            /*
             * ScaleType.CENTER_CROP 调整
             *
             * 1. 如果 getResizedDimension(...) 用于计算宽度:
             *      resized = 最大宽度
             *      ratio = 高宽比
             *      <1>.最大宽度 * 实际高宽比 < 最大高度:表示合格( 因为还小于 最大高度 ),根据
             *          实际宽高比调整的 高宽比 合格
             *          重新 利用 resized 复制上:调整宽度 = ( 最大高度 )maxSecondary / ratio ( 合格的高宽比 )
             *      <2.>不合格,就代表超过最大高度,直接返回 最大宽度 即可
             *
             * 2. 如果 getResizedDimension(...) 用于计算高度:
             *      resized = 最大高度
             *      ratio = 宽高比
             *      <1>.最大高度 * 实际宽高比 < 最大宽度:表示合格( 因为还小于 最大宽度 ),根据
             *          实际宽高比调整的 宽高比 合格
             *          重新 利用 resized 复制上:调整宽度 = ( 最大宽度 )maxSecondary / ratio ( 合格的宽高比 )
             *      <2.>不合格,就代表超过最大宽度,直接返回 最高高度 即可
             */
            if ((resized * ratio) < maxSecondary) {
                resized = (int) (maxSecondary / ratio);
            }
            return resized;
        }

        /*
         * 除了 ScaleType.FIT_XY 和 ScaleType.CENTER_CROP 以外的默认调整
         *
         *
         * 1. 如果 getResizedDimension(...) 用于计算宽度:
         *      resized = 最大宽度
         *      ratio = 高宽比
         *      <1>.最大宽度 * 实际高宽比 > 最大高度:表示合格( 因为大于 最大高度 ),根据
         *          实际高宽比 调整的 高宽比 合格
         *          重新 利用 resized 复制上:调整宽度 = ( 最大高度 )maxSecondary / ratio ( 合格的高宽比 )
         *      <2.>不合格,就代表超过最大高度,直接返回 最大宽度 即可
         *
         * 2. 如果 getResizedDimension(...) 用于计算高度:
         *      resized = 最大高度
         *      ratio = 宽高比
         *      <1>.最大高度 * 实际宽高比 > 最大宽度:表示合格( 因为大于 最大宽度 ),根据
         *          实际宽高比 调整的 宽高比 合格
         *          重新 利用 resized 复制上:调整宽度 = ( 最大宽度 )maxSecondary / ratio ( 合格的宽高比 )
         *      <2.>不合格,就代表超过最大宽度,直接返回 最高高度 即可
         */
        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
     */
    /*
     * 这是 Volley 的提供的算法
     * 根据 实际 Bitmap 宽高、需求宽高 计算得出 BitmapFactory.Options.inSampleSize
     * 如果 BitmapFactory.Options.inSampleSize = 4,那么宽高为 原 Bitmap 的 1/4
     *
     */
    // 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;
    }


    /*
     * 覆写 getPriority()
     * 修改优先级为:Priority.LOW ( 最低 )
     */
    @Override public Priority getPriority() {
        return Priority.LOW;
    }


    @Override protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
        // Serialize all decode on a global lock to reduce concurrent heap usage.
        // 解析 ImageRequest 的网络请求这块进行加锁,避免 OOM
        synchronized (sDecodeLock) {
            try {
                return doParse(response);
            } catch (OutOfMemoryError e) {
                // 发生 OOM,返回一个只带有 error 的 Response
                VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
                return Response.error(new ParseError(e));
            }
        }
    }


    /**
     * The real guts of parseNetworkResponse. Broken out for readability.
     */
    /*
     * 这里开始真正解析 网络请求结果( 响应 )NetworkResponse
     * NetworkResponse -> Response<Bitmap> 的转换
     */
    private Response<Bitmap> doParse(NetworkResponse response) {
        // 拿到结果数据
        byte[] data = response.data;
        // 实例化一个 BitmapFactory.Options 用于解析数据成 Bitmap
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        // 如果缺少 最大宽度 和 最大高度
        if (mMaxWidth == 0 && mMaxHeight == 0) {
            // 设置 BitmapFactory.Options.Config
            decodeOptions.inPreferredConfig = mDecodeConfig;
            // 开始生成 Bitmap
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            // If we have to resize this image, first get the natural bounds.

            /**
             * 如果存在 最大宽度 和 最大高度
             */

            /*
             * 由于一下四行操作只是想拿到这个 Bitmap 的自身的实际宽高,但又不想申请一个 Bitmap 内存
             * 可以设置 inJustDecodeBounds = true,只是读图片大小,不申请 Bitmap 内存
             * BitmapFactory.decodeByteArray(...) 的时候,就会 return null
             * 此时,再通过 BitmapFactory.Options 内被设置好的 outWidth 和 outHeight
             * 拿到该 Bitmap 的自身的实际宽高
             */
            decodeOptions.inJustDecodeBounds = true;
            /*
             * 这里正常是 Bitmap bitmap = BitmapFactory.decodeByteArray(...)
             * 但是由于上面设置了 inJustDecodeBounds = true
             * 这里一定返回 null
             * 但是这里为 BitmapFactory.Options 设置了 Bitmap 的数据参数
             * 所以下面能拿到 Bitmap 的实际宽高
             */
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
            // 记录该 Bitmap 的实际宽度
            int actualWidth = decodeOptions.outWidth;
            // 记录该 Bitmap 的实际高度
            int actualHeight = decodeOptions.outHeight;

            // Then compute the dimensions we would ideally like to decode to.

            // 根据 最宽高、Bitmap 实际宽高 以及 ImageView.ScaleType,计算出 需求宽度
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, actualWidth, actualHeight,
                    mScaleType);
            // 根据 最宽高、Bitmap 实际宽高 以及 ImageView.ScaleType,计算出 需求高度
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight,
                    actualWidth, mScaleType);

            // Decode to the nearest power of two scaling factor.
            // 关闭 inJustDecodeBounds,因为以下要进行真实的 Bitmap 内存申请
            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;

            /*
             * 计算缩放比例
             * 如果 BitmapFactory.Options.inSampleSize = 4,那么宽高为 原 Bitmap 的 1/4
             */
            decodeOptions.inSampleSize = findBestSampleSize(actualWidth, actualHeight, desiredWidth,
                    desiredHeight);
            // 解析出 测试 Bitmap
            Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            // If necessary, scale down to the maximal acceptable size.

            /*
             * 上面解析出的 测试 Bitmap
             * 如果 测试 Bitmap 的宽高 超过 需求宽高
             * 重新 根据 需求宽高 再拿 测试 Bitmap 解析一遍
             * 得到 最终 Bitmap 返回
             */
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desiredHeight)) {
                bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }
        /*
         * 没有解析出的 Bitmap,调用错误回调,回调一个 ParseError
         * 有解析出的 Bitmap,调用 解析结果数据 的回调接口,回调 Bitmap
         */
        if (bitmap == null) {
            return Response.error(new ParseError(response));
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }


    /*
     * 解析结果数据 的回调接口 开始 传递解析结果数据
     */
    @Override protected void deliverResponse(Bitmap response) {
        mListener.onResponse(response);
    }
}