package allen.town.podcast.core.glide;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Priority;
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import okhttp3.Call;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ResizingOkHttpStreamFetcher extends OkHttpStreamFetcher {
    private static final String TAG = "ResizingOkHttpStreamFet";
    private static final int MAX_DIMENSIONS = 1500;
    private static final int MAX_FILE_SIZE = 1024 * 1024; // 1 MB

    private FileInputStream stream;
    private File tempIn;
    private File tempOut;

    public ResizingOkHttpStreamFetcher(Call.Factory client, GlideUrl url) {
        super(client, url);
    }

    @Override
    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
        super.loadData(priority, new DataCallback<InputStream>() {
            @Override
            public void onDataReady(@Nullable InputStream data) {
                if (data == null) {
                    callback.onDataReady(null);
                    return;
                }
                try {
                    tempIn = File.createTempFile("resize_", null);
                    tempOut = File.createTempFile("resize_", null);
                    OutputStream outputStream = new FileOutputStream(tempIn);
                    IOUtils.copy(data, outputStream);
                    outputStream.close();
                    IOUtils.closeQuietly(data);

                    if (tempIn.length() <= MAX_FILE_SIZE) {
                        try {
                            stream = new FileInputStream(tempIn);
                            callback.onDataReady(stream); // Just deliver the original, non-scaled image
                        } catch (FileNotFoundException fileNotFoundException) {
                            callback.onLoadFailed(fileNotFoundException);
                        }
                        return;
                    }

                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inJustDecodeBounds = true;
                    FileInputStream in = new FileInputStream(tempIn);
                    BitmapFactory.decodeStream(in, null, options);
                    IOUtils.closeQuietly(in);

                    if (options.outWidth == -1 || options.outHeight == -1) {
                        throw new IOException("Not a valid image");
                    } else if (Math.max(options.outHeight, options.outWidth) >= MAX_DIMENSIONS) {
                        double sampleSize = (double) Math.max(options.outHeight, options.outWidth) / MAX_DIMENSIONS;
                        options.inSampleSize = (int) Math.pow(2d, Math.floor(Math.log(sampleSize) / Math.log(2d)));
                    }

                    options.inJustDecodeBounds = false;
                    in = new FileInputStream(tempIn);
                    Bitmap bitmap = BitmapFactory.decodeStream(in, null, options);
                    IOUtils.closeQuietly(in);

                    Bitmap.CompressFormat format = Build.VERSION.SDK_INT < 30
                            ? Bitmap.CompressFormat.WEBP : Bitmap.CompressFormat.WEBP_LOSSY;

                    int quality = 100;
                    while (true) {
                        FileOutputStream out = new FileOutputStream(tempOut);
                        bitmap.compress(format, quality, out);
                        IOUtils.closeQuietly(out);

                        if (tempOut.length() > 3 * MAX_FILE_SIZE && quality >= 45) {
                            quality -= 40;
                        } else if (tempOut.length() > 2 * MAX_FILE_SIZE && quality >= 25) {
                            quality -= 20;
                        } else if (tempOut.length() > MAX_FILE_SIZE && quality >= 15) {
                            quality -= 10;
                        } else if (tempOut.length() > MAX_FILE_SIZE && quality >= 10) {
                            quality -= 5;
                        } else {
                            break;
                        }
                    }
                    bitmap.recycle();

                    stream = new FileInputStream(tempOut);
                    callback.onDataReady(stream);
                    Log.d(TAG, "compress image from " + tempIn.length() / 1024
                            + " to " + tempOut.length() / 1024 + " kB ,final quality: " + quality + "%");
                } catch (Throwable e) {
                    e.printStackTrace();

                    try {
                        stream = new FileInputStream(tempIn);
                        callback.onDataReady(stream); // Just deliver the original, non-scaled image
                    } catch (FileNotFoundException fileNotFoundException) {
                        e.printStackTrace();
                        callback.onLoadFailed(fileNotFoundException);
                    }
                }
            }

            @Override
            public void onLoadFailed(@NonNull Exception e) {
                callback.onLoadFailed(e);
            }
        });
    }

    @Override
    public void cleanup() {
        IOUtils.closeQuietly(stream);
        FileUtils.deleteQuietly(tempIn);
        FileUtils.deleteQuietly(tempOut);
        super.cleanup();
    }
}
