package com.simpleteam.coder;

import com.simpleteam.*;

/**
 * <p>Title: SimpleTeam</p>
 * <p>Description: Base64 Coder</p>
 * <p>Copyright: All rights reserved by www.simpleteam.com. Copyright (c) 2003 ~ 2006</p>
 * <p>Company: www.simpleteam.com</p>
 * @author forest_luo@simpleteam.com
 * @version 1.1.2
 */

public class Base64Coder
{
	//Base64 Alphabet
	private final static String base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	//Masks
	private final static int[] mask = {0,1,3,7,15,31,63,127,255};

	//Type of Coder
	public final static String TYPE                         = "B";

	/**
	 * Encode.
	 *
	 * @param index Value for encoding.
	 * @return
	 *     <p>Encoded value.</p>
	 */
	private static char encode(int index)
	{
		//Check index.
		if(index >= 0 && index <= 63)
		{
			//Return char.
			return base64Alphabet.charAt(index);
		}
		//Return -1.
		return (char)-1;
	}

	/**
	 * Decode.
	 *
	 * @param value Encoded value.
	 * @return
	 *     <p>Value for decoding.</p>
	 */
	private static int decode(char value)
	{
		//Check value in table.
		for(int i = 0;i < 64;i ++)
		{
			//Check value.
			if(base64Alphabet.charAt(i) == value) return i;
		}
		//Return -1.
		return -1;
	}

	/**
	 * Make base64 encode.
	 *
	 * @param bytes Bytes for encoding.
	 * @return
	 *     <p>Encoded bytes.</p>
	 */
	public static String encode(byte[] bytes)
	{
		//Calculate bytes needed.
		int bytesNeeded = bytes.length * 8 / 6 + (((bytes.length * 8 % 6) == 0) ? 0 : 1);
		//System.out.println("Base64Coder.encode : " + bytesNeeded + " bytes needed !");
		int bytesPatched = ((bytesNeeded % 4) != 0) ? (4 - bytesNeeded % 4) : 0;

		//Create buffer.
		StringBuffer buffer = new StringBuffer();
		//Encode bytes.
		for(int i = 0;i < bytesNeeded;i ++)
		{
			//Get index.
			int index = i * 6 / 8;
			//Get offset.
			int offset = i * 6 % 8;
			//System.out.println("Base64Coder.encode : index=" + index + ",offset=" + offset);

			int bitsLeft = (offset > 0) ? (8 - offset) : 0;
			//System.out.println("Base64Coder.encode : bitsLeft=" + bitsLeft);
			int bitsNeeded = (bitsLeft > 0) ? (6 - bitsLeft) : 6;
			//System.out.println("Base64Coder.encode : nBitsNeed=" + bitsNeeded);

			int codeIndex = 0;

			//Check left bits.
			if(bitsLeft > 0)
			{
				//Get index of code.
				codeIndex = ((int)bytes[index]) & mask[bitsLeft];
				//Shift index.
				codeIndex <<= bitsNeeded;
			}

			//Check needed bits.
			if(bitsNeeded > 0)
			{
				//Get bits of shift.
				int bitsShift = 8 - bitsNeeded;
				//Calculate value.
				int value = 0;
				index += (offset <= 2) ? 0 : 1;
				if(index < bytes.length)
				{
					value = bytes[index];
				}
				//Get index of code.
				codeIndex |= (value >> bitsShift) & mask[bitsNeeded];
			}

			//System.out.println("Base64Coder.encode : codeIndex=" + codeIndex);
			buffer.append(encode(codeIndex));
			//System.out.println("Base64Coder.encode : encode=" + (char)temp[i]);
		}
		//Patch bytes.
		for(int i = 0;i < bytesPatched;i ++) buffer.append('=');
		//Return encoded.
		return buffer.toString();
	}

	/**
	 * Make base64 decode bytes.
	 *
	 * @param encoded Encoded bytes.
	 * @return
	 *     <p>Decoded bytes.</p>
	 */
	public static byte[] decode(String encoded)
	{
		//Patched bytes.
		int bytesPatched = 0;
		//Find patched bytes.
		for(int i = encoded.length();i > 0;i --)
		{
			//Check byte.
			if(encoded.charAt(i - 1) != '=') break;
			//Add bytes pathced.
			bytesPatched ++;
		}

		int bytesNeeded = (encoded.length() - bytesPatched) * 6 / 8;
		//System.out.println("Base64Coder.encode : " + bytesNeeded + " bytes needed !");

		//Create decoded bytes.
		byte[] decoded = new byte[bytesNeeded];
		//Decode bytes.
		for(int i = 0;i < bytesNeeded;i ++)
		{
			int index = i * 8 / 6;
			int offset = i * 8 % 6;
			//System.out.println("Base64Coder.encode : index=" + index + ",offset=" + offset);

			int bitsLeft = (offset > 0) ? (6 - offset) : 6;
			//System.out.println("Base64Coder.encode : bitsLeft=" + bitsLeft);
			int bitsNeeded = (bitsLeft > 0) ? (8 - bitsLeft) : 8;
			//System.out.println("Base64Coder.encode : nBitsNeed=" + bitsNeeded);

			int value = 0;

			//Check left bits.
			if(bitsLeft > 0)
			{
				//Get value.
				value = decode(encoded.charAt(index)) & mask[bitsLeft];
				//Shift value.
				value <<= bitsNeeded;
			}

			//Check needed bits.
			if(bitsNeeded > 0)
			{
				//Get bits of shift.
				int bitsShift = 6 - bitsNeeded;
				//Get byte value.
				int byteValue = decode(encoded.charAt(index + 1));
				//Get value.
				value |= (byteValue >> bitsShift) & mask[bitsNeeded];
			}
			//Set decoded byte.
			decoded[i] = (byte)(value & 0xff);
		}
		//Return decoded bytes.
		return decoded;
	}

	/**
	 * Main.
	 *
	 * @param args Arguments.
	 * @return
	 *     <p>No results returned.</p>
	 */
	public static void main(String[] args)
	{
		//Check arguments.
		if(args.length != 1)
		{
			//Print.
			System.out.println("Usage : java Base64Coder [base64 coded string]");
			return;
		}
		//Print encoded string.
		System.out.println("encoded string : " + args[0]);

		try
		{
			//Get bytes.
			byte[] bytes = Base64Coder.decode(args[0]);
			//Print bytes.
			System.out.println("decoded bytes : 0x" + Hex.toString(bytes));
			//Print.
			System.out.println("original bytes : 0x" + Hex.toString("ÂÞˆÒ".getBytes("gb2312")));
			//Print.
			System.out.println("decoded string : " + new String(bytes,"gb2312"));
		}
		catch(Exception e)
		{
			System.out.println("Base64Coder.main : " + e.getMessage());
			System.out.println("Base64Coder.main : unexpected exit !");
		}
	}
}

