/**
   * @param blockInfo
   * @return space currently available for writing all Flac metadatablocks except for StreamInfo
   *     which is fixed size
   */
  private int computeAvailableRoom(MetadataBlockInfo blockInfo) {
    int length = 0;

    for (MetadataBlock aMetadataBlockApplication : blockInfo.metadataBlockApplication) {
      length += aMetadataBlockApplication.getLength();
    }

    for (MetadataBlock aMetadataBlockSeekTable : blockInfo.metadataBlockSeekTable) {
      length += aMetadataBlockSeekTable.getLength();
    }

    for (MetadataBlock aMetadataBlockCueSheet : blockInfo.metadataBlockCueSheet) {
      length += aMetadataBlockCueSheet.getLength();
    }

    for (MetadataBlock aMetadataBlockPadding : blockInfo.metadataBlockPadding) {
      length += aMetadataBlockPadding.getLength();
    }

    return length;
  }
  /**
   * Write tag to file
   *
   * @param tag
   * @param raf
   * @param rafTemp
   * @throws CannotWriteException
   * @throws IOException
   */
  public void write(Tag tag, RandomAccessFile raf, RandomAccessFile rafTemp)
      throws CannotWriteException, IOException {
    logger.config("Writing tag");

    MetadataBlockInfo blockInfo = new MetadataBlockInfo();

    // Read existing data
    FlacStreamReader flacStream = new FlacStreamReader(raf);
    try {
      flacStream.findStream();
    } catch (CannotReadException cre) {
      throw new CannotWriteException(cre.getMessage());
    }

    boolean isLastBlock = false;
    while (!isLastBlock) {
      MetadataBlockHeader mbh = MetadataBlockHeader.readHeader(raf);
      switch (mbh.getBlockType()) {
        case STREAMINFO:
          {
            blockInfo.streamInfoBlock =
                new MetadataBlock(mbh, new MetadataBlockDataStreamInfo(mbh, raf));
            break;
          }

        case VORBIS_COMMENT:
        case PADDING:
        case PICTURE:
          {
            // All these will be replaced by the new metadata so we just treat as padding in order
            // to determine how much space is already allocated in the file
            raf.seek(raf.getFilePointer() + mbh.getDataLength());
            MetadataBlockData mbd = new MetadataBlockDataPadding(mbh.getDataLength());
            blockInfo.metadataBlockPadding.add(new MetadataBlock(mbh, mbd));
            break;
          }
        case APPLICATION:
          {
            MetadataBlockData mbd = new MetadataBlockDataApplication(mbh, raf);
            blockInfo.metadataBlockApplication.add(new MetadataBlock(mbh, mbd));
            break;
          }
        case SEEKTABLE:
          {
            MetadataBlockData mbd = new MetadataBlockDataSeekTable(mbh, raf);
            blockInfo.metadataBlockSeekTable.add(new MetadataBlock(mbh, mbd));
            break;
          }
        case CUESHEET:
          {
            MetadataBlockData mbd = new MetadataBlockDataCueSheet(mbh, raf);
            blockInfo.metadataBlockCueSheet.add(new MetadataBlock(mbh, mbd));
            break;
          }
        default:
          {
            // What are the consequences of doing this
            raf.seek(raf.getFilePointer() + mbh.getDataLength());
            break;
          }
      }
      isLastBlock = mbh.isLastBlock();
    }

    // Number of bytes in the existing file available before audio data
    int availableRoom = computeAvailableRoom(blockInfo);

    // Minimum Size of the New tag data without padding
    int newTagSize = tc.convert(tag).limit();

    // Number of bytes required for new tagdata and other metadata blocks
    int neededRoom = newTagSize + computeNeededRoom(blockInfo);

    // Go to start of Flac within file
    raf.seek(flacStream.getStartOfFlacInFile());

    logger.config("Writing tag available bytes:" + availableRoom + ":needed bytes:" + neededRoom);

    // There is enough room to fit the tag without moving the audio just need to
    // adjust padding accordingly need to allow space for padding header if padding required
    if ((availableRoom == neededRoom)
        || (availableRoom > neededRoom + MetadataBlockHeader.HEADER_LENGTH)) {
      // Jump over Id3 (if exists) Flac and StreamInfoBlock
      raf.seek(flacStream.getStartOfFlacInFile() + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH);

      // Write StreamInfo, we always write this first even if wasn't first in original spec
      raf.write(blockInfo.streamInfoBlock.getHeader().getBytesWithoutIsLastBlockFlag());
      raf.write(blockInfo.streamInfoBlock.getData().getBytes());

      // Write Application Blocks
      for (MetadataBlock aMetadataBlockApplication : blockInfo.metadataBlockApplication) {
        raf.write(aMetadataBlockApplication.getHeader().getBytesWithoutIsLastBlockFlag());
        raf.write(aMetadataBlockApplication.getData().getBytes());
      }

      // Write Seek Table Blocks
      for (MetadataBlock aMetadataBlockSeekTable : blockInfo.metadataBlockSeekTable) {
        raf.write(aMetadataBlockSeekTable.getHeader().getBytesWithoutIsLastBlockFlag());
        raf.write(aMetadataBlockSeekTable.getData().getBytes());
      }

      // Write Cue sheet Blocks
      for (MetadataBlock aMetadataBlockCueSheet : blockInfo.metadataBlockCueSheet) {
        raf.write(aMetadataBlockCueSheet.getHeader().getBytesWithoutIsLastBlockFlag());
        raf.write(aMetadataBlockCueSheet.getData().getBytes());
      }

      // Write tag (and padding)
      raf.getChannel().write(tc.convert(tag, availableRoom - neededRoom));
    }
    // Need to move audio
    else {
      // Skip to start of Audio

      // If Flac tag contains ID3header or something before start of official Flac header copy it
      // over
      if (flacStream.getStartOfFlacInFile() > 0) {
        raf.seek(0);
        rafTemp.getChannel().transferFrom(raf.getChannel(), 0, flacStream.getStartOfFlacInFile());
        rafTemp.seek(flacStream.getStartOfFlacInFile());
      }
      rafTemp.writeBytes(FlacStreamReader.FLAC_STREAM_IDENTIFIER);
      rafTemp.writeByte(0); // To ensure never set Last-metadata-block flag even if was before

      int uptoStreamHeaderSize =
          flacStream.getStartOfFlacInFile()
              + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH
              + MetadataBlockHeader.BLOCK_TYPE_LENGTH;
      rafTemp.seek(uptoStreamHeaderSize);
      raf.seek(uptoStreamHeaderSize);

      rafTemp
          .getChannel()
          .transferFrom(
              raf.getChannel(),
              uptoStreamHeaderSize,
              MetadataBlockHeader.BLOCK_LENGTH
                  + MetadataBlockDataStreamInfo.STREAM_INFO_DATA_LENGTH);

      int dataStartSize =
          flacStream.getStartOfFlacInFile()
              + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH
              + MetadataBlockHeader.HEADER_LENGTH
              + MetadataBlockDataStreamInfo.STREAM_INFO_DATA_LENGTH;
      rafTemp.seek(dataStartSize);

      // Write all the metadatablocks
      for (MetadataBlock aMetadataBlockApplication : blockInfo.metadataBlockApplication) {
        rafTemp.write(aMetadataBlockApplication.getHeader().getBytesWithoutIsLastBlockFlag());
        rafTemp.write(aMetadataBlockApplication.getData().getBytes());
      }

      for (MetadataBlock aMetadataBlockSeekTable : blockInfo.metadataBlockSeekTable) {
        rafTemp.write(aMetadataBlockSeekTable.getHeader().getBytesWithoutIsLastBlockFlag());
        rafTemp.write(aMetadataBlockSeekTable.getData().getBytes());
      }

      for (MetadataBlock aMetadataBlockCueSheet : blockInfo.metadataBlockCueSheet) {
        rafTemp.write(aMetadataBlockCueSheet.getHeader().getBytesWithoutIsLastBlockFlag());
        rafTemp.write(aMetadataBlockCueSheet.getData().getBytes());
      }

      // Write tag data use default padding
      rafTemp.write(tc.convert(tag, FlacTagCreator.DEFAULT_PADDING).array());
      // Write audio to new file
      raf.seek(dataStartSize + availableRoom);

      // Issue #385
      // Transfer 'size' bytes from raf at its current position to rafTemp at position but do it in
      // batches
      // to prevent OutOfMemory exceptions
      long amountToBeWritten = raf.getChannel().size() - raf.getChannel().position();
      long written = 0;
      long chunksize = TagOptionSingleton.getInstance().getWriteChunkSize();
      long count = amountToBeWritten / chunksize;
      long mod = amountToBeWritten % chunksize;
      for (int i = 0; i < count; i++) {
        written +=
            rafTemp
                .getChannel()
                .transferFrom(raf.getChannel(), rafTemp.getChannel().position(), chunksize);
        rafTemp.getChannel().position(rafTemp.getChannel().position() + chunksize);
      }
      written +=
          rafTemp.getChannel().transferFrom(raf.getChannel(), rafTemp.getChannel().position(), mod);
      if (written != amountToBeWritten) {
        throw new CannotWriteException(
            "Was meant to write "
                + amountToBeWritten
                + " bytes but only written "
                + written
                + " bytes");
      }
    }
  }