/*
 * Copyright 2009-2010 Yuichiro Moriguchi
 *
 * 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 net.morilib.awk.bytes;

import java.io.IOException;
import java.util.Collection;
import java.util.RandomAccess;

import net.morilib.awk.misc.Hashes;

/**
 * byte型の配列によるベクタを表します。
 * 
 * @author MORIGUCHI, Yuichiro 2010/10/11
 */
public class ByteArrayVector extends AbstractByteVector
implements RandomAccess, java.io.Serializable {

	//
	private static final long serialVersionUID = 3332872309405682099L;

	//
	private byte[] array;
	private transient int size;

	/**
	 * ベクタを生成します。
	 * 
	 * @param initialCapacity 初期容量
	 */
	public ByteArrayVector(int initialCapacity) {
		super();
		if(initialCapacity < 0) {
			throw new IllegalArgumentException();
		}
		array = new byte[initialCapacity];
	}

	/**
	 * ベクタを生成します。
	 */
	public ByteArrayVector() {
		this(10);
	}

	/**
	 * 与えられた配列を持つベクタを生成します。
	 * 
	 * @param bytes 値
	 */
	public ByteArrayVector(byte[] bytes) {
		array = new byte[bytes.length];
		size  = array.length;
		System.arraycopy(bytes, 0, array, 0, bytes.length);
	}

	/**
	 * 与えられたコレクションの要素を持つベクタを生成します。
	 * 
	 * @param a コレクション
	 */
	public ByteArrayVector(ByteCollection a) {
		array = a.toByteArray();
		size  = array.length;
	}

	/**
	 * 与えられたコレクションの要素を持つベクタを生成します。
	 * 
	 * @param as コレクション
	 */
	public ByteArrayVector(ByteCollection... as) {
		size = 0;
		for(ByteCollection a : as) {
			size  += a.size();
		}

		int s2 = 0;
		array = new byte[size];
		for(ByteCollection a : as) {
			byte[] b = a.toByteArray();

			System.arraycopy(b, 0, array, s2, b.length);
			s2 += a.size();
		}
	}

	/**
	 * 与えられたコレクションの要素を持つベクタを生成します。
	 * 
	 * @param as コレクション
	 */
	public ByteArrayVector(
			Collection<? extends ByteCollection> as) {
		size = 0;
		for(ByteCollection a : as) {
			size  += a.size();
		}

		int s2 = 0;
		array = new byte[size];
		for(ByteCollection a : as) {
			byte[] b = a.toByteArray();

			System.arraycopy(b, 0, array, s2, b.length);
			s2 += a.size();
		}
	}

	/**
	 * 容量が与えられた値を持つことを保障させます。
	 * 
	 * @param nsize 容量
	 */
	public void ensureCapacity(int nsize) {
		if(nsize > array.length) {
			int ns = nsize;
			byte[] b;

			while(ns < array.length) {
				ns = (array.length < 1288490186) ?
						(array.length / 3 * 5) + 1 : Integer.MAX_VALUE;
			}
			b = new byte[ns];
			System.arraycopy(array, 0, b, 0, size);
			array = b;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteList#add(int, byte)
	 */
	public void addByte(int index, byte v) {
		if(index > size || index < 0) {
			throw new IndexOutOfBoundsException();
		}

		ensureCapacity(size + 1);
		modCount++;
		if(index < size) {
			System.arraycopy(
					array, index, array, index + 1, size - index);
		}
		array[index] = v;
		size++;
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteList#addAll(int, net.morilib.util.primitive.ByteCollection)
	 */
	public boolean addAllByte(int index, ByteCollection a) {
		if(index > size || index < 0) {
			throw new IndexOutOfBoundsException();
		}

		modCount++;
		if(a.isEmpty()) {
			return false;
		} else {
			byte[] b = a.toByteArray();

			ensureCapacity(size + b.length);
			System.arraycopy(
					array, index, array, index + b.length,
					size - index);
			System.arraycopy(b, 0, array, index, b.length);
			size += b.length;
			return true;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteList#get(int)
	 */
	public byte getByte(int index) {
		if(index >= size || index < 0) {
			throw new IndexOutOfBoundsException();
		}
		return array[index];
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteList#remove(int)
	 */
	public byte removeAt(int index) {
		if(index >= size || index < 0) {
			throw new IndexOutOfBoundsException();
		}

		byte res = array[index];
		modCount++;
		if(index < size - 1) {
			System.arraycopy(
					array, index + 1, array, index,
					size - index - 1);
		}
		size--;
		return res;
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteList#set(int, byte)
	 */
	public byte setByte(int index, byte v) {
		if(index >= size || index < 0) {
			throw new IndexOutOfBoundsException();
		}

		byte res = array[index];
		modCount++;
		array[index] = v;
		return res;
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteCollection#addAll(net.morilib.util.primitive.ByteCollection)
	 */
	public boolean addAllByte(ByteCollection a) {
		byte[] b = a.toByteArray();

		modCount++;
		ensureCapacity(size + b.length);
		System.arraycopy(b, 0, array, size, b.length);
		size += b.length;
		return true;
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteCollection#addAll(net.morilib.util.primitive.ByteCollection[])
	 */
	public boolean addAllByte(ByteCollection... as) {
		int nsize = 0;
		for(ByteCollection a : as) {
			nsize  += a.size();
		}

		if(nsize > 0) {
			int s2 = size;

			modCount++;
			ensureCapacity(size + nsize);
			for(ByteCollection a : as) {
				byte[] b = a.toByteArray();

				System.arraycopy(b, 0, array, s2, b.length);
				s2 += a.size();
			}
			size += nsize;
			return true;
		} else {
			return false;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteCollection#addAll(java.util.Iterator)
	 */
	public boolean addAllByte(Collection<? extends ByteCollection> as) {
		int nsize = 0;
		for(ByteCollection a : as) {
			nsize  += a.size();
		}

		if(nsize > 0) {
			int s2 = size;

			modCount++;
			ensureCapacity(size + nsize);
			for(ByteCollection a : as) {
				byte[] b = a.toByteArray();

				System.arraycopy(b, 0, array, s2, b.length);
				s2 += a.size();
			}
			size += nsize;
			return true;
		} else {
			return false;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteCollection#addAll(net.morilib.util.primitive.ByteCollection)
	 */
	public boolean addAllByte(byte[] b) {
		modCount++;
		ensureCapacity(size + b.length);
		System.arraycopy(b, 0, array, size, b.length);
		size += b.length;
		return true;
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteList#addAll(int, net.morilib.util.primitive.ByteCollection)
	 */
	public boolean addAllByte(int index, byte[] b) {
		if(index > size || index < 0) {
			throw new IndexOutOfBoundsException();
		}

		modCount++;
		if(b.length == 0) {
			return false;
		} else {
			ensureCapacity(size + b.length);
			System.arraycopy(
					array, index, array, index + b.length,
					size - index);
			System.arraycopy(b, 0, array, index, b.length);
			size += b.length;
			return true;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteCollection#clear()
	 */
	public void clear() {
		modCount++;
		size = 0;
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteCollection#size()
	 */
	public int size() {
		return size;
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteCollection#toArray()
	 */
	public byte[] toByteArray() {
		byte[] b = new byte[size];

		System.arraycopy(array, 0, b, 0, size);
		return b;
	}

	/* (non-Javadoc)
	 * @see net.morilib.util.primitive.ByteCollection#toArray(byte[])
	 */
	public byte[] toByteArray(byte[] a) {
		if(a.length < size) {
			return toByteArray();
		}
		System.arraycopy(array, 0, a, 0, size);
		return a;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	public int hashCode() {
		return Hashes.sumHashCode(array);
	}

	//
	private void writeObject(
			java.io.ObjectOutputStream s) throws IOException {
		s.defaultWriteObject();
	}

	//
	private void readObject(
			java.io.ObjectInputStream s
			) throws IOException, ClassNotFoundException {
		s.defaultReadObject();
		size = array.length;
	}

}
