content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.asterix.runtime.evaluators.functions.bitwise;
  
  import java.io.IOException;
  
  import org.apache.asterix.common.exceptions.ErrorCode;
  import org.apache.asterix.dataflow.data.nontagged.serde.ABooleanSerializerDeserializer;
  import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
  import org.apache.asterix.om.base.ABoolean;
  import org.apache.asterix.om.exceptions.ExceptionUtil;
  import org.apache.asterix.om.types.ATypeTag;
  import org.apache.asterix.om.types.BuiltinType;
  import org.apache.asterix.om.types.EnumDeserializer;
  import org.apache.asterix.om.types.hierachy.ATypeHierarchy;
  import org.apache.asterix.runtime.evaluators.common.ListAccessor;
  import org.apache.asterix.runtime.evaluators.functions.AbstractScalarEval;
  import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
  import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
  import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
  import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
  import org.apache.hyracks.api.context.IEvaluatorContext;
  import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
  import org.apache.hyracks.api.exceptions.HyracksDataException;
  import org.apache.hyracks.api.exceptions.IWarningCollector;
  import org.apache.hyracks.api.exceptions.SourceLocation;
  import org.apache.hyracks.api.exceptions.Warning;
  import org.apache.hyracks.data.std.api.IPointable;
  import org.apache.hyracks.data.std.primitive.VoidPointable;
  import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
  import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
  
  /**
   * This function receives two or three arguments, first argument representing the value, second argument representing
   * the position to apply the operation at, and third argument (optional) represents a boolean flag. The position can
   * be a single value or an array of values. This evaluator is used by the bitwise BITTEST and ISBITSET
   * functions.
   *
   * These functions can be applied only to int64 type numeric values.
   *
   * The function has the following behavior:
   * For the first argument:
   * - If the argument is missing or null, then missing or null is returned, respectively.
   * - If the argument is int64, then boolean is returned.
   * - If the argument is float or double, then boolean or null is returned, depending on the argument value.
   * - If the argument is any other type, then null is returned.
   *
   * For the second argument:
   * - If the argument is missing or null, then missing or null is returned, respectively.
   * - If the argument is int64, then boolean is returned.
   * - If the argument is float or double, then boolean or null is returned, depending on the argument value.
   * - If the argument is an array:
   *      - If the array item type is int64, then int64 or boolean is returned.
   *      - If the array item type is float or double, then boolean or null is returned, depending on the argument.
   *      - If the array item type  is any other type, then null is returned.
   * - If the argument is any other type, then null is returned.
   *
   * For the third argument:
   * - If the argument is missing or null, then missing or null is returned, respectively.
   * - If the argument is boolean, then boolean is returned.
   * - If the argument is any other type, then null is returned.
   */
  
  class BitValuePositionFlagEvaluator extends AbstractScalarEval {
  
      // Result members
      private ABoolean resultBoolean;
      private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
  
      // This flag has a special purpose. In some cases, when an array of positions is passed, checking
      // some arguments might be enough to return the final result, but, instead of stopping, we need to
      // continue looping because if any value in the positions array is invalid, we should return a null instead of
      // the boolean result, this flag will keep the loop going just for checking the values, while the
      // final result is already set previously.
      private boolean isStopUpdatingResultBoolean = false;
  
      // Evaluators and Pointables
      private final IScalarEvaluator valueEvaluator;
      private final IScalarEvaluator positionEvaluator;
      private IScalarEvaluator flagEvaluator;
      private final IPointable valuePointable = new VoidPointable();
      private final IPointable positionPointable = new VoidPointable();
      private IPointable flagPointable;
  
      // List accessor
      private final ListAccessor listAccessor = new ListAccessor();
      private final IPointable listItemPointable = new VoidPointable();
  
      // Serializer/Deserializer
      @SuppressWarnings("rawtypes")
      private final ISerializerDeserializer aBooleanSerde =
              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ABOOLEAN);
  
      private final IEvaluatorContext context;
      private static final byte[] secondArgumentExpectedTypes =
              new byte[] { ATypeTag.SERIALIZED_INT64_TYPE_TAG, ATypeTag.SERIALIZED_ORDEREDLIST_TYPE_TAG };
  
      BitValuePositionFlagEvaluator(IEvaluatorContext context, IScalarEvaluatorFactory[] argEvaluatorFactories,
              FunctionIdentifier functionIdentifier, SourceLocation sourceLocation) throws HyracksDataException {
          super(sourceLocation, functionIdentifier);
  
          this.context = context;
  
          // Evaluators
          valueEvaluator = argEvaluatorFactories[0].createScalarEvaluator(context);
          positionEvaluator = argEvaluatorFactories[1].createScalarEvaluator(context);
  
          if (argEvaluatorFactories.length > 2) {
              flagEvaluator = argEvaluatorFactories[2].createScalarEvaluator(context);
              flagPointable = new VoidPointable();
          }
      }
  
      @SuppressWarnings("unchecked")
      @Override
      public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
          valueEvaluator.evaluate(tuple, valuePointable);
          positionEvaluator.evaluate(tuple, positionPointable);
  
          if (flagEvaluator != null) {
              flagEvaluator.evaluate(tuple, flagPointable);
          }
  
          if (PointableHelper.checkAndSetMissingOrNull(result, valuePointable, positionPointable, flagPointable)) {
              return;
          }
  
          // Value argument
          byte[] valueBytes = valuePointable.getByteArray();
          int valueStartOffset = valuePointable.getStartOffset();
  
          // Type and value validity check
          if (!PointableHelper.isValidLongValue(valueBytes, valueStartOffset, true)) {
              ExceptionUtil.warnTypeMismatch(context, srcLoc, funID, valueBytes[valueStartOffset], 0, ATypeTag.BIGINT);
              PointableHelper.setNull(result);
              return;
          }
  
          // Position argument
          byte[] positionBytes = positionPointable.getByteArray();
          int positionStartOffset = positionPointable.getStartOffset();
          ATypeTag positionTypeTag =
                  EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(positionBytes[positionStartOffset]);
  
          // Type validity check (for position argument, array is a valid type as well)
          if (!ATypeHierarchy.canPromote(positionTypeTag, ATypeTag.DOUBLE) && positionTypeTag != ATypeTag.ARRAY) {
              ExceptionUtil.warnTypeMismatch(context, srcLoc, funID, positionBytes[positionStartOffset], 1,
                      secondArgumentExpectedTypes);
              PointableHelper.setNull(result);
              return;
          }
  
          // Third argument
          boolean isAllSet = false;
          isStopUpdatingResultBoolean = false; // Reset the flag to false for each new tuple
          if (flagEvaluator != null) {
              byte[] flagBytes = flagPointable.getByteArray();
              int flagStartOffset = flagPointable.getStartOffset();
              ATypeTag flagTypeTag = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(flagBytes[flagStartOffset]);
  
              if (flagTypeTag != ATypeTag.BOOLEAN) {
                  ExceptionUtil.warnTypeMismatch(context, srcLoc, funID, flagBytes[flagStartOffset], 2, ATypeTag.BOOLEAN);
                  PointableHelper.setNull(result);
                  return;
              }
  
              isAllSet = ABooleanSerializerDeserializer.getBoolean(flagBytes, flagStartOffset + 1);
          }
  
          // Result long value
          long longResult = ATypeHierarchy.getLongValue(funID.getName(), 0, valueBytes, valueStartOffset);
  
          // If any operation returns false, the result should be null
          boolean isSuccessfulOperation;
  
          // From here onward, we got valid arguments, so let's handle numeric or array position argument
          if (positionTypeTag != ATypeTag.ARRAY) {
              isSuccessfulOperation =
                      applyBitWiseOperationWithNumericAsPosition(longResult, positionBytes, positionStartOffset);
          } else {
              isSuccessfulOperation =
                      applyBitWiseOperationWithArrayAsPosition(longResult, positionBytes, positionStartOffset, isAllSet);
          }
  
          if (!isSuccessfulOperation) {
              PointableHelper.setNull(result);
              return;
          }
  
          // Write the result
          resultStorage.reset();
          aBooleanSerde.serialize(resultBoolean, resultStorage.getDataOutput());
          result.set(resultStorage);
      }
  
      /**
       * This method applies the bitwise operation on the int64 value at the specified positions in
       * the array.
       *
       * @param value The long value
       * @param bytes bytes of the position
       * @param startOffset start offset of the position
       * @param isAllSet is AllSet check flag
       * @return returns {@code true} if all operations are successful, {@code false} otherwise.
       *
       * @throws HyracksDataException HyracksDataException
       */
      private boolean applyBitWiseOperationWithArrayAsPosition(long value, byte[] bytes, int startOffset,
              boolean isAllSet) throws HyracksDataException {
  
          listAccessor.reset(bytes, startOffset);
  
          // In case of an empty array, we return the value as is without change
          if (listAccessor.size() == 0) {
              return true;
          }
  
          try {
              // For each item in the array, apply the bitwise operation at that position
              for (int i = 0; i < listAccessor.size(); i++) {
                  listAccessor.getOrWriteItem(i, listItemPointable, resultStorage);
  
                  byte[] itemBytes = listItemPointable.getByteArray();
                  int itemStartOffset = listItemPointable.getStartOffset();
  
                  if (!applyBitWiseOperationWithNumericAsPosition(value, itemBytes, itemStartOffset)) {
                      return false;
                  }
  
                  // Based on the isAllSet flag, we might want to stop re-calculating the result, this
                  // isStopUpdatingResultBoolean keeps an eye on that.
                  isStopUpdatingResultBoolean =
                          !isAllSet ? resultBoolean == ABoolean.TRUE : resultBoolean == ABoolean.FALSE;
              }
  
              return true;
          } catch (IOException ex) {
              throw HyracksDataException.create(ex);
          }
      }
  
      /**
       * This method applies the bitwise operation on the int64 value at the specified position.
       *
       * @param value the long value
       * @param bytes data bytes
       * @param startOffset start offset
       * @return returns {@code true} if all operations are successful, {@code false} otherwise.
       */
      private boolean applyBitWiseOperationWithNumericAsPosition(long value, byte[] bytes, int startOffset)
              throws HyracksDataException {
  
          // Value validity check
          if (!PointableHelper.isValidLongValue(bytes, startOffset, true)) {
              ExceptionUtil.warnTypeMismatch(context, srcLoc, funID, bytes[startOffset], 1, ATypeTag.BIGINT);
              return false;
          }
  
          long position = ATypeHierarchy.getLongValue(funID.getName(), 1, bytes, startOffset);
  
          // Ensure the position is between 1 and 64 (int64 has 64 bits)
          if (position < 1 || position > 64) {
              handleOutOfRangeInput(1, 1, 64, position);
              return false;
          }
  
          // Checks if a certain position is bit 1
          if (!isStopUpdatingResultBoolean) {
              resultBoolean = (value & (1L << (position - 1))) != 0 ? ABoolean.TRUE : ABoolean.FALSE;
          }
  
          return true;
      }
  
      private void handleOutOfRangeInput(int inputPosition, int startLimit, int endLimit, long actual) {
          IWarningCollector warningCollector = context.getWarningCollector();
          if (warningCollector.shouldWarn()) {
              warningCollector.warn(Warning.of(srcLoc, ErrorCode.VALUE_OUT_OF_RANGE, funID,
                      ExceptionUtil.indexToPosition(inputPosition), startLimit, endLimit, actual));
          }
      }
  }
  