package j3deditor.bin.util;

import java.nio.charset.Charset;

import java.util.BitSet;


/**
 * Tool for converting numbers to byte
 * arrays and vice versa.
 *
 * @author Risto Seene
 */
public class ByteConverter{

	/**
	 * Encodes the given int into a sequence of bytes.
	 *
	 * @param nr
	 *	integer to be encoded
	 * @return Returns bytes of the given int
	 */
	private static byte[] toBytes(int nr){
		byte[] bytes = new byte[4];
		bytes[0] = (byte)((nr & 0x000000ff) >> 0);
		bytes[1] = (byte)((nr & 0x0000ff00) >> 8);
		bytes[2] = (byte)((nr & 0x00ff0000) >> 16);
		bytes[3] = (byte)((nr & 0xff000000) >> 24);

		return bytes;
	}


	/**
	 * Encodes the given int into a sequence of bytes.
	 *
	 * @param nr
	 *	integer to be encoded
	 * @return Returns bytes of the given int
	 */
	public static byte[] getBytes(int nr){
		return toBytes(nr);
	}
	
	
	/**
	 * Encodes the given float into a sequence of bytes.
	 *
	 * @param nr
	 *        float to be encoded
	 * @return Returns bytes of the given float
	 */
	public static byte[] getBytes(float nr){
		return toBytes(Float.floatToIntBits(nr));
	}
	
	
	/**
	 * Encodes the given String into a sequence of bytes using the "utf-8" charset.
	 * 
	 * @param s
	 * 	string to be encoded
	 * @return Returns bytes of the given String
	 */
	public static byte[] getBytes(String s){
		try{
			return s.getBytes("utf-8");
		} catch(Throwable t){
			Utilities.out.throwError(t);
			return new byte[0];
		}
	}


	/**
	 * Decodes the specified array of bytes to int.
	 *
	 * @param bytes
	 *	byte array to convert
	 * @return Returns integer converted from bytes
	 *
	 * @exception Exception
	 * 	IllegalArgumentException if the length of the given array is not equal to 4,<br/>
	 * 	NullPointerException if the given array is <code>null</code>
	 */
	public static int toInt(byte[] bytes) throws Exception{
		if(bytes == null){
			throw new NullPointerException("Argument can not be null");
		}
		if(bytes.length != 4){
			throw new IllegalArgumentException("Invalid number of bytes!");
		}

		int nr = ((bytes[0] & 0xff) << 0) | ((bytes[1] & 0xff) << 8) | ((bytes[2] & 0xff) << 16) | ((bytes[3] & 0xff) << 24);

		return nr;
	}


	/**
	 * Decodes the specified array of bytes to float.
	 *
	 * @param bytes
	 *	byte array to convert
	 * @return Returns float converted from bytes
	 *
	 * @exception Exception
	 * 	IllegalArgumentException if the length of the given array is not equal to 4,<br/>
	 * 	NullPointerException if the given array is <code>null</code>
	 */
	public static float toFloat(byte[] bytes) throws Exception{
		return Float.intBitsToFloat(toInt(bytes));
	}
	
	
	/**
	 * Decodes the specified array of bytes to String using "utf-8" charset.
	 * 
	 * @param bytes
	 * 	array of bytes to be decoded
	 * @return Returns the decoded String
	 * @throws Exception
	 * 	if unable to decode the given array
	 */
	public static String toString(byte[] bytes) throws Exception{
		return new String(bytes, Charset.forName("utf-8"));
	}


	/**
	 * Returns the bits of the given array of bytes.
	 * 
	 * @param bytes
	 * 	bytes which bits will be returned
	 * @return Returns the bits of the given array of bytes
	 */
	public static BitSet toBits(byte[] bytes){
		BitSet bits = new BitSet(bytes.length * 8);
		for(int i = 0; i < bytes.length * 8; i++){
			if((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0){
				bits.set(i);
			}
		}
		return bits;
	}


	/**
	 * Returns the given bits as the array of bytes.
	 * 
	 * @param bits
	 * 	bits that will be converted to an array of bytes
	 * @return Returns the given bits as the array of bytes
	 */
	public static byte[] toBytes(BitSet bits){
		byte[] bytes = new byte[bits.length() / 8 + 1];
		for(int i = 0; i < bits.length(); i++){
			if(bits.get(i)){
				bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
			}
		}
		return bytes;
	}


	/**
	 * Tests if the given flag is present.
	 * 
	 * @param allFlags
	 * 	flags that are bitwise OR'ed together
	 * @param theFlag
	 * 	wanted flag
	 * @return Returns <code>true</code> if the given flag is set, 
	 * 	otherwise <code>false</code>
	 */
	public static boolean isFlagSet(int allFlags, int theFlag){
		BitSet flags = toBits(toBytes(allFlags));
		BitSet flag = toBits(toBytes(theFlag));
		
		return flags.get(flag.nextSetBit(0));
	}


}
