// Copyright 2015 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.android.ziputils;

import com.google.common.base.Preconditions;

import java.nio.ByteBuffer;

/**
 * Helper class for finding byte patterns in a byte buffer, scanning forwards or backwards.
 * This is used to located the "end of central directory" marker, and in other instances
 * where zip file elements must be located by scanning for known signatures.
 * For instance, when it's necessary to scan for zip entries, without
 * relying on the central directory to provide the exact location, or when scanning for
 * data descriptor at the end of an entry of unknown size.
 */
public class ScanUtil {

  /**
   * Finds the next previous position of a given byte sequence in a given byte buffer,
   * starting from the buffer's current position (excluded).
   *
   * @param target byte sequence to search for.
   * @param buffer byte buffer in which to search.
   * @return position of the last location of the target sequence, at a position
   * strictly smaller than the current position, or -1 if not found.
   * @throws IllegalArgumentException if either {@code target} or {@code buffer} is {@code null}.
   */
  static int scanBackwardsTo(byte[] target, ByteBuffer buffer) {
    Preconditions.checkNotNull(target);
    Preconditions.checkNotNull(buffer);
    if (target.length == 0) {
      return buffer.position() - 1;
    }
    int pos = buffer.position() - target.length;
    if (pos < 0) {
      return -1;
    }
    scan:
    while (true) {
      while (pos >= 0 && buffer.get(pos) != target[0]) {
        pos--;
      }
      if (pos < 0) {
        return -1;
      }
      for (int i = 1; i < target.length; i++) {
        if (buffer.get(pos + i) != target[i]) {
          pos--;
          continue scan;
        }
      }
      return pos;
    }
  }

  /**
   * Finds the next position of a given byte sequence in a given byte buffer, starting from the
   * buffer's current position (included).
   *
   * @param target byte sequence to search for.
   * @param buffer byte buffer in which to search.
   * @return position of the first location of the target sequence, or -1 if not found.
   * @throws IllegalArgumentException if either {@code target} or {@code buffer} is {@code null}.
   */
  static int scanTo(byte[] target, ByteBuffer buffer) {
    Preconditions.checkNotNull(target);
    Preconditions.checkNotNull(buffer);
    if (!buffer.hasRemaining()) {
      return -1;
    }
    int pos = buffer.position();
    if (target.length == 0) {
      return pos;
    }
    scan:
    while (true) {
      while (pos <= buffer.limit() - target.length && buffer.get(pos) != target[0]) {
        pos++;
      }
      if (pos > buffer.limit() - target.length + 1) {
        return -1;
      }
      for (int i = 1; i < target.length; i++) {
        if (buffer.get(pos + i) != target[i]) {
          pos++;
          continue scan;
        }
      }
      return pos;
    }
  }
}
