// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb5.lib;

import java.util.BitSet;
import krb5.lib.asn1.*;
import krb5.lib.KerberosTime;

/**
 * Reverse ordered BitSet for ASN.1 representation
 */
public class BitString implements Cloneable {

	/**
	 * Bits
	 */
	private BitSet bits;

	/**
	 * Size
	 */
	private int size;

	/**
	 * Class constructor
	 */
	private BitString() {
	}

	/**
	 * Class constructor
	 *
	 * @param i is of type int
	 */
	public BitString(int i) {
		bits = new BitSet(i);
		size = i;
	}

	/**
	 * Clones this object
	 *
	 * @return Object is a result
	 */
	public Object clone() {
	    BitString new_bitString = null;
	    try {
		    new_bitString = (BitString)super.clone();
		    new_bitString.size = size;
		    if (bits != null) {
			    new_bitString.bits = (BitSet)bits.clone();
		    }
		} catch (CloneNotSupportedException e) {
		    new_bitString = null;
		}
		return new_bitString;
	}

	/**
	 * Class constructor
	 *
	 * @param data is of type byte[]
	 * @param new_size is of type int
	 * @exception BitStringException an exception
	 * @see krb5.lib.BitStringException
	 */
	public BitString(byte[] data, int new_size) throws BitStringException {
		if (new_size / 8 < data.length - 1 ||
			new_size / 8 > data.length)
			throw new BitStringException(Krb5.BITSTRING_SIZE_INVALID);
		bits = new BitSet(new_size);
		size = new_size;
		for (int i = 0; i < size; i++) {
			if (((data[i / 8] >>> (7 - (i % 8))) & 1) == 1) {
				bits.set(size - 1 - i);
			}
		}
	}

	/**
	 * Class constructor
	 *
	 * @param ref is of type EncodeRef
	 * @exception Asn1Exception an exception
	 * @exception BitStringException an exception
	 * @see krb5.lib.asn1.EncodeRef
	 * @see krb5.lib.Asn1Exception
	 * @see krb5.lib.BitStringException
	 */
	public BitString(EncodeRef ref) throws Asn1Exception,
		BitStringException {
		this(decode.BitString(ref.data, ref.index),
			decode.BitStringSize(ref.data, ref.index));
	}

	/**
	 * Clears a bit
	 *
	 * @param bit is of type int
	 * @exception BitStringException an exception
	 * @see krb5.lib.BitStringException
	 */
	public void clear(int  bit) throws BitStringException {
		if (bit < 0 || bit > size - 1)
			throw new BitStringException(Krb5.BITSTRING_INDEX_OUT_OF_BOUNDS);
		bits.clear(size - 1 - bit);
	}

	/**
	 * Gets a bit
	 *
	 * @return boolean is a result
	 * @param bit is of type int
	 * @exception BitStringException an exception
	 * @see krb5.lib.BitStringException
	 */
    public boolean get(int  bit) throws BitStringException {
		if (bit < 0 || bit > size - 1)
			throw new BitStringException(Krb5.BITSTRING_INDEX_OUT_OF_BOUNDS);
		return bits.get(size - 1 - bit);
	}

	/**
	 * Sets a bit
	 *
	 * @param bit is of type int
	 * @exception BitStringException an exception
	 * @see krb5.lib.BitStringException
	 */
    public void set(int  bit) throws BitStringException {
		if (bit < 0 || bit > size - 1)
			throw new BitStringException(Krb5.BITSTRING_INDEX_OUT_OF_BOUNDS);
		bits.set(size - 1 - bit);
	}

	/**
	 * Returns size of the BitString
	 *
	 * @return int is a result
	 */
    public int size() {
		return size;
	}

	/**
	 * Matches the 2 BitString
	 *
	 * @return boolean is a result
	 * @param other is of type BitString
	 * @param bit is of type int
	 * @exception BitStringException an exception
	 * @see krb5.lib.BitString
	 * @see krb5.lib.BitStringException
	 */
    public boolean match(BitString other, int  bit) throws BitStringException {
		return !(get(bit) ^ other.get(bit));
	}

	/**
	 * Converts to bytes
	 *
	 * @return byte[] is a result
	 * @exception BitStringException an exception
	 * @see krb5.lib.BitStringException
	 */
	public byte[] toBytes() throws BitStringException {
		byte[] result = new byte[size / 8 + (size % 8 == 0 ? 0 : 1)];
		for (int i = 0; i < size; i++) {
			if (bits.get(size - 1 - i)) {
				result[i / 8] |= 1 << (7 - (i % 8));
			}
		}
		return result;
	}

	/**
	 * Encodes the object in asn1
	 *
	 * @return byte[] is a result
	 * @exception Asn1Exception an exception
	 * @exception BitStringException an exception
	 * @see krb5.lib.Asn1Exception
	 * @see krb5.lib.BitStringException
	 */
	public byte[] asn1Encode() throws Asn1Exception,
		BitStringException {
		return encode.BitString(toBytes(), size);
	}

	//XXX should be moved to krb5.lib.util class
	/**
	 * Pads with zero
	 *
	 * @return String is a result
	 * @param s is of type String
	 * @param length is of type int
	 */
	public static String zeroPadWithTrunc(String s, int length) {
		StringBuffer temp = new StringBuffer(
			s.substring(
				(s.length() >= length ? s.length() - length : 0)));
		while (temp.length() < length)
			temp.insert(0, '0');
		return temp.toString();
	}

	/**
	 * Main section
	 *
	 * @param args is of type String[]
	 * @exception BitStringException an exception
	 * @see krb5.lib.BitStringException
	 */
	public static void main(String[] args) throws BitStringException {
		BitString b = new BitString(32);
		b.set(31);
		byte[] ba = b.toBytes();
		System.out.println("b.size=" + b.size());
		System.out.println("ba.length=" + ba.length);
		for (int i = 0; i < ba.length; i++)
			System.out.println("ba[" + i + "]=0x" +
				zeroPadWithTrunc(Integer.toHexString(ba[i]), 2));
		BitString c = new BitString(b.toBytes(), b.size());
		byte[] ca = c.toBytes();
		System.out.println("c.size=" + c.size());
		System.out.println("ca.length=" + ca.length);
		for (int i = 0; i < ca.length; i++)
			System.out.println("ca[" + i + "]=0x" +
				zeroPadWithTrunc(Integer.toHexString(ca[i]), 2));
	}

}