// Copyright 2008 Google Inc. All rights reserved.
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.google.gerrit.server.ioutil;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.gerrit.entities.CodedEnum;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.eclipse.jgit.util.IO;

public class BasicSerialization {
  private static final byte[] NO_BYTES = {};

  private static int safeRead(InputStream input) throws IOException {
    final int b = input.read();
    if (b == -1) {
      throw new EOFException();
    }
    return b;
  }

  /** Read a fixed-width 64 bit integer in network byte order (big-endian). */
  public static long readFixInt64(InputStream input) throws IOException {
    final long h = readFixInt32(input);
    final long l = readFixInt32(input) & 0xFFFFFFFFL;
    return (h << 32) | l;
  }

  /** Write a fixed-width 64 bit integer in network byte order (big-endian). */
  public static void writeFixInt64(OutputStream output, long val) throws IOException {
    writeFixInt32(output, (int) (val >>> 32));
    writeFixInt32(output, (int) (val & 0xFFFFFFFFL));
  }

  /** Read a fixed-width 32 bit integer in network byte order (big-endian). */
  public static int readFixInt32(InputStream input) throws IOException {
    final int b1 = safeRead(input);
    final int b2 = safeRead(input);
    final int b3 = safeRead(input);
    final int b4 = safeRead(input);
    return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
  }

  /** Write a fixed-width 32 bit integer in network byte order (big-endian). */
  public static void writeFixInt32(OutputStream output, int val) throws IOException {
    output.write((val >>> 24) & 0xFF);
    output.write((val >>> 16) & 0xFF);
    output.write((val >>> 8) & 0xFF);
    output.write(val & 0xFF);
  }

  /** Read a varint from the input, one byte at a time. */
  public static int readVarInt32(InputStream input) throws IOException {
    int result = 0;
    int offset = 0;
    for (; offset < 32; offset += 7) {
      final int b = safeRead(input);
      result |= (b & 0x7f) << offset;
      if ((b & 0x80) == 0) {
        return result;
      }
    }
    throw new EOFException();
  }

  /** Write a varint; value is treated as an unsigned value. */
  public static void writeVarInt32(OutputStream output, int value) throws IOException {
    while (true) {
      if ((value & ~0x7F) == 0) {
        output.write(value);
        return;
      }
      output.write((value & 0x7F) | 0x80);
      value >>>= 7;
    }
  }

  /** Read a fixed length byte array whose length is specified as a varint. */
  public static byte[] readBytes(InputStream input) throws IOException {
    final int len = readVarInt32(input);
    if (len == 0) {
      return NO_BYTES;
    }
    final byte[] buf = new byte[len];
    IO.readFully(input, buf, 0, len);
    return buf;
  }

  /** Write a byte array prefixed by its length in a varint. */
  public static void writeBytes(OutputStream output, byte[] data) throws IOException {
    writeBytes(output, data, 0, data.length);
  }

  /** Write a byte array prefixed by its length in a varint. */
  public static void writeBytes(final OutputStream output, byte[] data, int offset, int len)
      throws IOException {
    writeVarInt32(output, len);
    output.write(data, offset, len);
  }

  /** Read a UTF-8 string, prefixed by its byte length in a varint. */
  public static String readString(InputStream input) throws IOException {
    final byte[] bin = readBytes(input);
    if (bin.length == 0) {
      return null;
    }
    return new String(bin, 0, bin.length, UTF_8);
  }

  /** Write a UTF-8 string, prefixed by its byte length in a varint. */
  public static void writeString(OutputStream output, String s) throws IOException {
    if (s == null) {
      writeVarInt32(output, 0);
    } else {
      writeBytes(output, s.getBytes(UTF_8));
    }
  }

  /** Read an enum whose code is stored as a varint. */
  public static <T extends CodedEnum> T readEnum(InputStream input, T[] all) throws IOException {
    final int val = readVarInt32(input);
    for (T t : all) {
      if (t.getCode() == val) {
        return t;
      }
    }
    throw new IOException("Invalid enum " + val + " for " + all[0].getClass());
  }

  /** Write an enum whose code is stored as a varint. */
  public static <T extends CodedEnum> void writeEnum(OutputStream output, T e) throws IOException {
    writeVarInt32(output, e.getCode());
  }

  private BasicSerialization() {}
}
