/*
 * Copyright (C) 2014 Square, Inc.
 *
 * 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 okio;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.junit.Test;

import static java.util.Arrays.asList;
import static okio.TestUtil.repeat;
import static okio.Util.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 * Tests solely for the behavior of Buffer's implementation. For generic BufferedSink or
 * BufferedSource behavior use BufferedSinkTest or BufferedSourceTest, respectively.
 */
public final class BufferTest {
  @Test public void readAndWriteUtf8() throws Exception {
    Buffer buffer = new Buffer();
    buffer.writeUtf8("ab");
    assertEquals(2, buffer.size());
    buffer.writeUtf8("cdef");
    assertEquals(6, buffer.size());
    assertEquals("abcd", buffer.readUtf8(4));
    assertEquals(2, buffer.size());
    assertEquals("ef", buffer.readUtf8(2));
    assertEquals(0, buffer.size());
    try {
      buffer.readUtf8(1);
      fail();
    } catch (ArrayIndexOutOfBoundsException expected) {
    }
  }

  @Test public void completeSegmentByteCountOnEmptyBuffer() throws Exception {
    Buffer buffer = new Buffer();
    assertEquals(0, buffer.completeSegmentByteCount());
  }

  @Test public void completeSegmentByteCountOnBufferWithFullSegments() throws Exception {
    Buffer buffer = new Buffer();
    buffer.writeUtf8(repeat('a', Segment.SIZE * 4));
    assertEquals(Segment.SIZE * 4, buffer.completeSegmentByteCount());
  }

  @Test public void completeSegmentByteCountOnBufferWithIncompleteTailSegment() throws Exception {
    Buffer buffer = new Buffer();
    buffer.writeUtf8(repeat('a', Segment.SIZE * 4 - 10));
    assertEquals(Segment.SIZE * 3, buffer.completeSegmentByteCount());
  }

  @Test public void toStringOnEmptyBuffer() throws Exception {
    Buffer buffer = new Buffer();
    assertEquals("Buffer[size=0]", buffer.toString());
  }

  @Test public void toStringOnSmallBufferIncludesContents() throws Exception {
    Buffer buffer = new Buffer();
    buffer.write(ByteString.decodeHex("a1b2c3d4e5f61a2b3c4d5e6f10203040"));
    assertEquals("Buffer[size=16 data=a1b2c3d4e5f61a2b3c4d5e6f10203040]", buffer.toString());
  }

  @Test public void toStringOnLargeBufferIncludesMd5() throws Exception {
    Buffer buffer = new Buffer();
    buffer.write(ByteString.encodeUtf8("12345678901234567"));
    assertEquals("Buffer[size=17 md5=2c9728a2138b2f25e9f89f99bdccf8db]", buffer.toString());
  }

  @Test public void toStringOnMultipleSegmentBuffer() throws Exception {
    Buffer buffer = new Buffer();
    buffer.writeUtf8(repeat('a', 6144));
    assertEquals("Buffer[size=6144 md5=d890021f28522533c1cc1b9b1f83ce73]", buffer.toString());
  }

  @Test public void multipleSegmentBuffers() throws Exception {
    Buffer buffer = new Buffer();
    buffer.writeUtf8(repeat('a', 1000));
    buffer.writeUtf8(repeat('b', 2500));
    buffer.writeUtf8(repeat('c', 5000));
    buffer.writeUtf8(repeat('d', 10000));
    buffer.writeUtf8(repeat('e', 25000));
    buffer.writeUtf8(repeat('f', 50000));

    assertEquals(repeat('a', 999), buffer.readUtf8(999)); // a...a
    assertEquals("a" + repeat('b', 2500) + "c", buffer.readUtf8(2502)); // ab...bc
    assertEquals(repeat('c', 4998), buffer.readUtf8(4998)); // c...c
    assertEquals("c" + repeat('d', 10000) + "e", buffer.readUtf8(10002)); // cd...de
    assertEquals(repeat('e', 24998), buffer.readUtf8(24998)); // e...e
    assertEquals("e" + repeat('f', 50000), buffer.readUtf8(50001)); // ef...f
    assertEquals(0, buffer.size());
  }

  @Test public void fillAndDrainPool() throws Exception {
    Buffer buffer = new Buffer();

    // Take 2 * MAX_SIZE segments. This will drain the pool, even if other tests filled it.
    buffer.write(new byte[(int) SegmentPool.MAX_SIZE]);
    buffer.write(new byte[(int) SegmentPool.MAX_SIZE]);
    assertEquals(0, SegmentPool.byteCount);

    // Recycle MAX_SIZE segments. They're all in the pool.
    buffer.readByteString(SegmentPool.MAX_SIZE);
    assertEquals(SegmentPool.MAX_SIZE, SegmentPool.byteCount);

    // Recycle MAX_SIZE more segments. The pool is full so they get garbage collected.
    buffer.readByteString(SegmentPool.MAX_SIZE);
    assertEquals(SegmentPool.MAX_SIZE, SegmentPool.byteCount);

    // Take MAX_SIZE segments to drain the pool.
    buffer.write(new byte[(int) SegmentPool.MAX_SIZE]);
    assertEquals(0, SegmentPool.byteCount);

    // Take MAX_SIZE more segments. The pool is drained so these will need to be allocated.
    buffer.write(new byte[(int) SegmentPool.MAX_SIZE]);
    assertEquals(0, SegmentPool.byteCount);
  }

  @Test public void moveBytesBetweenBuffersShareSegment() throws Exception {
    int size = (Segment.SIZE / 2) - 1;
    List<Integer> segmentSizes = moveBytesBetweenBuffers(repeat('a', size), repeat('b', size));
    assertEquals(asList(size * 2), segmentSizes);
  }

  @Test public void moveBytesBetweenBuffersReassignSegment() throws Exception {
    int size = (Segment.SIZE / 2) + 1;
    List<Integer> segmentSizes = moveBytesBetweenBuffers(repeat('a', size), repeat('b', size));
    assertEquals(asList(size, size), segmentSizes);
  }

  @Test public void moveBytesBetweenBuffersMultipleSegments() throws Exception {
    int size = 3 * Segment.SIZE + 1;
    List<Integer> segmentSizes = moveBytesBetweenBuffers(repeat('a', size), repeat('b', size));
    assertEquals(asList(Segment.SIZE, Segment.SIZE, Segment.SIZE, 1,
        Segment.SIZE, Segment.SIZE, Segment.SIZE, 1), segmentSizes);
  }

  private List<Integer> moveBytesBetweenBuffers(String... contents) throws IOException {
    StringBuilder expected = new StringBuilder();
    Buffer buffer = new Buffer();
    for (String s : contents) {
      Buffer source = new Buffer();
      source.writeUtf8(s);
      buffer.writeAll(source);
      expected.append(s);
    }
    List<Integer> segmentSizes = buffer.segmentSizes();
    assertEquals(expected.toString(), buffer.readUtf8(expected.length()));
    return segmentSizes;
  }

  /** The big part of source's first segment is being moved. */
  @Test public void writeSplitSourceBufferLeft() throws Exception {
    int writeSize = Segment.SIZE / 2 + 1;

    Buffer sink = new Buffer();
    sink.writeUtf8(repeat('b', Segment.SIZE - 10));

    Buffer source = new Buffer();
    source.writeUtf8(repeat('a', Segment.SIZE * 2));
    sink.write(source, writeSize);

    assertEquals(asList(Segment.SIZE - 10, writeSize), sink.segmentSizes());
    assertEquals(asList(Segment.SIZE - writeSize, Segment.SIZE), source.segmentSizes());
  }

  /** The big part of source's first segment is staying put. */
  @Test public void writeSplitSourceBufferRight() throws Exception {
    int writeSize = Segment.SIZE / 2 - 1;

    Buffer sink = new Buffer();
    sink.writeUtf8(repeat('b', Segment.SIZE - 10));

    Buffer source = new Buffer();
    source.writeUtf8(repeat('a', Segment.SIZE * 2));
    sink.write(source, writeSize);

    assertEquals(asList(Segment.SIZE - 10, writeSize), sink.segmentSizes());
    assertEquals(asList(Segment.SIZE - writeSize, Segment.SIZE), source.segmentSizes());
  }

  @Test public void writePrefixDoesntSplit() throws Exception {
    Buffer sink = new Buffer();
    sink.writeUtf8(repeat('b', 10));

    Buffer source = new Buffer();
    source.writeUtf8(repeat('a', Segment.SIZE * 2));
    sink.write(source, 20);

    assertEquals(asList(30), sink.segmentSizes());
    assertEquals(asList(Segment.SIZE - 20, Segment.SIZE), source.segmentSizes());
    assertEquals(30, sink.size());
    assertEquals(Segment.SIZE * 2 - 20, source.size());
  }

  @Test public void writePrefixDoesntSplitButRequiresCompact() throws Exception {
    Buffer sink = new Buffer();
    sink.writeUtf8(repeat('b', Segment.SIZE - 10)); // limit = size - 10
    sink.readUtf8(Segment.SIZE - 20); // pos = size = 20

    Buffer source = new Buffer();
    source.writeUtf8(repeat('a', Segment.SIZE * 2));
    sink.write(source, 20);

    assertEquals(asList(30), sink.segmentSizes());
    assertEquals(asList(Segment.SIZE - 20, Segment.SIZE), source.segmentSizes());
    assertEquals(30, sink.size());
    assertEquals(Segment.SIZE * 2 - 20, source.size());
  }

  @Test public void copyToSpanningSegments() throws Exception {
    Buffer source = new Buffer();
    source.writeUtf8(repeat('a', Segment.SIZE * 2));
    source.writeUtf8(repeat('b', Segment.SIZE * 2));

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    source.copyTo(out, 10, Segment.SIZE * 3);

    assertEquals(repeat('a', Segment.SIZE * 2 - 10) + repeat('b', Segment.SIZE + 10),
        out.toString());
    assertEquals(repeat('a', Segment.SIZE * 2) + repeat('b', Segment.SIZE * 2),
        source.readUtf8(Segment.SIZE * 4));
  }

  @Test public void copyToStream() throws Exception {
    Buffer buffer = new Buffer().writeUtf8("hello, world!");
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    buffer.copyTo(out);
    String outString = new String(out.toByteArray(), UTF_8);
    assertEquals("hello, world!", outString);
    assertEquals("hello, world!", buffer.readUtf8());
  }

  @Test public void writeToSpanningSegments() throws Exception {
    Buffer buffer = new Buffer();
    buffer.writeUtf8(repeat('a', Segment.SIZE * 2));
    buffer.writeUtf8(repeat('b', Segment.SIZE * 2));

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    buffer.skip(10);
    buffer.writeTo(out, Segment.SIZE * 3);

    assertEquals(repeat('a', Segment.SIZE * 2 - 10) + repeat('b', Segment.SIZE + 10),
        out.toString());
    assertEquals(repeat('b', Segment.SIZE - 10), buffer.readUtf8(buffer.size));
  }

  @Test public void writeToStream() throws Exception {
    Buffer buffer = new Buffer().writeUtf8("hello, world!");
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    buffer.writeTo(out);
    String outString = new String(out.toByteArray(), UTF_8);
    assertEquals("hello, world!", outString);
    assertEquals(0, buffer.size());
  }

  @Test public void readFromStream() throws Exception {
    InputStream in = new ByteArrayInputStream("hello, world!".getBytes(UTF_8));
    Buffer buffer = new Buffer();
    buffer.readFrom(in);
    String out = buffer.readUtf8();
    assertEquals("hello, world!", out);
  }

  @Test public void readFromSpanningSegments() throws Exception {
    InputStream in = new ByteArrayInputStream("hello, world!".getBytes(UTF_8));
    Buffer buffer = new Buffer().writeUtf8(repeat('a', Segment.SIZE - 10));
    buffer.readFrom(in);
    String out = buffer.readUtf8();
    assertEquals(repeat('a', Segment.SIZE - 10) + "hello, world!", out);
  }

  @Test public void readFromStreamWithCount() throws Exception {
    InputStream in = new ByteArrayInputStream("hello, world!".getBytes(UTF_8));
    Buffer buffer = new Buffer();
    buffer.readFrom(in, 10);
    String out = buffer.readUtf8();
    assertEquals("hello, wor", out);
  }

  @Test public void moveAllRequestedBytesWithRead() throws Exception {
    Buffer sink = new Buffer();
    sink.writeUtf8(repeat('a', 10));

    Buffer source = new Buffer();
    source.writeUtf8(repeat('b', 15));

    assertEquals(10, source.read(sink, 10));
    assertEquals(20, sink.size());
    assertEquals(5, source.size());
    assertEquals(repeat('a', 10) + repeat('b', 10), sink.readUtf8(20));
  }

  @Test public void moveFewerThanRequestedBytesWithRead() throws Exception {
    Buffer sink = new Buffer();
    sink.writeUtf8(repeat('a', 10));

    Buffer source = new Buffer();
    source.writeUtf8(repeat('b', 20));

    assertEquals(20, source.read(sink, 25));
    assertEquals(30, sink.size());
    assertEquals(0, source.size());
    assertEquals(repeat('a', 10) + repeat('b', 20), sink.readUtf8(30));
  }

  @Test public void indexOfWithOffset() throws Exception {
    Buffer buffer = new Buffer();
    int halfSegment = Segment.SIZE / 2;
    buffer.writeUtf8(repeat('a', halfSegment));
    buffer.writeUtf8(repeat('b', halfSegment));
    buffer.writeUtf8(repeat('c', halfSegment));
    buffer.writeUtf8(repeat('d', halfSegment));
    assertEquals(0, buffer.indexOf((byte) 'a', 0));
    assertEquals(halfSegment - 1, buffer.indexOf((byte) 'a', halfSegment - 1));
    assertEquals(halfSegment, buffer.indexOf((byte) 'b', halfSegment - 1));
    assertEquals(halfSegment * 2, buffer.indexOf((byte) 'c', halfSegment - 1));
    assertEquals(halfSegment * 3, buffer.indexOf((byte) 'd', halfSegment - 1));
    assertEquals(halfSegment * 3, buffer.indexOf((byte) 'd', halfSegment * 2));
    assertEquals(halfSegment * 3, buffer.indexOf((byte) 'd', halfSegment * 3));
    assertEquals(halfSegment * 4 - 1, buffer.indexOf((byte) 'd', halfSegment * 4 - 1));
  }

  @Test public void byteAt() throws Exception {
    Buffer buffer = new Buffer();
    buffer.writeUtf8("a");
    buffer.writeUtf8(repeat('b', Segment.SIZE));
    buffer.writeUtf8("c");
    assertEquals('a', buffer.getByte(0));
    assertEquals('a', buffer.getByte(0)); // getByte doesn't mutate!
    assertEquals('c', buffer.getByte(buffer.size - 1));
    assertEquals('b', buffer.getByte(buffer.size - 2));
    assertEquals('b', buffer.getByte(buffer.size - 3));
  }

  @Test public void getByteOfEmptyBuffer() throws Exception {
    Buffer buffer = new Buffer();
    try {
      buffer.getByte(0);
      fail();
    } catch (IndexOutOfBoundsException expected) {
    }
  }

  @Test public void writePrefixToEmptyBuffer() throws IOException {
    Buffer sink = new Buffer();
    Buffer source = new Buffer();
    source.writeUtf8("abcd");
    sink.write(source, 2);
    assertEquals("ab", sink.readUtf8(2));
  }

  @Test public void cloneDoesNotObserveWritesToOriginal() throws Exception {
    Buffer original = new Buffer();
    Buffer clone = original.clone();
    original.writeUtf8("abc");
    assertEquals(0, clone.size());
  }

  @Test public void cloneDoesNotObserveReadsFromOriginal() throws Exception {
    Buffer original = new Buffer();
    original.writeUtf8("abc");
    Buffer clone = original.clone();
    assertEquals("abc", original.readUtf8(3));
    assertEquals(3, clone.size());
    assertEquals("ab", clone.readUtf8(2));
  }

  @Test public void originalDoesNotObserveWritesToClone() throws Exception {
    Buffer original = new Buffer();
    Buffer clone = original.clone();
    clone.writeUtf8("abc");
    assertEquals(0, original.size());
  }

  @Test public void originalDoesNotObserveReadsFromClone() throws Exception {
    Buffer original = new Buffer();
    original.writeUtf8("abc");
    Buffer clone = original.clone();
    assertEquals("abc", clone.readUtf8(3));
    assertEquals(3, original.size());
    assertEquals("ab", original.readUtf8(2));
  }

  @Test public void cloneMultipleSegments() throws Exception {
    Buffer original = new Buffer();
    original.writeUtf8(repeat('a', Segment.SIZE * 3));
    Buffer clone = original.clone();
    original.writeUtf8(repeat('b', Segment.SIZE * 3));
    clone.writeUtf8(repeat('c', Segment.SIZE * 3));

    assertEquals(repeat('a', Segment.SIZE * 3) + repeat('b', Segment.SIZE * 3),
        original.readUtf8(Segment.SIZE * 6));
    assertEquals(repeat('a', Segment.SIZE * 3) + repeat('c', Segment.SIZE * 3),
        clone.readUtf8(Segment.SIZE * 6));
  }

  @Test public void equalsAndHashCodeEmpty() throws Exception {
    Buffer a = new Buffer();
    Buffer b = new Buffer();
    assertTrue(a.equals(b));
    assertTrue(a.hashCode() == b.hashCode());
  }

  @Test public void equalsAndHashCode() throws Exception {
    Buffer a = new Buffer().writeUtf8("dog");
    Buffer b = new Buffer().writeUtf8("hotdog");
    assertFalse(a.equals(b));
    assertFalse(a.hashCode() == b.hashCode());

    b.readUtf8(3); // Leaves b containing 'dog'.
    assertTrue(a.equals(b));
    assertTrue(a.hashCode() == b.hashCode());
  }

  @Test public void equalsAndHashCodeSpanningSegments() throws Exception {
    byte[] data = new byte[1024 * 1024];
    Random dice = new Random(0);
    dice.nextBytes(data);

    Buffer a = bufferWithRandomSegmentLayout(dice, data);
    Buffer b = bufferWithRandomSegmentLayout(dice, data);
    assertTrue(a.equals(b));
    assertTrue(a.hashCode() == b.hashCode());

    data[data.length / 2]++; // Change a single byte.
    Buffer c = bufferWithRandomSegmentLayout(dice, data);
    assertFalse(a.equals(c));
    assertFalse(a.hashCode() == c.hashCode());
  }

  @Test public void bufferInputStreamByteByByte() throws Exception {
    Buffer source = new Buffer();
    source.writeUtf8("abc");

    InputStream in = source.inputStream();
    assertEquals(3, in.available());
    assertEquals('a', in.read());
    assertEquals('b', in.read());
    assertEquals('c', in.read());
    assertEquals(-1, in.read());
    assertEquals(0, in.available());
  }

  @Test public void bufferInputStreamBulkReads() throws Exception {
    Buffer source = new Buffer();
    source.writeUtf8("abc");

    byte[] byteArray = new byte[4];

    Arrays.fill(byteArray, (byte) -5);
    InputStream in = source.inputStream();
    assertEquals(3, in.read(byteArray));
    assertEquals("[97, 98, 99, -5]", Arrays.toString(byteArray));

    Arrays.fill(byteArray, (byte) -7);
    assertEquals(-1, in.read(byteArray));
    assertEquals("[-7, -7, -7, -7]", Arrays.toString(byteArray));
  }

  /**
   * When writing data that's already buffered, there's no reason to page the
   * data by segment.
   */
  @Test public void readAllWritesAllSegmentsAtOnce() throws Exception {
    Buffer write1 = new Buffer().writeUtf8(""
        + TestUtil.repeat('a', Segment.SIZE)
        + TestUtil.repeat('b', Segment.SIZE)
        + TestUtil.repeat('c', Segment.SIZE));

    Buffer source = new Buffer().writeUtf8(""
        + TestUtil.repeat('a', Segment.SIZE)
        + TestUtil.repeat('b', Segment.SIZE)
        + TestUtil.repeat('c', Segment.SIZE));

    MockSink mockSink = new MockSink();

    assertEquals(Segment.SIZE * 3, source.readAll(mockSink));
    assertEquals(0, source.size());
    mockSink.assertLog("write(" + write1 + ", " + write1.size() + ")");
  }

  @Test public void writeAllMultipleSegments() throws Exception {
    Buffer source = new Buffer().writeUtf8(TestUtil.repeat('a', Segment.SIZE * 3));
    Buffer sink = new Buffer();

    assertEquals(Segment.SIZE * 3, sink.writeAll(source));
    assertEquals(0, source.size());
    assertEquals(TestUtil.repeat('a', Segment.SIZE * 3), sink.readUtf8());
  }

  @Test public void copyTo() throws Exception {
    Buffer source = new Buffer();
    source.writeUtf8("party");

    Buffer target = new Buffer();
    source.copyTo(target, 1, 3);

    assertEquals("art", target.readUtf8());
    assertEquals("party", source.readUtf8());
  }

  @Test public void copyToOnSegmentBoundary() throws Exception {
    String as = repeat('a', Segment.SIZE);
    String bs = repeat('b', Segment.SIZE);
    String cs = repeat('c', Segment.SIZE);
    String ds = repeat('d', Segment.SIZE);

    Buffer source = new Buffer();
    source.writeUtf8(as);
    source.writeUtf8(bs);
    source.writeUtf8(cs);

    Buffer target = new Buffer();
    target.writeUtf8(ds);

    source.copyTo(target, as.length(), bs.length() + cs.length());
    assertEquals(ds + bs + cs, target.readUtf8());
  }

  @Test public void copyToOffSegmentBoundary() throws Exception {
    String as = repeat('a', Segment.SIZE - 1);
    String bs = repeat('b', Segment.SIZE + 2);
    String cs = repeat('c', Segment.SIZE - 4);
    String ds = repeat('d', Segment.SIZE + 8);

    Buffer source = new Buffer();
    source.writeUtf8(as);
    source.writeUtf8(bs);
    source.writeUtf8(cs);

    Buffer target = new Buffer();
    target.writeUtf8(ds);

    source.copyTo(target, as.length(), bs.length() + cs.length());
    assertEquals(ds + bs + cs, target.readUtf8());
  }

  @Test public void copyToSourceAndTargetCanBeTheSame() throws Exception {
    String as = repeat('a', Segment.SIZE);
    String bs = repeat('b', Segment.SIZE);

    Buffer source = new Buffer();
    source.writeUtf8(as);
    source.writeUtf8(bs);

    source.copyTo(source, 0, source.size());
    assertEquals(as + bs + as + bs, source.readUtf8());
  }

  @Test public void copyToEmptySource() throws Exception {
    Buffer source = new Buffer();
    Buffer target = new Buffer().writeUtf8("aaa");
    source.copyTo(target, 0L, 0L);
    assertEquals("", source.readUtf8());
    assertEquals("aaa", target.readUtf8());
  }

  @Test public void copyToEmptyTarget() throws Exception {
    Buffer source = new Buffer().writeUtf8("aaa");
    Buffer target = new Buffer();
    source.copyTo(target, 0L, 3L);
    assertEquals("aaa", source.readUtf8());
    assertEquals("aaa", target.readUtf8());
  }

  /**
   * Returns a new buffer containing the data in {@code data}, and a segment
   * layout determined by {@code dice}.
   */
  private Buffer bufferWithRandomSegmentLayout(Random dice, byte[] data) throws IOException {
    Buffer result = new Buffer();

    // Writing to result directly will yield packed segments. Instead, write to
    // other buffers, then write those buffers to result.
    for (int pos = 0, byteCount; pos < data.length; pos += byteCount) {
      byteCount = (Segment.SIZE / 2) + dice.nextInt(Segment.SIZE / 2);
      if (byteCount > data.length - pos) byteCount = data.length - pos;
      int offset = dice.nextInt(Segment.SIZE - byteCount);

      Buffer segment = new Buffer();
      segment.write(new byte[offset]);
      segment.write(data, pos, byteCount);
      segment.skip(offset);

      result.write(segment, byteCount);
    }

    return result;
  }
}