package com.ryuunoakaihitomi.rebootmenu.util;

import android.util.Log;
import android.util.TimingLogger;

import com.ryuunoakaihitomi.rebootmenu.util.hook.ReflectionOnPie;

import java.lang.reflect.Field;
import java.util.ArrayList;

/**
 * 不受限制永远可输出的TimingLogger
 * Created by ZQY on 2018/10/29.
 * <p>
 * https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/util/TimingLogger.java
 */

public class TrueTimingLogger {

    private static final String TAG = "TrueTimingLogger";
    private Field mDisabledField, mSplitsField, mSplitLabelsField, mTagField, mLabelField;
    private final TimingLogger logger;

    /**
     * Create and initialize a TimingLogger object that will log using
     * the specific tag.
     *
     * @param tag   the log tag to use while logging the timings
     * @param label a string to be displayed with each log
     */
    public TrueTimingLogger(String tag, String label) {
        ReflectionOnPie.clearClassLoaderInClass(TrueTimingLogger.class);
        logger = new TimingLogger(tag, label);
        initFields();
        reset(tag, label);
    }

    /**
     * Clear and initialize a TimingLogger object that will log using
     * the specific tag.
     *
     * @param tag   the log tag to use while logging the timings
     * @param label a string to be displayed with each log
     */
    public void reset(String tag, String label) {
        setField(mTagField, tag);
        setField(mLabelField, label);
        reset();
    }

    /**
     * Clear and initialize a TimingLogger object that will log using
     * the tag and label that was specified previously, either via
     * the constructor or a call to reset(tag, label).
     */
    @SuppressWarnings("ConstantConditions")
    public void reset() {
        if (getField(mSplitsField) == null) {
            setField(mSplitsField, new ArrayList<Long>());
            setField(mSplitLabelsField, new ArrayList<String>());
        } else {
            try {
                ((ArrayList) getField(mSplitsField)).clear();
                ((ArrayList) getField(mSplitLabelsField)).clear();
            } catch (NullPointerException e) {
                Log.w(TAG, "reset: ", e);
            }
        }
        addSplit(null);
    }

    /**
     * Add a split for the current time, labeled with splitLabel.
     *
     * @param splitLabel a label to associate with this split.
     */
    public void addSplit(String splitLabel) {
        setField(mDisabledField, false);
        logger.addSplit(splitLabel);
    }

    /**
     * Dumps the timings to the log using Log.d().
     */
    public void dumpToLog() {
        setField(mDisabledField, false);
        logger.dumpToLog();
    }

    @SuppressWarnings("JavaReflectionMemberAccess")
    private void initFields() {
        try {
            Class tlClz = TimingLogger.class;
            mDisabledField = tlClz.getDeclaredField("mDisabled");
            mDisabledField.setAccessible(true);
            mSplitsField = tlClz.getDeclaredField("mSplits");
            mSplitsField.setAccessible(true);
            mSplitLabelsField = tlClz.getDeclaredField("mSplitLabels");
            mSplitLabelsField.setAccessible(true);
            mTagField = tlClz.getDeclaredField("mTag");
            mTagField.setAccessible(true);
            mLabelField = tlClz.getDeclaredField("mLabel");
            mLabelField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            Log.w(TAG, "initFields: ", e);
        }
    }

    private void setField(Field field, Object value) {
        if (field == null)
            return;
        try {
            field.set(logger, value);
        } catch (IllegalAccessException e) {
            Log.w(TAG, "setField: ", e);
        }
    }

    private Object getField(Field field) {
        try {
            return field.get(logger);
        } catch (IllegalAccessException | NullPointerException e) {
            Log.w(TAG, "getField: ", e);
        }
        return null;
    }
}