/*
 * Copyright (c) 2002-2011 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.util.io;

import com.jscape.util.ArrayTools;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import static com.jscape.util.Parameters.*;

/**
 * @author Alex Usun
 */
public class ByteArrayOutputStream
        extends OutputStream {

    public static final int DEFAULT_SIZE = 128;

    private byte[] buffer;
    private int position;

    public ByteArrayOutputStream(byte[] buffer, int position) {
        assertNotNull(buffer);
        this.buffer = buffer;

        assertInRangeInclusive(position, 0, buffer.length, "Bad position.");
        this.position = position;
    }

    public ByteArrayOutputStream(byte[] buffer) {
        this(buffer, 0);
    }

    public ByteArrayOutputStream(int size) {
        this(new byte[size]);
    }

    public ByteArrayOutputStream() {
        this(DEFAULT_SIZE);
    }

    public ByteArrayOutputStream put(byte[] buffer, int offset) {
        this.buffer = buffer;
        this.position = offset;
        return this;
    }

    public void write(int data) {
        int newPosition = this.position + 1;
        adjustBuffer(newPosition);
        this.buffer[this.position] = (byte) data;
        this.position = newPosition;
    }

    public void write(byte[] data, int offset, int length) {
        int newPosition = this.position + length;
        adjustBuffer(newPosition);
        System.arraycopy(data, offset, this.buffer, this.position, length);
        this.position = newPosition;
    }

    public void close() {
        // empty
    }

    public byte[] buffer() {
        return this.buffer;
    }

    public int position() {
        return this.position;
    }

    public ByteArrayOutputStream position(int value) {
        assertGreaterOrEqual(value, 0, "Bad position.");
        adjustBuffer(value);
        this.position = value;

        return this;
    }

    public ByteArrayOutputStream relocate(int startPosition) {
        if (startPosition == 0) {
            return this;
        }

        int length = this.position - startPosition;

        if (length == 0) {
            reset();
        } else {
            ArrayTools.copy(this.buffer, startPosition, this.buffer, 0, length);
            this.position = length;
        }

        return this;
    }

    public ByteArrayOutputStream reset() {
        this.position = 0;

        return this;
    }

    public void writeTo(OutputStream out)
            throws IOException {
        out.write(this.buffer, 0, this.position);
    }

    public byte toByteArray()[] {
        return ArrayTools.copy(this.buffer, 0, this.position);
    }

    public boolean isEmpty() {
        return this.position == 0;
    }

    public String toString(Charset charset)
            throws UnsupportedEncodingException {
        return new String(this.buffer, 0, this.position, charset);
    }

    @Override
    public String toString() {
        return "ByteArrayOutputStream {"
                + "buffer.length=" + this.buffer.length
                + ", position=" + this.position
                + '}';
    }

    private void adjustBuffer(int requiredLength) {
        if (requiredLength > this.buffer.length) {
            int newLength = Math.max(this.buffer.length * 2, requiredLength);
            this.buffer = ArrayTools.copy(this.buffer, 0, new byte[newLength], 0, this.position);
        }
    }
}