package com.hjq.toast;

import android.app.AppOpsManager;
import android.app.Application;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import com.hjq.toast.style.ToastBlackStyle;
import com.hjq.toast.style.ToastQQStyle;
import com.hjq.toast.style.ToastWhiteStyle;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 *    author : Android 轮子哥
 *    github : https://github.com/getActivity/ToastUtils
 *    time   : 2018/09/01
 *    desc   : Toast 工具类
 */
public final class ToastUtils {

    private static IToastInterceptor sInterceptor;

    private static IToastStrategy sStrategy;

    private static Toast sToast;

    /**
     * 不允许外部实例化
     */
    private ToastUtils() {}

    /**
     * 初始化 ToastUtils,在 Application 中初始化
     *
     * @param application       应用的上下文
     */
    public static void init(Application application) {
        init(application, new ToastBlackStyle(application));
    }

    /**
     * 初始化 ToastUtils 及样式
     */
    public static void init(Application application, IToastStyle style) {
        checkNullPointer(application);
        // 初始化 Toast 拦截器
        if (sInterceptor == null) {
            setToastInterceptor(new ToastInterceptor());
        }

        // 初始化 Toast 显示处理器
        if (sStrategy == null) {
            setToastStrategy(new ToastStrategy());
        }

        // 初始化吐司
        if (areNotificationsEnabled(application)) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + 4) {
                // 适配 Android 11 无法使用自定义 Toast 的问题
                // 官方文档:https://developer.android.google.cn/preview/features/toasts
                setToast(new SupportToast(application));
            } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
                // 解决 Android 7.1 上主线程被阻塞后吐司会报错的问题
                setToast(new SafeToast(application));
            } else {
                setToast(new BaseToast(application));
            }
        } else {
            // 解决关闭通知栏权限后 Toast 不显示的问题
            setToast(new SupportToast(application));
        }

        // 设置 Toast 视图
        setView(createTextView(application, style));

        // 设置 Toast 重心
        setGravity(style.getGravity(), style.getXOffset(), style.getYOffset());
    }

    /**
     * 显示一个对象的吐司
     *
     * @param object      对象
     */
    public static void show(Object object) {
        show(object != null ? object.toString() : "null");
    }

    /**
     * 显示一个吐司
     *
     * @param id      如果传入的是正确的 string id 就显示对应字符串
     *                如果不是则显示一个整数的string
     */
    public static void show(int id) {
        checkToastState();

        try {
            // 如果这是一个资源 id
            show(getContext().getResources().getText(id));
        } catch (Resources.NotFoundException ignored) {
            // 如果这是一个 int 整数
            show(String.valueOf(id));
        }
    }

    /**
     * 显示一个吐司
     *
     * @param id            资源 id
     * @param args          参数集
     */
    public static void show(int id, Object... args) {
        show(getContext().getResources().getString(id), args);
    }

    /**
     * 显示一个吐司
     *
     * @param format        原字符串
     * @param args          参数集
     */
    public static void show(String format, Object... args) {
        show(String.format(format, args));
    }

    /**
     * 显示一个吐司
     *
     * @param text      需要显示的文本
     */
    public static synchronized void show(CharSequence text) {
        checkToastState();

        if (sInterceptor.intercept(sToast, text)) {
            return;
        }

        sStrategy.show(text);
    }

    /**
     * 取消吐司的显示
     */
    public static synchronized void cancel() {
        checkToastState();

        sStrategy.cancel();
    }

    /**
     * 设置吐司的位置
     *
     * @param gravity           重心
     * @param xOffset           x轴偏移
     * @param yOffset           y轴偏移
     */
    public static void setGravity(int gravity, int xOffset, int yOffset) {
        checkToastState();

        // 适配 Android 4.2 新特性,布局反方向(开发者选项 - 强制使用从右到左的布局方向)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            gravity = Gravity.getAbsoluteGravity(gravity, sToast.getView().getResources().getConfiguration().getLayoutDirection());
        }

        sToast.setGravity(gravity, xOffset, yOffset);
    }

    /**
     * 给当前Toast设置新的布局,具体实现可看{@link BaseToast#setView(View)}
     */
    public static void setView(int id) {
        checkToastState();

        setView(View.inflate(getContext(), id, null));
    }
    public static void setView(View view) {
        checkToastState();

         // 这个 View 不能为空
        checkNullPointer(view);

        // 当前必须用 Application 的上下文创建的 View,否则可能会导致内存泄露
        Context context = view.getContext();
        if (!(context instanceof Application)) {
            throw new IllegalArgumentException("The view must be initialized using the context of the application");
        }

        // 如果吐司已经创建,就重新初始化吐司
        if (sToast != null) {
            // 取消原有吐司的显示
            sToast.cancel();
            sToast.setView(view);
        }
    }

    /**
     * 获取当前 Toast 的视图
     */
    @SuppressWarnings("unchecked")
    public static <V extends View> V getView() {
        checkToastState();

        return (V) sToast.getView();
    }

    /**
     * 初始化全局的Toast样式
     *
     * @param style         样式实现类,框架已经实现三种不同的样式
     *                      黑色样式:{@link ToastBlackStyle}
     *                      白色样式:{@link ToastWhiteStyle}
     *                      仿QQ样式:{@link ToastQQStyle}
     */
    public static void initStyle(IToastStyle style) {
        checkNullPointer(style);
        // 如果吐司已经创建,就重新初始化吐司
        if (sToast != null) {
            // 取消原有吐司的显示
            sToast.cancel();
            sToast.setView(createTextView(getContext(), style));
            sToast.setGravity(style.getGravity(), style.getXOffset(), style.getYOffset());
        }
    }

    /**
     * 设置当前Toast对象
     */
    public static void setToast(Toast toast) {
        checkNullPointer(toast);
        if (sToast != null && toast.getView() == null) {
            // 移花接木
            toast.setView(sToast.getView());
            toast.setGravity(sToast.getGravity(), sToast.getXOffset(), sToast.getYOffset());
            toast.setMargin(sToast.getHorizontalMargin(), sToast.getVerticalMargin());
        }
        sToast = toast;
        if (sStrategy != null) {
            sStrategy.bind(sToast);
        }
    }

    /**
     * 设置 Toast 显示策略
     */
    public static void setToastStrategy(IToastStrategy handler) {
        checkNullPointer(handler);
        sStrategy = handler;
        if (sToast != null) {
            sStrategy.bind(sToast);
        }
    }

    /**
     * 设置 Toast 拦截器(可以根据显示的内容决定是否拦截这个Toast)
     * 场景:打印 Toast 内容日志、根据 Toast 内容是否包含敏感字来动态切换其他方式显示(这里可以使用我的另外一套框架 XToast)
     */
    public static void setToastInterceptor(IToastInterceptor interceptor) {
        checkNullPointer(interceptor);
        sInterceptor = interceptor;
    }

    /**
     * 获取当前Toast对象
     */
    public static Toast getToast() {
        return sToast;
    }

    /**
     * 检查吐司状态,如果未初始化请先调用{@link ToastUtils#init(Application)}
     */
    private static void checkToastState() {
        // 吐司工具类还没有被初始化,必须要先调用init方法进行初始化
        if (sToast == null) {
            throw new IllegalStateException("ToastUtils has not been initialized");
        }
    }

    /**
     * 检查对象是否为空
     */
    private static void checkNullPointer(Object object) {
        if (object == null) {
            throw new NullPointerException("are you ok?");
        }
    }

    /**
     * 生成默认的 TextView 对象
     */
    private static TextView createTextView(Context context, IToastStyle style) {

        GradientDrawable drawable = new GradientDrawable();
        // 设置背景色
        drawable.setColor(style.getBackgroundColor());
        // 设置圆角大小
        drawable.setCornerRadius(style.getCornerRadius());

        TextView textView = new TextView(context);
        textView.setId(android.R.id.message);
        textView.setTextColor(style.getTextColor());
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, style.getTextSize());

        // 适配布局反方向
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            textView.setPaddingRelative(style.getPaddingStart(), style.getPaddingTop(), style.getPaddingEnd(), style.getPaddingBottom());
        } else {
            textView.setPadding(style.getPaddingStart(), style.getPaddingTop(), style.getPaddingEnd(), style.getPaddingBottom());
        }

        textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));

        // setBackground API 版本兼容
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            textView.setBackground(drawable);
        } else {
            textView.setBackgroundDrawable(drawable);
        }

        // 设置 Z 轴阴影
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            textView.setZ(style.getZ());
        }

        // 设置最大显示行数
        if (style.getMaxLines() > 0) {
            textView.setMaxLines(style.getMaxLines());
        }

        return textView;
    }

    /**
     * 获取上下文对象
     */
    private static Context getContext() {
        checkToastState();
        return sToast.getView().getContext();
    }

    /**
     * 检查通知栏权限有没有开启
     * 参考 SupportCompat 包中的方法: NotificationManagerCompat.from(context).areNotificationsEnabled();
     */
    private static boolean areNotificationsEnabled(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            return manager != null && manager.areNotificationsEnabled();
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            ApplicationInfo appInfo = context.getApplicationInfo();
            String packageName = context.getApplicationContext().getPackageName();
            int uid = appInfo.uid;

            try {
                Class<?> appOpsClass = Class.forName(AppOpsManager.class.getName());
                Method checkOpNoThrowMethod = appOpsClass.getMethod("checkOpNoThrow", Integer.TYPE, Integer.TYPE, String.class);
                Field opPostNotificationValue = appOpsClass.getDeclaredField("OP_POST_NOTIFICATION");
                int value = (Integer) opPostNotificationValue.get(Integer.class);
                return ((int) checkOpNoThrowMethod.invoke(appOps, value, uid, packageName) == AppOpsManager.MODE_ALLOWED);
            } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException
                    | InvocationTargetException | IllegalAccessException | RuntimeException ignored) {
                return true;
            }
        } else {
            return true;
        }
    }
}