content: /*
   * Copyright (C) 2016 Bilibili
   *
   * 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.bilibili.magicasakura.utils;
  
  import android.app.Activity;
  import android.content.Context;
  import android.content.ContextWrapper;
  import android.content.res.ColorStateList;
  import android.content.res.Configuration;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.graphics.Color;
  import android.graphics.PorterDuff;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.DrawableContainer;
  import android.graphics.drawable.InsetDrawable;
  import android.graphics.drawable.LayerDrawable;
  import android.graphics.drawable.NinePatchDrawable;
  import android.graphics.drawable.StateListDrawable;
  import android.os.Build;
  import android.support.annotation.AttrRes;
  import android.support.annotation.ColorInt;
  import android.support.annotation.ColorRes;
  import android.support.annotation.DrawableRes;
  import android.support.annotation.StyleRes;
  import android.support.v4.graphics.ColorUtils;
  import android.support.v4.graphics.drawable.DrawableCompat;
  import android.support.v7.view.ContextThemeWrapper;
  import android.support.v7.widget.RecyclerView;
  import android.util.AttributeSet;
  import android.util.DisplayMetrics;
  import android.util.StateSet;
  import android.util.TypedValue;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.AbsListView;
  import android.widget.BaseAdapter;
  import android.widget.ListAdapter;
  import android.widget.WrapperListAdapter;
  
  import com.bilibili.magicasakura.widgets.Tintable;
  import com.wm.remusic.R;
  
  import java.lang.reflect.Field;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.LinkedList;
  
  /**
   * Created by xyczero on 15/9/6.
   * Email : xyczero@sina.com
   */
  public class ThemeUtils {
      private static final ThreadLocal<TypedValue> TL_TYPED_VALUE = new ThreadLocal<>();
  
      public static final int[] DISABLED_STATE_SET = new int[]{-android.R.attr.state_enabled};
      public static final int[] ENABLED_STATE_SET = new int[]{android.R.attr.state_enabled};
      public static final int[] FOCUSED_STATE_SET = new int[]{android.R.attr.state_focused};
      public static final int[] ACTIVATED_STATE_SET = new int[]{android.R.attr.state_activated};
      public static final int[] PRESSED_STATE_SET = new int[]{android.R.attr.state_pressed};
      public static final int[] CHECKED_STATE_SET = new int[]{android.R.attr.state_checked};
      public static final int[] SELECTED_STATE_SET = new int[]{android.R.attr.state_selected};
      public static final int[] EMPTY_STATE_SET = new int[0];
  
      private static final int[] TEMP_ARRAY = new int[1];
  
  
      public static Drawable tintDrawable(Drawable drawable, @ColorInt int color, PorterDuff.Mode mode) {
          if (drawable == null) return null;
          Drawable wrapper = DrawableCompat.wrap(drawable.mutate());
          DrawableCompat.setTint(wrapper, color);
          DrawableCompat.setTintMode(drawable, mode);
          return wrapper;
      }
  
      public static Drawable tintDrawable(Context context, @DrawableRes int resId, @ColorRes int colorId) {
          if (resId <= 0 || colorId <= 0) return null;
          Drawable drawable = context.getResources().getDrawable(resId);
          return tintDrawableByColorId(context, drawable, colorId);
      }
  
      public static Drawable tintDrawable(Drawable drawable, @ColorInt int color) {
          return tintDrawable(drawable, color, PorterDuff.Mode.SRC_IN);
      }
  
      public static Drawable tintDrawableByDrawableId(Context context, @DrawableRes int resId, @ColorInt int color) {
          if (resId <= 0) return null;
          Drawable drawable = context.getResources().getDrawable(resId);
          return tintDrawable(drawable, color);
      }
  
      public static Drawable tintDrawableByColorId(Context context, Drawable drawable, @ColorRes int colorId) {
          if (drawable == null) return null;
          if (colorId <= 0) return drawable;
          return tintDrawable(drawable, replaceColor(context, context.getResources().getColor(colorId)));
      }
  
      public static Drawable tintDrawable(Drawable drawable, ColorStateList cls, PorterDuff.Mode mode) {
          if (drawable == null) return null;
          Drawable wrapper = DrawableCompat.wrap(drawable.mutate());
          DrawableCompat.setTintList(wrapper, cls);
          DrawableCompat.setTintMode(drawable, mode);
          return wrapper;
      }
  
      public static Drawable tintDrawableByColorList(Context context, Drawable drawable, @ColorRes int colorListId, PorterDuff.Mode mode) {
          if (drawable == null) return null;
          if (colorListId <= 0) return drawable;
          return tintDrawable(drawable, TintManager.get(context).getColorStateList(colorListId), mode == null ? PorterDuff.Mode.SRC_IN : mode);
      }
  
      public static
      @ColorInt
      int getColorById(Context context, @ColorRes int colorId) {
          return replaceColorById(context, colorId);
      }
  
      public static
      @ColorInt
      int getColor(Context context, @ColorInt int color) {
          return replaceColor(context, color);
      }
  
      public static
      @ColorInt
      int getThemeAttrColor(Context context, @AttrRes int attr) {
          return hasThemeAttr(context, attr) ? replaceColorById(context, getThemeAttrId(context, attr)) : Color.TRANSPARENT;
      }
  
      static int getThemeAttrColor(Context context, @AttrRes int attr, float alpha) {
          final int color = getThemeAttrColor(context, attr);
          final int originalAlpha = Color.alpha(color);
  
          return ColorUtils.setAlphaComponent(color, Math.round(originalAlpha * alpha));
      }
  
      public static ColorStateList getThemeAttrColorStateList(Context context, @AttrRes int attr) {
          TEMP_ARRAY[0] = attr;
          TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
          try {
              return a.getColorStateList(0);
          } finally {
              a.recycle();
          }
      }
  
      public static int getDisabledThemeAttrColor(Context context, @AttrRes int attr) {
          final ColorStateList csl = getThemeAttrColorStateList(context, attr);
          if (csl != null && csl.isStateful()) {
              // If the CSL is stateful, we'll assume it has a disabled state and use it
              return csl.getColorForState(DISABLED_STATE_SET, csl.getDefaultColor());
          } else {
              // Else, we'll generate the color using disabledAlpha from the theme
  
              final TypedValue tv = getTypedValue();
              // Now retrieve the disabledAlpha value from the theme
              context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
              final float disabledAlpha = tv.getFloat();
  
              return getThemeAttrColor(context, attr, disabledAlpha);
          }
      }
  
      public static int getThemeAttrId(Context context, @AttrRes int attr) {
          TEMP_ARRAY[0] = attr;
          TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
          try {
              return a.getResourceId(0, 0);
          } finally {
              a.recycle();
          }
      }
  
      public static int getThemeAttrId(Context context, AttributeSet attrs, @AttrRes int attr) {
          TEMP_ARRAY[0] = attr;
          TypedArray a = context.obtainStyledAttributes(attrs, TEMP_ARRAY);
          try {
              return a.getResourceId(0, 0);
          } finally {
              a.recycle();
          }
      }
  
      public static boolean getThemeAttrBoolean(Context context, @AttrRes int attr) {
          TEMP_ARRAY[0] = attr;
          TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
          try {
              return a.getBoolean(0, false);
          } finally {
              a.recycle();
          }
      }
  
      public static boolean hasThemeAttr(Context context, @AttrRes int attr) {
          TEMP_ARRAY[0] = attr;
          TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
          try {
              return a.hasValue(0);
          } finally {
              a.recycle();
          }
      }
  
      public static int getThemeAttrDimensionPixelSize(Context context, @AttrRes int attr) {
          TEMP_ARRAY[0] = attr;
          TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
          try {
              return a.getDimensionPixelSize(0, 0);
          } finally {
              a.recycle();
          }
      }
  
      private static TypedValue getTypedValue() {
          TypedValue typedValue = TL_TYPED_VALUE.get();
          if (typedValue == null) {
              typedValue = new TypedValue();
              TL_TYPED_VALUE.set(typedValue);
          }
          return typedValue;
      }
  
      // skip animated-selector when android version is 5.0.x
      private static boolean isSkipAnimatedSelector = false;
      private static boolean hasRecordedVersion = false;
  
      public static boolean isSkipAnimatedSelector() {
          if (!hasRecordedVersion) {
              final String sdkVersion = Build.VERSION.RELEASE;
              isSkipAnimatedSelector = !Build.UNKNOWN.equals(sdkVersion) && "5.0".compareTo(sdkVersion) <= 0 && "5.1".compareTo(sdkVersion) > 0;
              hasRecordedVersion = true;
          }
          return isSkipAnimatedSelector;
      }
  
      public static boolean containsNinePatch(Drawable drawable) {
          drawable = getWrapperDrawable(drawable);
          if (drawable instanceof NinePatchDrawable
                  || drawable instanceof InsetDrawable
                  || drawable instanceof LayerDrawable) {
              return true;
          } else if (drawable instanceof StateListDrawable) {
              final DrawableContainer.DrawableContainerState containerState = ((DrawableContainer.DrawableContainerState) drawable.getConstantState());
              //can't get containState from drawable which is containing DrawableWrapperDonut
              //https://code.google.com/p/android/issues/detail?id=169920
              if (containerState == null) {
                  return true;
              }
              for (Drawable dr : containerState.getChildren()) {
                  dr = getWrapperDrawable(dr);
                  if (dr instanceof NinePatchDrawable
                          || dr instanceof InsetDrawable
                          || dr instanceof LayerDrawable) {
                      return true;
                  }
              }
          }
          return false;
      }
  
      public static Drawable getWrapperDrawable(Drawable drawable) {
          if (drawable instanceof android.support.v4.graphics.drawable.DrawableWrapper) {
              return ((android.support.v4.graphics.drawable.DrawableWrapper) drawable).getWrappedDrawable();
          } else if (drawable instanceof android.support.v7.graphics.drawable.DrawableWrapper) {
              return ((android.support.v7.graphics.drawable.DrawableWrapper) drawable).getWrappedDrawable();
          } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && drawable instanceof android.graphics.drawable.DrawableWrapper) {
              return ((android.graphics.drawable.DrawableWrapper) drawable).getDrawable();
          }
          return drawable;
      }
  
      public static Activity getWrapperActivity(Context context) {
          if (context instanceof Activity) {
              return (Activity) context;
          } else if (context instanceof ContextWrapper) {
              return getWrapperActivity(((ContextWrapper) context).getBaseContext());
          }
          return null;
      }
  
      public static ContextWrapper getWrapperContext(Context context, @StyleRes int themeId) {
          if (context == null) return null;
  
          return new ContextThemeWrapper(context, themeId);
      }
  
      public static Resources updateNightMode(Resources resource, boolean on) {
          DisplayMetrics dm = resource.getDisplayMetrics();
          Configuration config = resource.getConfiguration();
          final int uiModeNightMaskOrigin = config.uiMode &= ~Configuration.UI_MODE_TYPE_MASK;
          final int uiModeNightMaskNew = on ? Configuration.UI_MODE_NIGHT_YES : Configuration.UI_MODE_NIGHT_NO;
          if (uiModeNightMaskOrigin != uiModeNightMaskNew) {
              config.uiMode &= ~Configuration.UI_MODE_NIGHT_MASK;
              config.uiMode |= uiModeNightMaskNew;
              resource.updateConfiguration(config, dm);
          }
          return resource;
      }
  
      static TintInfo parseColorStateList(ColorStateList origin) {
          if (origin == null) return null;
  
          boolean hasDisable = false;
          int originDefaultColor = origin.getDefaultColor();
          LinkedList<int[]> stateList = new LinkedList<>();
          LinkedList<Integer> colorList = new LinkedList<>();
  
          int disableColor = origin.getColorForState(DISABLED_STATE_SET, 0);
          if (disableColor != originDefaultColor) {
              hasDisable = true;
              stateList.add(DISABLED_STATE_SET);
              colorList.add(disableColor);
          }
  
          int pressedColor = origin.getColorForState(wrapState(hasDisable, PRESSED_STATE_SET), 0);
          if (pressedColor != originDefaultColor) {
              stateList.add(PRESSED_STATE_SET);
              colorList.add(pressedColor);
          }
  
          int focusColor = origin.getColorForState(wrapState(hasDisable, FOCUSED_STATE_SET), 0);
          if (focusColor != originDefaultColor) {
              stateList.add(FOCUSED_STATE_SET);
              colorList.add(focusColor);
          }
  
          int checkedColor = origin.getColorForState(wrapState(hasDisable, CHECKED_STATE_SET), 0);
          if (checkedColor != originDefaultColor) {
              stateList.add(CHECKED_STATE_SET);
              colorList.add(checkedColor);
          }
  
          int selectedColor = origin.getColorForState(wrapState(hasDisable, SELECTED_STATE_SET), 0);
          if (selectedColor != originDefaultColor) {
              stateList.add(SELECTED_STATE_SET);
              colorList.add(selectedColor);
          }
  
          int normalColor = origin.getColorForState(wrapState(hasDisable, EMPTY_STATE_SET), 0);
          if (normalColor != 0) {
              stateList.add(EMPTY_STATE_SET);
              colorList.add(normalColor);
          }
  
          if (colorList.size() > 1) {
              return new TintInfo(stateList, colorList);
          } else {
              return null;
          }
      }
  
      private static int[] wrapState(boolean hasDisable, int[] targetState) {
          return targetState.length > 0
                  ? (hasDisable ? new int[]{ENABLED_STATE_SET[0], targetState[0]} : targetState)
                  : (hasDisable ? ENABLED_STATE_SET : targetState);
      }
  
      public static ColorStateList getThemeColorStateList(Context context, ColorStateList origin) {
          if (origin == null) return null;
  
          if (origin.isStateful()) {
              TintInfo tintInfo = parseColorStateList(origin);
              if (tintInfo == null || tintInfo.isInvalid()) {
                  return origin;
              }
  
              int[] newColors;
              int[][] newStates;
              int index = 0;
              boolean hasDisableColor = StateSet.stateSetMatches(tintInfo.mTintStates[0], DISABLED_STATE_SET);
              if (!hasDisableColor) {
                  newStates = new int[tintInfo.mTintStates.length + 1][];
                  newColors = new int[tintInfo.mTintStates.length + 1];
                  newStates[index] = DISABLED_STATE_SET;
                  newColors[index] = getDisabledThemeAttrColor(context, R.attr.themeColorSecondary);
                  index++;
              } else {
                  newStates = new int[tintInfo.mTintStates.length][];
                  newColors = new int[tintInfo.mTintStates.length];
              }
  
              for (int i = 0; i < tintInfo.mTintStates.length; i++) {
                  newStates[index] = tintInfo.mTintStates[i];
                  newColors[index] = replaceColor(context, tintInfo.mTintColors[i]);
                  index++;
              }
              return new ColorStateList(newStates, newColors);
          }
          return ColorStateList.valueOf(replaceColor(context, origin.getDefaultColor()));
      }
  
      public static ColorStateList getThemeColorStateList(Context context, @ColorRes int colorId) {
          return ColorStateListUtils.createColorStateList(context, colorId);
      }
  
      public static void refreshUI(Context context) {
          refreshUI(context, null);
      }
  
      public static void refreshUI(Context context, ExtraRefreshable extraRefreshable) {
          TintManager.clearTintCache();
          Activity activity = getWrapperActivity(context);
          if (activity != null) {
              if (extraRefreshable != null) {
                  extraRefreshable.refreshGlobal(activity);
              }
              View rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
              refreshView(rootView, extraRefreshable);
          }
      }
  
      private static Field mRecycler;
      private static Method mClearMethod;
  
      private static void refreshView(View view, ExtraRefreshable extraRefreshable) {
          if (view == null) return;
  
          view.destroyDrawingCache();
          if (view instanceof Tintable) {
              ((Tintable) view).tint();
              if (view instanceof ViewGroup) {
                  for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                      refreshView(((ViewGroup) view).getChildAt(i), extraRefreshable);
                  }
              }
          } else {
              if (extraRefreshable != null) {
                  extraRefreshable.refreshSpecificView(view);
              }
              if (view instanceof AbsListView) {
                  ListAdapter adapter = ((AbsListView) view).getAdapter();
                  while (adapter instanceof WrapperListAdapter) {
                      adapter = ((WrapperListAdapter) adapter).getWrappedAdapter();
                  }
                  if (adapter instanceof BaseAdapter) {
                      ((BaseAdapter) adapter).notifyDataSetChanged();
                  }
              }
              if (view instanceof RecyclerView) {
                  try {
                      if (mRecycler == null) {
                          mRecycler = RecyclerView.class.getDeclaredField("mRecycler");
                          mRecycler.setAccessible(true);
                      }
                      if (mClearMethod == null) {
                          mClearMethod = Class.forName("android.support.v7.widget.RecyclerView$Recycler")
                                  .getDeclaredMethod("clear");
                          mClearMethod.setAccessible(true);
                      }
                      mClearMethod.invoke(mRecycler.get(view));
                  } catch (NoSuchMethodException e) {
                      e.printStackTrace();
                  } catch (IllegalAccessException e) {
                      e.printStackTrace();
                  } catch (NoSuchFieldException e) {
                      e.printStackTrace();
                  } catch (InvocationTargetException e) {
                      e.printStackTrace();
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }
                  ((RecyclerView) view).getRecycledViewPool().clear();
                  ((RecyclerView) view).invalidateItemDecorations();
              }
              if (view instanceof ViewGroup) {
                  for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                      refreshView(((ViewGroup) view).getChildAt(i), extraRefreshable);
                  }
              }
          }
      }
  
      public interface ExtraRefreshable {
          void refreshGlobal(Activity activity);
  
          void refreshSpecificView(View view);
      }
  
      public static switchColor mSwitchColor;
  
      public static void setSwitchColor(switchColor switchColor) {
          mSwitchColor = switchColor;
      }
  
      static
      @ColorInt
      int replaceColorById(Context context, @ColorRes int colorId) {
          return mSwitchColor == null ? Color.TRANSPARENT : mSwitchColor.replaceColorById(context, colorId);
      }
  
      static
      @ColorInt
      int replaceColor(Context context, @ColorInt int color) {
          return mSwitchColor == null ? Color.TRANSPARENT : mSwitchColor.replaceColor(context, color);
      }
  
      public interface switchColor {
          @ColorInt
          int replaceColorById(Context context, @ColorRes int colorId);
  
          @ColorInt
          int replaceColor(Context context, @ColorInt int color);
      }
  }
  