package com.zhilin.patrol.tool;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.support.annotation.NonNull;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;

import static android.support.v4.util.Preconditions.checkNotNull;

/**
 * 图片压缩
 * Created by hanlli on 2017/8/16.
 */
public class Compresser {

    private static final String TAG = "Compress";

    public static final int FIRST_GEAR = 1;

    public static final int THIRD_GEAR = 3;

    public static final int CUSTOM_GEAR = 4;

    private static Compresser mInstance;


    public static Compresser getInstance() {
        if (mInstance == null) {
            mInstance = new Compresser();
        }
        return mInstance;
    }

    private ByteArrayOutputStream mByteArrayOutputStream;

    private int maxSize = 1024;//1M
    private int maxWidth = 1080;
    private int maxHeight = 1920;
    private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;


    public File compressImage(int gear, @NonNull File file, String dirPath) throws IOException {
        switch (gear) {
            case THIRD_GEAR:
                return thirdCompress(file, dirPath);
            case CUSTOM_GEAR:
                return customCompress(file, dirPath);
            case FIRST_GEAR:
                return firstCompress(file, dirPath);
            default:
                return file;
        }
    }

    private File thirdCompress(@NonNull File file, String dirPath) throws IOException {
        String thumb = getFilePath(dirPath, file.getName());

        double size;
        String filePath = file.getAbsolutePath();

        int angle = getImageSpinAngle(filePath);
        int width = getImageSize(filePath)[0];
        int height = getImageSize(filePath)[1];
        boolean flip = width > height;
        int thumbW = width % 2 == 1 ? width + 1 : width;
        int thumbH = height % 2 == 1 ? height + 1 : height;

        width = thumbW > thumbH ? thumbH : thumbW;
        height = thumbW > thumbH ? thumbW : thumbH;

        double scale = ((double) width / height);

        if (scale <= 1 && scale > 0.5625) {
            if (height < 1664) {
                if (file.length() / 1024 < 150) {
                    return file;
                }

                size = (width * height) / Math.pow(1664, 2) * 150;
                size = size < 60 ? 60 : size;
            } else if (height >= 1664 && height < 4990) {
                thumbW = width / 2;
                thumbH = height / 2;
                size = (thumbW * thumbH) / Math.pow(2495, 2) * 300;
                size = size < 60 ? 60 : size;
            } else if (height >= 4990 && height < 10240) {
                thumbW = width / 4;
                thumbH = height / 4;
                size = (thumbW * thumbH) / Math.pow(2560, 2) * 300;
                size = size < 100 ? 100 : size;
            } else {
                int multiple = height / 1280 == 0 ? 1 : height / 1280;
                thumbW = width / multiple;
                thumbH = height / multiple;
                size = (thumbW * thumbH) / Math.pow(2560, 2) * 300;
                size = size < 100 ? 100 : size;
            }
        } else if (scale <= 0.5625 && scale > 0.5) {
            if (height < 1280 && file.length() / 1024 < 200) {
                return file;
            }

            int multiple = height / 1280 == 0 ? 1 : height / 1280;
            thumbW = width / multiple;
            thumbH = height / multiple;
            size = (thumbW * thumbH) / (1440.0 * 2560.0) * 400;
            size = size < 100 ? 100 : size;
        } else {
            int multiple = (int) Math.ceil(height / (1280.0 / scale));
            thumbW = width / multiple;
            thumbH = height / multiple;
            size = ((thumbW * thumbH) / (1280.0 * (1280 / scale))) * 500;
            size = size < 100 ? 100 : size;
        }

        return compress(file, scale, filePath, thumb, flip ? thumbH : thumbW, flip ? thumbW : thumbH, angle,
                (long) size);
    }

    private File firstCompress(@NonNull File file, String dirPath) throws IOException {
        int minSize = 60;
        int longSide = 720;
        int shortSide = 1280;

        String thumbFilePath = getFilePath(dirPath, file.getName());
        String filePath = file.getAbsolutePath();

        long size = 0;
        long maxSize = file.length() / 5;

        int angle = getImageSpinAngle(filePath);
        int[] imgSize = getImageSize(filePath);
        int width = 0, height = 0;
        double scale;
        if (imgSize[0] <= imgSize[1]) {
            scale = (double) imgSize[0] / (double) imgSize[1];
            if (scale <= 1.0 && scale > 0.5625) {
                width = imgSize[0] > shortSide ? shortSide : imgSize[0];
                height = width * imgSize[1] / imgSize[0];
                size = minSize;
            } else if (scale <= 0.5625) {
                height = imgSize[1] > longSide ? longSide : imgSize[1];
                width = height * imgSize[0] / imgSize[1];
                size = maxSize;
            }
        } else {
            scale = (double) imgSize[1] / (double) imgSize[0];
            if (scale <= 1.0 && scale > 0.5625) {
                height = imgSize[1] > shortSide ? shortSide : imgSize[1];
                width = height * imgSize[0] / imgSize[1];
                size = minSize;
            } else if (scale <= 0.5625) {
                width = imgSize[0] > longSide ? longSide : imgSize[0];
                height = width * imgSize[1] / imgSize[0];
                size = maxSize;
            }
        }

        return compress(file, scale, filePath, thumbFilePath, width, height, angle, size);
    }


    /**
     * 压缩图片
     */
    public File customCompress(@NonNull File file, String dirPath) throws IOException {
        String thumbFilePath = getFilePath(dirPath, file.getName());
        String filePath = file.getAbsolutePath();

        int angle = getImageSpinAngle(filePath);
        long fileSize = maxSize > 0 && maxSize < file.length() / 1024 ? maxSize
                : file.length() / 1024;

        int[] size = getImageSize(filePath);
        int width = size[0];
        int height = size[1];

        if (maxSize > 0 && maxSize < file.length() / 1024f) {
            // find a suitable size
            float scale = (float) Math.sqrt(file.length() / 1024f / maxSize);
            width = (int) (width / scale);
            height = (int) (height / scale);
        }

        // check the width&height
        if (maxWidth > 0) {
            width = Math.min(width, maxWidth);
        }
        if (maxHeight > 0) {
            height = Math.min(height, maxHeight);
        }
        float scale = Math.min((float) width / size[0], (float) height / size[1]);
        width = (int) (size[0] * scale);
        height = (int) (size[1] * scale);

        //        // 不压缩
        //        if (maxSize > file.length() / 1024f && scale == 1) {
        //            return file;
        //        }

        return compress(file, scale, filePath, thumbFilePath, width, height, angle, fileSize);
    }

    /**
     * 创建中间缓存文件
     * @param fileName
     * @return file
     */
    private String getFilePath(String dirPath, String fileName) {
        File dir = new File(dirPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        String hostName = fileName;
        String extName = "";
        int pos = fileName.lastIndexOf(".");
        if (pos != -1) {
            hostName = fileName.substring(0, pos);
            extName = fileName.substring(pos + 1);
        }

        File tempFile = new File(dirPath, fileName);
        int i = 1;
        while (tempFile.exists()) {
            if (extName == null || extName.length() == 0) {
                fileName = MessageFormat.format("{0}({1})", hostName, i++);
            } else {
                fileName = MessageFormat.format("{0}({1}).{2}", hostName, i++, extName);
            }
            tempFile = new File(dirPath, fileName);
        }

        return tempFile.getAbsolutePath();
    }

    /**
     * obtain the image rotation angle
     * @param path path of target image
     */
    private int getImageSpinAngle(String path) {
        int degree = 0;
        ExifInterface exifInterface = null;
        try {
            exifInterface = new ExifInterface(path);
        } catch (IOException e) {
            // 图片不支持获取角度
            return 0;
        }
        int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_NORMAL);
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                degree = 90;
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                degree = 180;
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                degree = 270;
                break;
        }
        return degree;
    }

    /**
     * obtain the image's width and height
     * @param imagePath the path of image
     */
    private static int[] getImageSize(String imagePath) {
        int[] res = new int[2];

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        options.inSampleSize = 1;
        BitmapFactory.decodeFile(imagePath, options);

        res[0] = options.outWidth;
        res[1] = options.outHeight;

        return res;
    }

    /**
     * 指定参数压缩图片
     * create the thumbnail with the true rotate angle
     * @param largeImagePath the big image path
     * @param thumbFilePath the thumbnail path
     * @param width width of thumbnail
     * @param height height of thumbnail
     * @param angle rotation angle of thumbnail
     * @param size the file size of image
     */
    private File compress(File file, double scale, String largeImagePath, String thumbFilePath, int width, int height,
                          int angle, long size) throws IOException {
        Bitmap thbBitmap;
        // 不压缩
        if (maxSize > file.length() / 1024f && scale == 1) {
            thbBitmap = BitmapFactory.decodeFile(largeImagePath);
        } else {
            thbBitmap = compress(largeImagePath, width, height);
        }
        thbBitmap = rotatingImage(angle, thbBitmap);
        return saveImage(thumbFilePath, thbBitmap, size);
    }

    /**
     * 旋转图片
     * rotate the image with specified angle
     * @param angle the angle will be rotating 旋转的角度
     * @param bitmap target image               目标图片
     */
    private static Bitmap rotatingImage(int angle, Bitmap bitmap) {
        //rotate image
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);

        //create a new image
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix,
                true);
    }

    /**
     * obtain the thumbnail that specify the size
     * @param imagePath the target image path
     * @param width the width of thumbnail
     * @param height the height of thumbnail
     * @return {@link Bitmap}
     */
    private Bitmap compress(String imagePath, int width, int height) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imagePath, options);

        int outH = options.outHeight;
        int outW = options.outWidth;
        int inSampleSize = 1;

        while (outH / inSampleSize > height || outW / inSampleSize > width) {
            inSampleSize *= 2;
        }

        options.inSampleSize = inSampleSize;
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFile(imagePath, options);
    }

    /**
     * 保存图片到指定路径
     * Save image with specified size
     * @param filePath the image file save path 储存路径
     * @param bitmap the image what be save   目标图片
     * @param size the file size of image   期望大小
     */
    @SuppressLint("RestrictedApi")
    private File saveImage(String filePath, Bitmap bitmap, long size) throws IOException {
        checkNotNull(bitmap, TAG + "bitmap cannot be null");

        File result = new File(filePath.substring(0, filePath.lastIndexOf("/")));

        if (!result.exists() && !result.mkdirs()) {
            return null;
        }

        if (mByteArrayOutputStream == null) {
            mByteArrayOutputStream = new ByteArrayOutputStream(
                    bitmap.getWidth() * bitmap.getHeight());
        } else {
            mByteArrayOutputStream.reset();
        }

        int options = 100;
        bitmap.compress(compressFormat, options, mByteArrayOutputStream);

        while (mByteArrayOutputStream.size() / 1024 > size && options > 60) {
            mByteArrayOutputStream.reset();
            options -= 6;
            bitmap.compress(compressFormat, options, mByteArrayOutputStream);
        }
        bitmap.recycle();

        FileOutputStream fos = new FileOutputStream(filePath);
        mByteArrayOutputStream.writeTo(fos);
        fos.close();

        return new File(filePath);
    }

}
