/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.uimanager;

import com.facebook.yoga.YogaConstants;
import java.util.Arrays;

/**
 * Class representing CSS spacing (padding, margin, and borders). This is mostly necessary to
 * properly implement interactions and updates for properties like margin, marginLeft, and
 * marginHorizontal.
 */
public class Spacing {

  /** Spacing type that represents the left direction. E.g. {@code marginLeft}. */
  public static final int LEFT = 0;
  /** Spacing type that represents the top direction. E.g. {@code marginTop}. */
  public static final int TOP = 1;
  /** Spacing type that represents the right direction. E.g. {@code marginRight}. */
  public static final int RIGHT = 2;
  /** Spacing type that represents the bottom direction. E.g. {@code marginBottom}. */
  public static final int BOTTOM = 3;
  /**
   * Spacing type that represents start direction e.g. left in left-to-right, right in
   * right-to-left.
   */
  public static final int START = 4;
  /**
   * Spacing type that represents end direction e.g. right in left-to-right, left in right-to-left.
   */
  public static final int END = 5;
  /**
   * Spacing type that represents horizontal direction (left and right). E.g. {@code
   * marginHorizontal}.
   */
  public static final int HORIZONTAL = 6;
  /**
   * Spacing type that represents vertical direction (top and bottom). E.g. {@code marginVertical}.
   */
  public static final int VERTICAL = 7;
  /**
   * Spacing type that represents all directions (left, top, right, bottom). E.g. {@code margin}.
   */
  public static final int ALL = 8;

  private static final int[] sFlagsMap = {
    1, /*LEFT*/ 2, /*TOP*/ 4, /*RIGHT*/ 8, /*BOTTOM*/ 16, /*START*/ 32, /*END*/ 64, /*HORIZONTAL*/
    128, /*VERTICAL*/ 256, /*ALL*/
  };

  private final float[] mSpacing;
  private int mValueFlags = 0;
  private final float mDefaultValue;
  private boolean mHasAliasesSet;

  public Spacing() {
    this(0);
  }

  public Spacing(float defaultValue) {
    mDefaultValue = defaultValue;
    mSpacing = newFullSpacingArray();
  }

  public Spacing(Spacing original) {
    mDefaultValue = original.mDefaultValue;
    mSpacing = Arrays.copyOf(original.mSpacing, original.mSpacing.length);
    mValueFlags = original.mValueFlags;
    mHasAliasesSet = original.mHasAliasesSet;
  }

  /**
   * Set a spacing value.
   *
   * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}, {@link
   *     #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL}
   * @param value the value for this direction
   * @return {@code true} if the spacing has changed, or {@code false} if the same value was already
   *     set
   */
  public boolean set(int spacingType, float value) {
    if (!FloatUtil.floatsEqual(mSpacing[spacingType], value)) {
      mSpacing[spacingType] = value;

      if (YogaConstants.isUndefined(value)) {
        mValueFlags &= ~sFlagsMap[spacingType];
      } else {
        mValueFlags |= sFlagsMap[spacingType];
      }

      mHasAliasesSet =
          (mValueFlags & sFlagsMap[ALL]) != 0
              || (mValueFlags & sFlagsMap[VERTICAL]) != 0
              || (mValueFlags & sFlagsMap[HORIZONTAL]) != 0;

      return true;
    }

    return false;
  }

  /**
   * Get the spacing for a direction. This takes into account any default values that have been set.
   *
   * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}
   */
  public float get(int spacingType) {
    float defaultValue =
        (spacingType == START || spacingType == END ? YogaConstants.UNDEFINED : mDefaultValue);

    if (mValueFlags == 0) {
      return defaultValue;
    }

    if ((mValueFlags & sFlagsMap[spacingType]) != 0) {
      return mSpacing[spacingType];
    }

    if (mHasAliasesSet) {
      int secondType = spacingType == TOP || spacingType == BOTTOM ? VERTICAL : HORIZONTAL;
      if ((mValueFlags & sFlagsMap[secondType]) != 0) {
        return mSpacing[secondType];
      } else if ((mValueFlags & sFlagsMap[ALL]) != 0) {
        return mSpacing[ALL];
      }
    }

    return defaultValue;
  }

  /**
   * Get the raw value (that was set using {@link #set(int, float)}), without taking into account
   * any default values.
   *
   * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}, {@link
   *     #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL}
   */
  public float getRaw(int spacingType) {
    return mSpacing[spacingType];
  }

  /**
   * Resets the spacing instance to its default state. This method is meant to be used when
   * recycling {@link Spacing} instances.
   */
  public void reset() {
    Arrays.fill(mSpacing, YogaConstants.UNDEFINED);
    mHasAliasesSet = false;
    mValueFlags = 0;
  }

  /**
   * Try to get start value and fallback to given type if not defined. This is used privately by the
   * layout engine as a more efficient way to fetch direction-aware values by avoid extra method
   * invocations.
   */
  float getWithFallback(int spacingType, int fallbackType) {
    return (mValueFlags & sFlagsMap[spacingType]) != 0 ? mSpacing[spacingType] : get(fallbackType);
  }

  private static float[] newFullSpacingArray() {
    return new float[] {
      YogaConstants.UNDEFINED,
      YogaConstants.UNDEFINED,
      YogaConstants.UNDEFINED,
      YogaConstants.UNDEFINED,
      YogaConstants.UNDEFINED,
      YogaConstants.UNDEFINED,
      YogaConstants.UNDEFINED,
      YogaConstants.UNDEFINED,
      YogaConstants.UNDEFINED,
    };
  }
}
