@SuppressWarnings("fallthrough")
  public final void writeDouble(double value) {
    if (Debug.COMMUNICATIONS && getCheckCommunications()) {
      int step = DOUBLE_DEBUG;

      if (interrupted()) step = resumeInt();

      switch (step) {
        case DOUBLE_DEBUG:
          {
            if (!canWriteDebugInfo()) {
              interruptInt(DOUBLE_DEBUG);
              return;
            }

            Helper.getInstance().setExpectedClass(ImmutableClass.DOUBLE_INDEX);
            writeDebugInfo(ImmutableClass.DOUBLE_INDEX, 0);
          }
        case DOUBLE_VALUE:
          {
            writeDoubleNoDebug(value);

            if (interrupted()) {
              interruptInt(DOUBLE_VALUE);
              return;
            }
          }
      }
    } else writeDoubleNoDebug(value);
  }
  public final boolean canWriteBoolean() {
    int needed = BOOLEAN_LENGTH;

    if (Debug.COMMUNICATIONS && getCheckCommunications()) {
      Helper.getInstance().setExpectedClass(ImmutableClass.BOOLEAN_INDEX);
      needed += ImmutableWriter.DEBUG_OVERHEAD;
    }

    return remaining() >= needed;
  }
  @SuppressWarnings("fallthrough")
  public final void writeDoubleBoxed(Double value) {
    int step =
        Debug.COMMUNICATIONS && getCheckCommunications() ? DOUBLE_BOXED_DEBUG : DOUBLE_BOXED_NULL;

    if (interrupted()) step = resumeInt();

    if (Debug.COMMUNICATIONS && getCheckCommunications()) {
      if (step == DOUBLE_BOXED_DEBUG) {
        if (!canWriteDebugInfo()) {
          interruptInt(DOUBLE_BOXED_DEBUG);
          return;
        }

        Helper.getInstance().setExpectedClass(ImmutableClass.DOUBLE_BOXED_INDEX);
        writeDebugInfo(ImmutableClass.DOUBLE_BOXED_INDEX, 0);
        step = DOUBLE_BOXED_NULL;
      }
    }

    switch (step) {
      case DOUBLE_BOXED_NULL:
        {
          if (!canWriteBoolean()) {
            interruptInt(DOUBLE_BOXED_NULL);
            return;
          }

          writeBooleanToBuffer(value == null); // Marker

          if (value == null) // Marker
          return;
        }
      case DOUBLE_BOXED_VALUE:
        {
          writeDoubleNoDebug(value); // Marker

          if (interrupted()) {
            interruptInt(DOUBLE_BOXED_VALUE);
            return;
          }
        }
    }
  }
  private final void writeBinary(byte[] value, int classId) {
    int index = Debug.COMMUNICATIONS && getCheckCommunications() ? -2 : -1;

    if (interrupted()) index = resumeInt();

    if (Debug.COMMUNICATIONS && getCheckCommunications()) {
      if (index == -2) {
        if (!canWriteDebugInfo()) {
          interruptInt(-2);
          return;
        }

        Helper.getInstance().setExpectedClass(classId);
        writeDebugInfo(classId, 0);
      }
    }

    if (index < 0) {
      if (remaining() < INTEGER_LENGTH) {
        interruptInt(-1);
        return;
      }

      if (value == null) {
        writeIntegerToBuffer(-1);
        return;
      }

      writeIntegerToBuffer(value.length);
      index = 0;
    }

    for (; index < value.length; index++) {
      if (remaining() == 0) {
        interruptInt(index);
        return;
      }

      writeByteToBuffer(value[index]);
    }
  }
  private final void writeDebugInfo(int classId, int tag) {
    if (!Debug.COMMUNICATIONS || !getCheckCommunications()) throw new IllegalStateException();

    if (Debug.COMMUNICATIONS_LOG_ALL) {
      String name = ImmutableClass.ALL.get(classId).toString();
      Log.write(
          PlatformClass.getSimpleName(getClass())
              + ", "
              + ThreadAssert.getOrCreateCurrent().getWriterDebugCounter(this)
              + ", class: "
              + name
              + ", tag: "
              + tag);
    }

    Debug.assertion(classId == Helper.getInstance().getExpectedClass());
    long debugCounter = ThreadAssert.getOrCreateCurrent().getAndIncrementWriterDebugCounter(this);
    writeLongToBuffer(debugCounter);
    writeByteToBuffer((byte) classId);
    writeIntegerToBuffer(tag);
    writeIntegerToBuffer(getCustomDebugInfo1());
    writeIntegerToBuffer(getCustomDebugInfo2());
  }
  private final void writeString(String value, int tag, boolean debug) {
    int index = Debug.COMMUNICATIONS && getCheckCommunications() && debug ? -1 : 0;

    if (interrupted()) index = resumeInt();

    if (Debug.COMMUNICATIONS && getCheckCommunications() && debug) {
      if (index < 0) {
        if (!canWriteDebugInfo()) {
          interruptInt(-1);
          return;
        }

        Helper.getInstance().setExpectedClass(ImmutableClass.STRING_INDEX);
        writeDebugInfo(ImmutableClass.STRING_INDEX, tag);
        index = 0;
      }
    }

    if (value == null) {
      if (remaining() == 0) {
        interruptInt(0);
        return;
      }

      int mask = STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK;
      mask |= STRING_ENC_DOES_NOT_FIT_ON_2_BYTES_MASK;
      mask |= STRING_ENC_EOF_MASK;
      mask |= STRING_ENC_NULL_MASK;
      writeByteToBuffer((byte) mask);
      return;
    }

    for (; ; ) {
      if (index == value.length()) {
        if (remaining() == 0) {
          interruptInt(index);
          return;
        }

        int mask = STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK;
        mask |= STRING_ENC_DOES_NOT_FIT_ON_2_BYTES_MASK;
        mask |= STRING_ENC_EOF_MASK;
        writeByteToBuffer((byte) mask);
        return;
      }

      char c = value.charAt(index);

      if (c < 128) { // Char fits in 7 bits -> 1 byte
        if (remaining() == 0) {
          interruptInt(index);
          return;
        }

        writeByteToBuffer((byte) c);
      } else if (c < 16384) { // Char fits in 14 bits -> 2 bytes
        if (remaining() < 2) {
          interruptInt(index);
          return;
        }

        writeByteToBuffer((byte) (STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK | (c >> 8)));
        writeByteToBuffer((byte) (c & 0xff));
      } else { // Others -> 3 bytes
        if (remaining() < 3) {
          interruptInt(index);
          return;
        }

        int mask = STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK;
        mask |= STRING_ENC_DOES_NOT_FIT_ON_2_BYTES_MASK;
        writeByteToBuffer((byte) mask);
        writeCharacterToBuffer(c);
      }

      index++;
    }
  }