// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.packages;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.errorprone.annotations.CheckReturnValue;
import java.util.BitSet;
import java.util.List;
import javax.annotation.Nullable;

/**
 * AttributeContainer holds all attribute values of a rule. In addition, for each one, it records
 * whether it is 'explicit', that is, provided by the function call that instantiated the rule, as
 * opposed to the default value, or a value computed from other attributes.
 *
 * <p>This class provides the lowest-level access to attribute information. The public interface for
 * attribute access is the rule itself. For a higher level abstraction use {@link AttributeMap}
 * instead.
 */
abstract class AttributeContainer {

  /**
   * Returns true iff the value of the specified attribute is explicitly set in the BUILD file. In
   * addition, this method returns false if the rule has no attribute with the given index.
   */
  abstract boolean isAttributeValueExplicitlySpecified(int attrIndex);

  /**
   * Returns the value of the attribute with index attrIndex. Returns null if the attribute is not
   * set in the container.
   */
  @Nullable
  abstract Object getAttributeValue(int attrIndex);

  /**
   * Returns attribute values as tracked by this instance. The indices of attribute values in the
   * returned list are not guaranteed to be consistent with the other methods of this class. If this
   * is important, which is generally the case, avoid this method.
   */
  abstract List<Object> getRawAttributeValues();

  /**
   * Updates the value of the attribute.
   *
   * @param attrIndex the index of attribute whose value to update. Assumed to be valid index.
   * @param value the value to set
   * @param explicit was this explicitly set in the BUILD file.
   */
  abstract void setAttributeValue(int attrIndex, Object value, boolean explicit);

  /** Returns a frozen AttributeContainer with the same attributes, and a compact representation. */
  @CheckReturnValue
  abstract AttributeContainer freeze();

  /** Returns an AttributeContainer for holding attributes of the given rule class. */
  static AttributeContainer newMutableInstance(RuleClass ruleClass) {
    int attrCount = ruleClass.getAttributeCount();
    Preconditions.checkArgument(attrCount < 254);
    return new Mutable(attrCount);
  }

  /** An AttributeContainer to which attributes may be added. */
  static final class Mutable extends AttributeContainer {

    // Sparsely populated array of values, indexed by Attribute.index.
    final Object[] values;
    final BitSet explicitAttrs = new BitSet();

    @VisibleForTesting
    Mutable(int maxAttrCount) {
      values = new Object[maxAttrCount];
    }

    @Override
    public boolean isAttributeValueExplicitlySpecified(int attrIndex) {
      return (attrIndex >= 0) && explicitAttrs.get(attrIndex);
    }

    /**
     * Returns the value of the attribute with index attrIndex. Returns null if the attribute is not
     * yet been set.
     */
    @Override
    @Nullable
    Object getAttributeValue(int attrIndex) {
      return values[attrIndex];
    }

    /**
     * Updates the value of the attribute.
     *
     * @param attrIndex the index of the attribute whose value to update.
     * @param value the value to set
     * @param explicit was this explicitly set in the BUILD file.
     */
    @Override
    void setAttributeValue(int attrIndex, Object value, boolean explicit) {
      if (attrIndex < 0 || attrIndex >= values.length) {
        throw new IllegalArgumentException(
            "attribute with index " + attrIndex + " is not valid for rule");
      }
      if (!explicit && explicitAttrs.get(attrIndex)) {
        throw new IllegalArgumentException(
            "attribute with index " + attrIndex + " already explicitly set");
      }
      values[attrIndex] = value;
      if (explicit) {
        explicitAttrs.set(attrIndex);
      }
    }

    @Override
    public AttributeContainer freeze() {
      if (values.length < 126) {
        return new Small(values, explicitAttrs);
      } else {
        return new Large(values, explicitAttrs);
      }
    }

    @Override
    List<Object> getRawAttributeValues() {
      // Mutable copy since ImmutableList doesn't support null.
      return Lists.newArrayList(values);
    }
  }

  /** Frozen AttributeContainer based on compact array with indirect indexing. */
  private abstract static class Frozen extends AttributeContainer {

    @Override
    final void setAttributeValue(int attrIndex, Object value, boolean explicit) {
      throw new UnsupportedOperationException(
          "Readonly implementations do not support setAttributeValue");
    }

    @Override
    final AttributeContainer freeze() {
      return this;
    }
  }

  private static final byte[] EMPTY_STATE = {};
  private static final Object[] EMPTY_VALUES = {};

  /** Returns number of non-null values. */
  private static int nonNullCount(Object[] attrValues) {
    // Pre-allocate longer array.
    int numSet = 0;
    for (Object val : attrValues) {
      if (val != null) {
        numSet++;
      }
    }
    return numSet;
  }

  /** Returns index into state array for attrIndex, or -1 if not found */
  private static int getStateIndex(byte[] state, int start, int attrIndex, int mask) {
    // Binary search, treating values as unsigned bytes.
    int lo = start;
    int hi = state.length - 1;
    while (hi >= lo) {
      int mid = (lo + hi) / 2;
      int midAttrIndex = state[mid] & mask;
      if (midAttrIndex == attrIndex) {
        return mid;
      } else if (midAttrIndex < attrIndex) {
        lo = mid + 1;
      } else {
        hi = mid - 1;
      }
    }
    return -1;
  }

  /**
   * A frozen implementation of AttributeContainer which supports RuleClasses with up to 126
   * attributes.
   */
  @VisibleForTesting
  static final class Small extends Frozen {

    private final int maxAttrCount;

    // Conceptually an AttributeContainer is an unordered set of triples
    // (attribute, value, explicit).
    // - attribute is represented internally as its index attrIndex.
    //   cheaply convertible to name and Attribute classes via RuleClass.
    // - value is an opaque object, stored in the values array
    // - explicit is a boolean which tracks if the attribute was
    //   explicitly set in the BUILD file.

    // Attribute values stored in attrIndex order.
    private final Object[] values;

    // The 'value' and 'explicit' components are encoded in the same byte.
    // Since this class only supports ruleClass with < 126 attributes,
    // state[i] encodes the the 'value' index in the 7 lower bits and 'explicit' in the top bit.
    // This is the common case.
    private final byte[] state;

    // Useful Terminology for reading the code.
    //  - attrIndex: an integer associated with a legal attribute of the ruleClass.
    //    RuleClass owns the mapping between attribute, its name, and attributeIndex.
    //  - stateIndex: an index into the state[] array.
    //  - valueIndex: an index into the attributeValues array.

    /**
     * Creates a container for a rule of the given rule class. Assumes attrIndex < 126 always.
     *
     * @param attrValues values for all attributes, null values are considered unset.
     * @param explicitAttrs holds explicit bit for each attribute index
     */
    private Small(Object[] attrValues, BitSet explicitAttrs) {
      maxAttrCount = attrValues.length;
      int numSet = nonNullCount(attrValues);
      if (numSet == 0) {
        this.values = EMPTY_VALUES;
        this.state = EMPTY_STATE;
        return;
      }
      values = new Object[numSet];
      state = new byte[numSet];
      int index = 0;
      int attrIndex = -1;
      for (Object attrValue : attrValues) {
        attrIndex++;
        if (attrValue == null) {
          continue;
        }
        byte stateValue = (byte) (0x7f & attrIndex);
        if (explicitAttrs.get(attrIndex)) {
          stateValue = (byte) (stateValue | 0x80);
        }
        state[index] = stateValue;
        values[index] = attrValue;
        index += 1;
      }
    }

    /**
     * Returns true iff the value of the specified attribute is explicitly set in the BUILD file. In
     * addition, this method return false if the rule has no attribute with the given name.
     */
    @Override
    boolean isAttributeValueExplicitlySpecified(int attrIndex) {
      if (attrIndex < 0) {
        return false;
      }
      int stateIndex = getStateIndex(state, 0, attrIndex, 0x7f);
      return stateIndex >= 0 && (state[stateIndex] & 0x80) != 0;
    }

    @Nullable
    @Override
    Object getAttributeValue(int attrIndex) {
      Preconditions.checkArgument(attrIndex >= 0);
      if (attrIndex >= maxAttrCount) {
        throw new IndexOutOfBoundsException(
            "Maximum valid attrIndex is " + (maxAttrCount - 1) + ". Given " + attrIndex);
      }
      int stateIndex = getStateIndex(state, 0, attrIndex, 0x7f);
      return stateIndex < 0 ? null : values[stateIndex];
    }

    @Override
    List<Object> getRawAttributeValues() {
      // Mutable copy since ImmutableList doesn't support null.
      return Lists.newArrayList(values);
    }
  }

  /**
   * A frozen implementation of AttributeContainer which supports RuleClasses with up to 254
   * attributes.
   */
  @VisibleForTesting
  static final class Large extends Frozen {

    private final int maxAttrCount;

    // Conceptually an AttributeContainer is an unordered set of triples
    // (attribute, value, explicit).
    // - attribute is represented internally as its index attrIndex.
    //   cheaply convertible to name and Attribute classes via RuleClass.
    // - value is an opaque object, stored in the values array
    // - explicit is a boolean which tracks if the attribute was
    //   explicitly set in the BUILD file.

    // Attribute values stored in attrIndex order.
    private final Object[] values;

    // P = ceil(ruleClass.attributeCount()/8)
    // The first P bytes store the explicit bits, while the remaining bytes store attrIndex.
    //
    // NOTE: We could potentially shave off a few bytes by using P=ceil(values.length/8)
    // But
    // - it leads to higher CPU cost
    // - actual memory savings may not be much since memory is allocated in blocks of 8 bytes and
    //   the savings is at most 8 bytes.
    // - this implementation is used only if ruleClass supports > 126 attributes (very rare).
    private final byte[] state;

    // Useful Terminology for reading the code.
    //  - attrIndex: an integer associated with a legal attribute of the ruleClass.
    //    RuleClass owns the mapping between attribute, its name, and attributeIndex.
    //  - stateIndex: an index into the state[] array.
    //  - valueIndex: an index into the attributeValues array.

    private static int prefixSize(int attrCount) {
      // ceil(max attributes / 8)
      return (attrCount + 7) >> 3;
    }

    /** Set the specified bit in the byte array. Assumes bitIndex is a valid index. */
    private static void setBit(byte[] bits, int bitIndex) {
      int idx = (bitIndex + 1);
      int explicitByte = bits[idx >> 3];
      byte mask = (byte) (1 << (idx & 0x07));
      bits[idx >> 3] = (byte) (explicitByte | mask);
    }

    /** Get the specified bit in the byte array. Assumes bitIndex is a valid index. */
    private static boolean getBit(byte[] bits, int bitIndex) {
      int idx = (bitIndex + 1);
      int explicitByte = bits[idx >> 3];
      int mask = (byte) (1 << (idx & 0x07));
      return (explicitByte & mask) != 0;
    }

    /**
     * Creates a container for a rule of the given rule class. Assumes maxAttrCount < 254
     *
     * @param attrValues values for all attributes, null values are considered unset.
     * @param explicitAttrs holds explicit bit for each attribute index
     */
    private Large(Object[] attrValues, BitSet explicitAttrs) {
      this.maxAttrCount = attrValues.length;
      int numSet = nonNullCount(attrValues);
      if (numSet == 0) {
        this.values = EMPTY_VALUES;
        this.state = EMPTY_STATE;
        return;
      }
      int p = prefixSize(maxAttrCount);
      values = new Object[numSet];
      state = new byte[p + numSet];
      int index = 0;
      int attrIndex = -1;
      for (Object attrValue : attrValues) {
        attrIndex++;
        if (attrValue == null) {
          continue;
        }
        if (explicitAttrs.get(attrIndex)) {
          setBit(state, attrIndex);
        }
        state[index + p] = (byte) attrIndex;
        values[index] = attrValue;
        index += 1;
      }
    }

    /**
     * Returns true iff the value of the specified attribute is explicitly set in the BUILD file. In
     * addition, this method return false if the rule has no attribute with the given name.
     */
    @Override
    boolean isAttributeValueExplicitlySpecified(int attrIndex) {
      return (attrIndex >= 0) && getBit(state, attrIndex);
    }

    @Nullable
    @Override
    Object getAttributeValue(int attrIndex) {
      Preconditions.checkArgument(attrIndex >= 0);
      if (state.length == 0) {
        return null;
      }
      if (attrIndex >= maxAttrCount) {
        throw new IndexOutOfBoundsException(
            "Maximum valid attrIndex is " + (maxAttrCount - 1) + ". Given " + attrIndex);
      }
      int p = prefixSize(maxAttrCount);
      int stateIndex = getStateIndex(state, p, attrIndex, 0xff);
      return stateIndex < 0 ? null : values[stateIndex - p];
    }

    @Override
    List<Object> getRawAttributeValues() {
      // Mutable copy since ImmutableList doesn't support null.
      return Lists.newArrayList(values);
    }
  }
}
