content: /*
   * Copyright (C) 2015 Actor LLC. <https://actor.im>
   */
  
  package im.actor.runtime.bser;
  
  import org.jetbrains.annotations.NotNull;
  
  import java.io.IOException;
  import java.util.List;
  
  import im.actor.runtime.collections.SparseArray;
  import im.actor.runtime.collections.SparseBooleanArray;
  
  // Disabling Bounds checks for speeding up calculations
  
  /*-[
  #define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1
  ]-*/
  
  public class BserWriter {
  
      private DataOutput stream;
  
      private SparseBooleanArray writtenFields = new SparseBooleanArray();
  
      public BserWriter(DataOutput stream) {
          if (stream == null) {
              throw new IllegalArgumentException("Stream can not be null");
          }
  
          this.stream = stream;
      }
  
      public void writeBytes(int fieldNumber, @NotNull byte[] value) throws IOException {
          if (value == null) {
              throw new IllegalArgumentException("Value can not be null");
          }
          if (value.length > BserLimits.MAX_BLOCK_SIZE) {
              throw new IllegalArgumentException("Unable to write more than 1 MB");
          }
          writtenFields.put(fieldNumber, true);
          writeBytesField(fieldNumber, value);
      }
  
      public void writeString(int fieldNumber, @NotNull String value) throws IOException {
          if (value == null) {
              throw new IllegalArgumentException("Value can not be null");
          }
          writtenFields.put(fieldNumber, true);
          writeBytesField(fieldNumber, value.getBytes());
      }
  
      public void writeBool(int fieldNumber, boolean value) throws IOException {
          writeVarIntField(fieldNumber, value ? 1 : 0);
      }
  
      public void writeInt(int fieldNumber, int value) throws IOException {
          writeVarIntField(fieldNumber, value);
      }
  
      public void writeIntFixed(int fieldNumber, int value) throws IOException {
          writeVar32Fixed(fieldNumber, value);
      }
  
      public void writeDouble(int fieldNumber, double value) throws IOException {
          writeVar64Fixed(fieldNumber, Double.doubleToLongBits(value));
      }
  
      public void writeLongFixed(int fieldNumber, long value) throws IOException {
          writeVar64Fixed(fieldNumber, Double.doubleToLongBits(value));
      }
  
      public void writeLong(int fieldNumber, long value) throws IOException {
          writeVarIntField(fieldNumber, value);
      }
  
      public void writeRepeatedLong(int fieldNumber, @NotNull List<Long> values) throws IOException {
          if (values == null) {
              throw new IllegalArgumentException("Values can not be null");
          }
          if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
              throw new IllegalArgumentException("Too many values");
          }
          writtenFields.put(fieldNumber, true);
          for (Long l : values) {
              if (l == null) {
                  throw new IllegalArgumentException("Value can not be null");
              }
              writeVarIntField(fieldNumber, l);
          }
      }
  
      public void writeRepeatedInt(int fieldNumber, @NotNull List<Integer> values) throws IOException {
          if (values == null) {
              throw new IllegalArgumentException("Values can not be null");
          }
          if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
              throw new IllegalArgumentException("Too many values");
          }
          writtenFields.put(fieldNumber, true);
          for (Integer l : values) {
              if (l == null) {
                  throw new IllegalArgumentException("Value can not be null");
              }
              writeVarIntField(fieldNumber, l);
          }
      }
  
      public void writeRepeatedBool(int fieldNumber, @NotNull List<Boolean> values) throws IOException {
          if (values == null) {
              throw new IllegalArgumentException("Values can not be null");
          }
          if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
              throw new IllegalArgumentException("Too many values");
          }
          writtenFields.put(fieldNumber, true);
          for (Boolean l : values) {
              if (l == null) {
                  throw new IllegalArgumentException("Value can not be null");
              }
              writeBool(fieldNumber, l);
          }
      }
  
      public void writeRepeatedString(int fieldNumber, @NotNull List<String> values) throws IOException {
          if (values == null) {
              throw new IllegalArgumentException("Values can not be null");
          }
          if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
              throw new IllegalArgumentException("Too many values");
          }
          writtenFields.put(fieldNumber, true);
          for (String l : values) {
              if (l == null) {
                  throw new IllegalArgumentException("Value can not be null");
              }
              writeString(fieldNumber, l);
          }
      }
  
      public void writeRepeatedBytes(int fieldNumber, @NotNull List<byte[]> values) throws IOException {
          if (values == null) {
              throw new IllegalArgumentException("Values can not be null");
          }
          if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
              throw new IllegalArgumentException("Too many values");
          }
          writtenFields.put(fieldNumber, true);
          for (byte[] l : values) {
              if (l == null) {
                  throw new IllegalArgumentException("Value can not be null");
              }
              writeBytes(fieldNumber, l);
          }
      }
  
      public <T extends BserObject> void writeRepeatedObj(int fieldNumber, @NotNull List<T> values) throws IOException {
          if (values == null) {
              throw new IllegalArgumentException("Values can not be null");
          }
          if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
              throw new IllegalArgumentException("Too many values");
          }
          writtenFields.put(fieldNumber, true);
          for (T l : values) {
              if (l == null) {
                  throw new IllegalArgumentException("Value can not be null");
              }
              writeObject(fieldNumber, l);
          }
      }
  
      public void writeObject(int fieldNumber, @NotNull BserObject value) throws IOException {
          if (value == null) {
              throw new IllegalArgumentException("Value can not be null");
          }
          writtenFields.put(fieldNumber, true);
          writeTag(fieldNumber, WireTypes.TYPE_LENGTH_DELIMITED);
          DataOutput outputStream = new DataOutput();
          BserWriter writer = new BserWriter(outputStream);
          value.serialize(writer);
          writeBytes(outputStream.toByteArray());
      }
  
      public void writeUnmapped(int fieldNumber, @NotNull Object value) throws IOException {
          if (writtenFields.get(fieldNumber, false)) {
              return;
          }
  
          if (value instanceof Long) {
              writeLong(fieldNumber, (Long) value);
          } else if (value instanceof byte[]) {
              writeBytes(fieldNumber, (byte[]) value);
          } else if (value instanceof List) {
              for (Object o : (List) value) {
                  if (o instanceof Long) {
                      writeLong(fieldNumber, (Long) o);
                  } else if (o instanceof byte[]) {
                      writeBytes(fieldNumber, (byte[]) o);
                  } else {
                      throw new IOException("Incorrect unmapped value in List");
                  }
              }
          } else {
              throw new IOException("Incorrect unmapped value");
          }
      }
  
      public void writeRaw(byte[] raw) throws IOException {
          if (raw == null) {
              throw new IllegalArgumentException("Raw can not be null");
          }
  
          stream.writeBytes(raw, 0, raw.length);
      }
  
      private void writeTag(int fieldNumber, int wireType) throws IOException {
          fieldNumber = (fieldNumber & 0xFFFF);
          if (fieldNumber <= 0) {
              throw new IllegalArgumentException("Field Number must greater than zero");
          }
  
          long tag = ((long) (fieldNumber << 3) | wireType);
          stream.writeVarInt(tag);
      }
  
      private void writeVarIntField(int fieldNumber, long value) throws IOException {
          writeTag(fieldNumber, WireTypes.TYPE_VARINT);
          writeVarInt(value);
      }
  
      private void writeBytesField(int fieldNumber, @NotNull byte[] value) throws IOException {
          writeTag(fieldNumber, WireTypes.TYPE_LENGTH_DELIMITED);
          writeBytes(value);
      }
  
      private void writeVar64Fixed(int fieldNumber, long value) throws IOException {
          writeTag(fieldNumber, WireTypes.TYPE_64BIT);
          writeLong(value);
      }
  
      private void writeVar32Fixed(int fieldNumber, long value) throws IOException {
          writeTag(fieldNumber, WireTypes.TYPE_32BIT);
          writeInt(value);
      }
  
  
      private void writeVarInt(long value) throws IOException {
          stream.writeVarInt(value & 0xFFFFFFFF);
      }
  
      private void writeLong(long v) throws IOException {
          stream.writeLong(v & 0xFFFFFFFF);
      }
  
      private void writeInt(long v) throws IOException {
          stream.writeInt((int) (v & 0xFFFF));
      }
  
      private void writeBytes(@NotNull byte[] data) throws IOException {
          stream.writeProtoBytes(data, 0, data.length);
      }
  }
  