content: /*
   * Copyright (C) 2021 The Android Open Source Project
   *
   * 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 androidx.constraintlayout.core.motion;
  
  import androidx.constraintlayout.core.motion.utils.TypedValues;
  
  /**
   * Defines non standard Attributes
   */
  public class CustomVariable {
      private static final String TAG = "TransitionLayout";
      String mName;
      private int mType;
      private int mIntegerValue = Integer.MIN_VALUE;
      private float mFloatValue = Float.NaN;
      private String mStringValue = null;
      boolean mBooleanValue;
  
      // @TODO: add description
      public CustomVariable copy() {
          return new CustomVariable(this);
      }
  
      public CustomVariable(CustomVariable c) {
          mName = c.mName;
          mType = c.mType;
          mIntegerValue = c.mIntegerValue;
          mFloatValue = c.mFloatValue;
          mStringValue = c.mStringValue;
          mBooleanValue = c.mBooleanValue;
      }
  
      public CustomVariable(String name, int type, String value) {
          mName = name;
          mType = type;
          mStringValue = value;
      }
  
      public CustomVariable(String name, int type, int value) {
          mName = name;
          mType = type;
          if (type == TypedValues.Custom.TYPE_FLOAT) { // catch int ment for float
              mFloatValue = value;
          } else {
              mIntegerValue = value;
          }
      }
  
      public CustomVariable(String name, int type, float value) {
          mName = name;
          mType = type;
          mFloatValue = value;
      }
  
      public CustomVariable(String name, int type, boolean value) {
          mName = name;
          mType = type;
          mBooleanValue = value;
      }
  
      // @TODO: add description
      public static String colorString(int v) {
          String str = "00000000" + Integer.toHexString(v);
          return "#" + str.substring(str.length() - 8);
      }
  
      @Override
      public String toString() {
          String str = mName + ':';
          switch (mType) {
              case TypedValues.Custom.TYPE_INT:
                  return str + mIntegerValue;
              case TypedValues.Custom.TYPE_FLOAT:
                  return str + mFloatValue;
              case TypedValues.Custom.TYPE_COLOR:
                  return str + colorString(mIntegerValue);
              case TypedValues.Custom.TYPE_STRING:
                  return str + mStringValue;
              case TypedValues.Custom.TYPE_BOOLEAN:
                  return str + (Boolean) mBooleanValue;
              case TypedValues.Custom.TYPE_DIMENSION:
                  return str + mFloatValue;
          }
          return str + "????";
      }
  
      public int getType() {
          return mType;
      }
  
      public boolean getBooleanValue() {
          return mBooleanValue;
      }
  
      public float getFloatValue() {
          return mFloatValue;
      }
  
      public int getColorValue() {
          return mIntegerValue;
      }
  
      public int getIntegerValue() {
          return mIntegerValue;
      }
  
      public String getStringValue() {
          return mStringValue;
      }
  
      /**
       * Continuous types are interpolated they are fired only at
       */
      public boolean isContinuous() {
          switch (mType) {
              case TypedValues.Custom.TYPE_REFERENCE:
              case TypedValues.Custom.TYPE_BOOLEAN:
              case TypedValues.Custom.TYPE_STRING:
                  return false;
              default:
                  return true;
          }
      }
  
      public void setFloatValue(float value) {
          mFloatValue = value;
      }
  
      public void setBooleanValue(boolean value) {
          mBooleanValue = value;
      }
  
      public void setIntValue(int value) {
          mIntegerValue = value;
      }
  
      public void setStringValue(String value) {
          mStringValue = value;
      }
  
      /**
       * The number of interpolation values that need to be interpolated
       * Typically 1 but 3 for colors.
       *
       * @return Typically 1 but 3 for colors.
       */
      public int numberOfInterpolatedValues() {
          switch (mType) {
              case TypedValues.Custom.TYPE_COLOR:
                  return 4;
              default:
                  return 1;
          }
      }
  
      /**
       * Transforms value to a float for the purpose of interpolation
       *
       * @return interpolation value
       */
      public float getValueToInterpolate() {
          switch (mType) {
              case TypedValues.Custom.TYPE_INT:
                  return mIntegerValue;
              case TypedValues.Custom.TYPE_FLOAT:
                  return mFloatValue;
              case TypedValues.Custom.TYPE_COLOR:
                  throw new RuntimeException("Color does not have a single color to interpolate");
              case TypedValues.Custom.TYPE_STRING:
                  throw new RuntimeException("Cannot interpolate String");
              case TypedValues.Custom.TYPE_BOOLEAN:
                  return mBooleanValue ? 1 : 0;
              case TypedValues.Custom.TYPE_DIMENSION:
                  return mFloatValue;
          }
          return Float.NaN;
      }
  
      // @TODO: add description
      public void getValuesToInterpolate(float[] ret) {
          switch (mType) {
              case TypedValues.Custom.TYPE_INT:
                  ret[0] = mIntegerValue;
                  break;
              case TypedValues.Custom.TYPE_FLOAT:
                  ret[0] = mFloatValue;
                  break;
              case TypedValues.Custom.TYPE_COLOR:
                  int a = 0xFF & (mIntegerValue >> 24);
                  int r = 0xFF & (mIntegerValue >> 16);
                  int g = 0xFF & (mIntegerValue >> 8);
                  int b = 0xFF & mIntegerValue;
                  float f_r = (float) Math.pow(r / 255.0f, 2.2);
                  float f_g = (float) Math.pow(g / 255.0f, 2.2);
                  float f_b = (float) Math.pow(b / 255.0f, 2.2);
                  ret[0] = f_r;
                  ret[1] = f_g;
                  ret[2] = f_b;
                  ret[3] = a / 255f;
                  break;
              case TypedValues.Custom.TYPE_STRING:
                  throw new RuntimeException("Cannot interpolate String");
              case TypedValues.Custom.TYPE_BOOLEAN:
                  ret[0] = mBooleanValue ? 1 : 0;
                  break;
              case TypedValues.Custom.TYPE_DIMENSION:
                  ret[0] = mFloatValue;
                  break;
          }
      }
  
      // @TODO: add description
      public void setValue(float[] value) {
          switch (mType) {
              case TypedValues.Custom.TYPE_REFERENCE:
              case TypedValues.Custom.TYPE_INT:
                  mIntegerValue = (int) value[0];
                  break;
              case TypedValues.Custom.TYPE_FLOAT:
              case TypedValues.Custom.TYPE_DIMENSION:
                  mFloatValue = value[0];
                  break;
              case TypedValues.Custom.TYPE_COLOR:
                  float f_r = value[0];
                  float f_g = value[1];
                  float f_b = value[2];
                  int r = 0xFF & Math.round((float) Math.pow(f_r, 1.0 / 2.0) * 255.0f);
                  int g = 0xFF & Math.round((float) Math.pow(f_g, 1.0 / 2.0) * 255.0f);
                  int b = 0xFF & Math.round((float) Math.pow(f_b, 1.0 / 2.0) * 255.0f);
                  int a = 0xFF & Math.round(value[3] * 255.0f);
                  mIntegerValue = a << 24 | r << 16 | g << 8 | b;
                  break;
              case TypedValues.Custom.TYPE_STRING:
                  throw new RuntimeException("Cannot interpolate String");
              case TypedValues.Custom.TYPE_BOOLEAN:
                  mBooleanValue = value[0] > 0.5;
                  break;
          }
      }
  
      // @TODO: add description
      public static int hsvToRgb(float hue, float saturation, float value) {
          int h = (int) (hue * 6);
          float f = hue * 6 - h;
          int p = (int) (0.5f + 255 * value * (1 - saturation));
          int q = (int) (0.5f + 255 * value * (1 - f * saturation));
          int t = (int) (0.5f + 255 * value * (1 - (1 - f) * saturation));
          int v = (int) (0.5f + 255 * value);
          switch (h) {
              case 0:
                  return 0XFF000000 | (v << 16) + (t << 8) + p;
              case 1:
                  return 0XFF000000 | (q << 16) + (v << 8) + p;
              case 2:
                  return 0XFF000000 | (p << 16) + (v << 8) + t;
              case 3:
                  return 0XFF000000 | (p << 16) + (q << 8) + v;
              case 4:
                  return 0XFF000000 | (t << 16) + (p << 8) + v;
              case 5:
                  return 0XFF000000 | (v << 16) + (p << 8) + q;
  
          }
          return 0;
      }
  
      /**
       * test if the two attributes are different
       */
      public boolean diff(CustomVariable customAttribute) {
          if (customAttribute == null || mType != customAttribute.mType) {
              return false;
          }
          switch (mType) {
              case TypedValues.Custom.TYPE_INT:
              case TypedValues.Custom.TYPE_REFERENCE:
                  return mIntegerValue == customAttribute.mIntegerValue;
              case TypedValues.Custom.TYPE_FLOAT:
                  return mFloatValue == customAttribute.mFloatValue;
              case TypedValues.Custom.TYPE_COLOR:
                  return mIntegerValue == customAttribute.mIntegerValue;
              case TypedValues.Custom.TYPE_STRING:
                  return mIntegerValue == customAttribute.mIntegerValue;
              case TypedValues.Custom.TYPE_BOOLEAN:
                  return mBooleanValue == customAttribute.mBooleanValue;
              case TypedValues.Custom.TYPE_DIMENSION:
                  return mFloatValue == customAttribute.mFloatValue;
          }
          return false;
      }
  
      public CustomVariable(String name, int attributeType) {
          mName = name;
          mType = attributeType;
      }
  
      public CustomVariable(String name, int attributeType, Object value) {
          mName = name;
          mType = attributeType;
          setValue(value);
      }
  
      public CustomVariable(CustomVariable source, Object value) {
          mName = source.mName;
          mType = source.mType;
          setValue(value);
  
      }
  
      // @TODO: add description
      public void setValue(Object value) {
          switch (mType) {
              case TypedValues.Custom.TYPE_REFERENCE:
              case TypedValues.Custom.TYPE_INT:
                  mIntegerValue = (Integer) value;
                  break;
              case TypedValues.Custom.TYPE_FLOAT:
                  mFloatValue = (Float) value;
                  break;
              case TypedValues.Custom.TYPE_COLOR:
                  mIntegerValue = (Integer) value;
                  break;
              case TypedValues.Custom.TYPE_STRING:
                  mStringValue = (String) value;
                  break;
              case TypedValues.Custom.TYPE_BOOLEAN:
                  mBooleanValue = (Boolean) value;
                  break;
              case TypedValues.Custom.TYPE_DIMENSION:
                  mFloatValue = (Float) value;
                  break;
          }
      }
  
      private static int clamp(int c) {
          int n = 255;
          c &= ~(c >> 31);
          c -= n;
          c &= (c >> 31);
          c += n;
          return c;
      }
  
      // @TODO: add description
      public int getInterpolatedColor(float[] value) {
          int r = clamp((int) ((float) Math.pow(value[0], 1.0 / 2.2) * 255.0f));
          int g = clamp((int) ((float) Math.pow(value[1], 1.0 / 2.2) * 255.0f));
          int b = clamp((int) ((float) Math.pow(value[2], 1.0 / 2.2) * 255.0f));
          int a = clamp((int) (value[3] * 255.0f));
          int color = (a << 24) | (r << 16) | (g << 8) | b;
          return color;
      }
  
      // @TODO: add description
      public void setInterpolatedValue(MotionWidget view, float[] value) {
  
          switch (mType) {
              case TypedValues.Custom.TYPE_INT:
                  view.setCustomAttribute(mName, mType, (int) value[0]);
                  break;
              case TypedValues.Custom.TYPE_COLOR:
                  int r = clamp((int) ((float) Math.pow(value[0], 1.0 / 2.2) * 255.0f));
                  int g = clamp((int) ((float) Math.pow(value[1], 1.0 / 2.2) * 255.0f));
                  int b = clamp((int) ((float) Math.pow(value[2], 1.0 / 2.2) * 255.0f));
                  int a = clamp((int) (value[3] * 255.0f));
                  int color = (a << 24) | (r << 16) | (g << 8) | b;
                  view.setCustomAttribute(mName, mType, color);
                  break;
              case TypedValues.Custom.TYPE_REFERENCE:
              case TypedValues.Custom.TYPE_STRING:
                  throw new RuntimeException("unable to interpolate " + mName);
              case TypedValues.Custom.TYPE_BOOLEAN:
                  view.setCustomAttribute(mName, mType, value[0] > 0.5f);
                  break;
              case TypedValues.Custom.TYPE_DIMENSION:
              case TypedValues.Custom.TYPE_FLOAT:
                  view.setCustomAttribute(mName, mType, value[0]);
                  break;
          }
      }
  
      // @TODO: add description
      public static int rgbaTocColor(float r, float g, float b, float a) {
          int ir = clamp((int) (r * 255f));
          int ig = clamp((int) (g * 255f));
          int ib = clamp((int) (b * 255f));
          int ia = clamp((int) (a * 255f));
          int color = (ia << 24) | (ir << 16) | (ig << 8) | ib;
          return color;
      }
  
      // @TODO: add description
      public void applyToWidget(MotionWidget view) {
          switch (mType) {
              case TypedValues.Custom.TYPE_INT:
              case TypedValues.Custom.TYPE_COLOR:
              case TypedValues.Custom.TYPE_REFERENCE:
                  view.setCustomAttribute(mName, mType, mIntegerValue);
                  break;
              case TypedValues.Custom.TYPE_STRING:
                  view.setCustomAttribute(mName, mType, mStringValue);
                  break;
              case TypedValues.Custom.TYPE_BOOLEAN:
                  view.setCustomAttribute(mName, mType, mBooleanValue);
                  break;
              case TypedValues.Custom.TYPE_DIMENSION:
              case TypedValues.Custom.TYPE_FLOAT:
                  view.setCustomAttribute(mName, mType, mFloatValue);
                  break;
          }
      }
  
      public String getName() {
          return mName;
      }
  }
  