// 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 static java.nio.ByteOrder.LITTLE_ENDIAN;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.nio.ByteBuffer;

/**
 * Provides a view of a zip files "end of central directory" record.
 */
public class EndOfCentralDirectory extends View<EndOfCentralDirectory> {

  /**
   * Returns a {@code EndOfCentralDirectory} of the given buffer.
   *
   * @param buffer containing the data of a "end of central directory" record.
   * @return a {@code EndOfCentralDirectory} of the data at the current position of the given
   * byte buffer.
   */
  public static EndOfCentralDirectory viewOf(ByteBuffer buffer) {
    EndOfCentralDirectory view = new EndOfCentralDirectory(buffer.slice());
    view.buffer.position(0).limit(view.getSize());
    buffer.position(buffer.position() + view.buffer.remaining());
    return view;
  }

  private EndOfCentralDirectory(ByteBuffer buffer) {
    super(buffer);
  }

  /**
   * Creates a {@code EndOfCentralDirectory} with a heap allocated buffer.
   *
   * @param comment zip file comment, or {@code null}.
   * @return a {@code EndOfCentralDirectory} with a heap allocated buffer.
   */
  public static EndOfCentralDirectory allocate(String comment) {
    byte[] commentData = comment != null ? comment.getBytes(UTF_8) : EMPTY;
    int size = SIZE + commentData.length;
    ByteBuffer buffer = ByteBuffer.allocate(size).order(LITTLE_ENDIAN);
    return new EndOfCentralDirectory(buffer).init(commentData);
  }

  /**
   * Creates a {@code EndOfCentralDirectory} over a writable buffer.
   *
   * @param buffer buffer to hold data for the "end of central directory" record.
   * @param comment zip file global comment, or {@code null}.
   * @return a {@code EndOfCentralDirectory} with a heap allocated buffer.
   */
  public static EndOfCentralDirectory view(ByteBuffer buffer, String comment) {
    byte[] commentData = comment != null ? comment.getBytes(UTF_8) : EMPTY;
    int size = SIZE + commentData.length;
    EndOfCentralDirectory view = new EndOfCentralDirectory(buffer.slice()).init(commentData);
    buffer.position(buffer.position() + size);
    return view;
  }

  /**
   * Copies this {@code EndOfCentralDirectory} over a writable buffer.
   *
   * @param buffer writable byte buffer to hold the data of the copy.
   */
  public EndOfCentralDirectory copy(ByteBuffer buffer) {
    EndOfCentralDirectory view = new EndOfCentralDirectory(buffer.slice());
    this.buffer.rewind();
    view.buffer.put(this.buffer).flip();
    this.buffer.rewind();
    buffer.position(buffer.position() + this.buffer.remaining());
    return view;
  }

  @CanIgnoreReturnValue
  private EndOfCentralDirectory init(byte[] comment) {
    buffer.putInt(0, SIGNATURE);
    set(ENDCOM, comment.length);
    if (comment.length > 0) {
      buffer.position(SIZE);
      buffer.put(comment);
    }
    buffer.position(0).limit(SIZE + comment.length);
    return this;
  }

  /**
   * Signature of end of directory record.
   */
  public static final int SIGNATURE = 0x06054b50; //101010256L

  /**
   * Size of end of directory record, not including variable data.
   * Also the offset of the file comment, if any.
   */
  public static final int SIZE = 22;

  /**
   * For accessing the end of central directory signature, with the {@link
   * View#get(com.google.devtools.build.android.ziputils.View.IntFieldId)} and {@link
   * View#set(com.google.devtools.build.android.ziputils.View.IntFieldId, long)} methods.
   */
  public static final IntFieldId<EndOfCentralDirectory> ENDSIG = new IntFieldId<>(0);

  /**
   * For accessing the "this disk number" end of central directory field, with the {@link
   * View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)} and {@link
   * View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, int)} methods.
   */
  public static final ShortFieldId<EndOfCentralDirectory> ENDDSK = new ShortFieldId<>(4);

  /**
   * For accessing the "central directory start disk" end of central directory field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)} and {@link
   * View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, int)} methods.
   */
  public static final ShortFieldId<EndOfCentralDirectory> ENDDCD = new ShortFieldId<>(6);

  /**
   * For accessing the "central directory local records" end of central directory field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)} and {@link
   * View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, int)} methods.
   */
  public static final ShortFieldId<EndOfCentralDirectory> ENDSUB = new ShortFieldId<>(8);

  /**
   * For accessing the "central directory total records" end of central directory field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)} and {@link
   * View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, int)} methods.
   */
  public static final ShortFieldId<EndOfCentralDirectory> ENDTOT = new ShortFieldId<>(10);

  /**
   * For accessing the "central directory size" end of central directory field, with the {@link
   * View#get(com.google.devtools.build.android.ziputils.View.IntFieldId)} and {@link
   * View#set(com.google.devtools.build.android.ziputils.View.IntFieldId, long)} methods.
   */
  public static final IntFieldId<EndOfCentralDirectory> ENDSIZ = new IntFieldId<>(12);

  /**
   * For accessing the "central directory offset" end of central directory field, with the {@link
   * View#get(com.google.devtools.build.android.ziputils.View.IntFieldId)} and {@link
   * View#set(com.google.devtools.build.android.ziputils.View.IntFieldId, long)} methods.
   */
  public static final IntFieldId<EndOfCentralDirectory> ENDOFF = new IntFieldId<>(16);

  /**
   * For accessing the "file comment length" end of central directory field, with the {@link
   * View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)} and {@link
   * View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, int)} methods.
   */
  public static final ShortFieldId<EndOfCentralDirectory> ENDCOM = new ShortFieldId<>(20);

  /**
   * Returns the file comment.
   * @return the file comment, or an empty string.
   */
  public final String getComment() {
    return getString(SIZE, get(ENDCOM));
  }

  /**
   * Returns the total size of the end of directory record, including file comment, if any.
   * @return the total size of the end of directory record.
   */
  public int getSize() {
    return SIZE + get(ENDCOM);
  }
}
