/*
 * Java Base64 - A pure Java library for reading and writing Base64
 *               encoded streams.
 * 
 * Copyright (C) 2007-2009 Carlo Pelliccia (www.sauronsoftware.it)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version
 * 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License version 2.1 along with this program.
 * If not, see <http://www.gnu.org/licenses/>.
 */
package com.mjitech.qa.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;


/**
 * <p>
 * Base64 encoding and decoding utility methods, both for binary and textual
 * informations.
 * </p>
 * 
 * @author Carlo Pelliccia
 * @since 1.1
 * @version 1.3
 */
public class Base64 {
	  private static byte[] base64DecodeChars = new byte[]{
          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
          52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
          -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
          15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
          -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
          41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
	/**
	 * <p>
	 * Encodes a string.
	 * </p>
	 * <p>
	 * Before the string is encoded in Base64, it is converted in a binary
	 * sequence using the system default charset.
	 * </p>
	 * 
	 * @param str
	 *            The source string.
	 * @return The encoded string.
	 * @throws RuntimeException
	 *             If an unexpected error occurs.
	 */
	public static String encode(String str) throws RuntimeException {
		byte[] bytes = str.getBytes();
		byte[] encoded = encode(bytes);
		try {
			return new String(encoded, "ASCII");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("ASCII is not supported!", e);
		}
	}

	/**
	 * <p>
	 * Encodes a string.
	 * </p>
	 * <p>
	 * Before the string is encoded in Base64, it is converted in a binary
	 * sequence using the supplied charset.
	 * </p>
	 * 
	 * @param str
	 *            The source string
	 * @param charset
	 *            The charset name.
	 * @return The encoded string.
	 * @throws RuntimeException
	 *             If an unexpected error occurs.
	 * @since 1.2
	 */
	public static String encode(String str, String charset)
			throws RuntimeException {
		byte[] bytes;
		try {
			bytes = str.getBytes(charset);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("Unsupported charset: " + charset, e);
		}
		byte[] encoded = encode(bytes);
		try {
			return new String(encoded, "ASCII");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("ASCII is not supported!", e);
		}
	}

	/**
	 * <p>
	 * Decodes the supplied string.
	 * </p>
	 * <p>
	 * The supplied string is decoded into a binary sequence, and then the
	 * sequence is encoded with the system default charset and returned.
	 * </p>
	 * 
	 * @param str
	 *            The encoded string.
	 * @return The decoded string.
	 * @throws RuntimeException
	 *             If an unexpected error occurs.
	 */
	 public static byte[] decode(String str) throws UnsupportedEncodingException {
	        StringBuffer sb = new StringBuffer();
	        byte[] data = str.getBytes("US-ASCII");
	        int len = data.length;
	        int i = 0;
	        int b1, b2, b3, b4;
	        while (i < len) {
	           
	            do {
	                b1 = base64DecodeChars[data[i++]];
	            } while (i < len && b1 == -1);
	            if (b1 == -1) break;
	           
	            do {
	                b2 = base64DecodeChars
	                        [data[i++]];
	            } while (i < len && b2 == -1);
	            if (b2 == -1) break;
	            sb.append((char) ((b1 << 2) | ((b2 & 0x30) >>> 4)));
	           
	            do {
	                b3 = data[i++];
	                if (b3 == 61) return sb.toString().getBytes("iso8859-1");
	                b3 = base64DecodeChars[b3];
	            } while (i < len && b3 == -1);
	            if (b3 == -1) break;
	            sb.append((char) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));
	           
	            do {
	                b4 = data[i++];
	                if (b4 == 61) return sb.toString().getBytes("iso8859-1");
	                b4 = base64DecodeChars[b4];
	            } while (i < len && b4 == -1);
	            if (b4 == -1) break;
	            sb.append((char) (((b3 & 0x03) << 6) | b4));
	        }
	        return sb.toString().getBytes("iso8859-1");
	    }

	/**
	 * <p>
	 * Decodes the supplied string.
	 * </p>
	 * <p>
	 * The supplied string is decoded into a binary sequence, and then the
	 * sequence is encoded with the supplied charset and returned.
	 * </p>
	 * 
	 * @param str
	 *            The encoded string.
	 * @param charset
	 *            The charset name.
	 * @return The decoded string.
	 * @throws RuntimeException
	 *             If an unexpected error occurs.
	 * @since 1.2
	 */
	public static String decode(String str, String charset)
			throws RuntimeException {
		byte[] bytes;
		try {
			bytes = str.getBytes("ASCII");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("ASCII is not supported!", e);
		}
		byte[] decoded = decode(bytes);
		try {
			return new String(decoded, charset);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("Unsupported charset: " + charset, e);
		}
	}

	/**
	 * <p>
	 * Encodes a binary sequence.
	 * </p>
	 * <p>
	 * If data are large, i.e. if you are working with large binary files,
	 * consider to use a {@link Base64OutputStream} instead of loading too much
	 * data in memory.
	 * </p>
	 * 
	 * @param bytes
	 *            The source sequence.
	 * @return The encoded sequence.
	 * @throws RuntimeException
	 *             If an unexpected error occurs.
	 * @since 1.2
	 */
	public static byte[] encode(byte[] bytes) throws RuntimeException {
		return encode(bytes, 0);
	}

	/**
	 * <p>
	 * Encodes a binary sequence, wrapping every encoded line every
	 * <em>wrapAt</em> characters. A <em>wrapAt</em> value less than 1 disables
	 * wrapping.
	 * </p>
	 * <p>
	 * If data are large, i.e. if you are working with large binary files,
	 * consider to use a {@link Base64OutputStream} instead of loading too much
	 * data in memory.
	 * </p>
	 * 
	 * @param bytes
	 *            The source sequence.
	 * @param wrapAt
	 *            The max line length for encoded data. If less than 1 no wrap
	 *            is applied.
	 * @return The encoded sequence.
	 * @throws RuntimeException
	 *             If an unexpected error occurs.
	 * @since 1.2
	 */
	public static byte[] encode(byte[] bytes, int wrapAt)
			throws RuntimeException {
		ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		try {
			encode(inputStream, outputStream, wrapAt);
		} catch (IOException e) {
			throw new RuntimeException("Unexpected I/O error", e);
		} finally {
			try {
				inputStream.close();
			} catch (Throwable t) {
				;
			}
			try {
				outputStream.close();
			} catch (Throwable t) {
				;
			}
		}
		return outputStream.toByteArray();
	}

	/**
	 * <p>
	 * Decodes a binary sequence.
	 * </p>
	 * <p>
	 * If data are large, i.e. if you are working with large binary files,
	 * consider to use a {@link Base64InputStream} instead of loading too much
	 * data in memory.
	 * </p>
	 * 
	 * @param bytes
	 *            The encoded sequence.
	 * @return The decoded sequence.
	 * @throws RuntimeException
	 *             If an unexpected error occurs.
	 * @since 1.2
	 */
	public static byte[] decode(byte[] bytes) throws RuntimeException {
		ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		try {
			decode(inputStream, outputStream);
		} catch (IOException e) {
			throw new RuntimeException("Unexpected I/O error", e);
		} finally {
			try {
				inputStream.close();
			} catch (Throwable t) {
				;
			}
			try {
				outputStream.close();
			} catch (Throwable t) {
				;
			}
		}
		return outputStream.toByteArray();
	}

	/**
	 * <p>
	 * Encodes data from the given input stream and writes them in the given
	 * output stream.
	 * </p>
	 * <p>
	 * The supplied input stream is read until its end is reached, but it's not
	 * closed by this method.
	 * </p>
	 * <p>
	 * The supplied output stream is nor flushed neither closed by this method.
	 * </p>
	 * 
	 * @param inputStream
	 *            The input stream.
	 * @param outputStream
	 *            The output stream.
	 * @throws IOException
	 *             If an I/O error occurs.
	 */
	public static void encode(InputStream inputStream, OutputStream outputStream)
			throws IOException {
		encode(inputStream, outputStream, 0);
	}

	/**
	 * <p>
	 * Encodes data from the given input stream and writes them in the given
	 * output stream, wrapping every encoded line every <em>wrapAt</em>
	 * characters. A <em>wrapAt</em> value less than 1 disables wrapping.
	 * </p>
	 * <p>
	 * The supplied input stream is read until its end is reached, but it's not
	 * closed by this method.
	 * </p>
	 * <p>
	 * The supplied output stream is nor flushed neither closed by this method.
	 * </p>
	 * 
	 * @param inputStream
	 *            The input stream from which clear data are read.
	 * @param outputStream
	 *            The output stream in which encoded data are written.
	 * @param wrapAt
	 *            The max line length for encoded data. If less than 1 no wrap
	 *            is applied.
	 * @throws IOException
	 *             If an I/O error occurs.
	 */
	public static void encode(InputStream inputStream,
			OutputStream outputStream, int wrapAt) throws IOException {
		Base64OutputStream aux = new Base64OutputStream(outputStream, wrapAt);
		copy(inputStream, aux);
		aux.commit();
	}

	/**
	 * <p>
	 * Decodes data from the given input stream and writes them in the given
	 * output stream.
	 * </p>
	 * <p>
	 * The supplied input stream is read until its end is reached, but it's not
	 * closed by this method.
	 * </p>
	 * <p>
	 * The supplied output stream is nor flushed neither closed by this method.
	 * </p>
	 * 
	 * @param inputStream
	 *            The input stream from which encoded data are read.
	 * @param outputStream
	 *            The output stream in which decoded data are written.
	 * @throws IOException
	 *             If an I/O error occurs.
	 */
	public static void decode(InputStream inputStream, OutputStream outputStream)
			throws IOException {
		copy(new Base64InputStream(inputStream), outputStream);
	}

	/**
	 * <p>
	 * Encodes data from the given source file contents and writes them in the
	 * given target file, wrapping every encoded line every <em>wrapAt</em>
	 * characters. A <em>wrapAt</em> value less than 1 disables wrapping.
	 * </p>
	 * 
	 * @param source
	 *            The source file, from which decoded data are read.
	 * @param target
	 *            The target file, in which encoded data are written.
	 * @param wrapAt
	 *            The max line length for encoded data. If less than 1 no wrap
	 *            is applied.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @since 1.3
	 */
	public static void encode(File source, File target, int wrapAt)
			throws IOException {
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try {
			inputStream = new FileInputStream(source);
			outputStream = new FileOutputStream(target);
			Base64.encode(inputStream, outputStream, wrapAt);
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (Throwable t) {
					;
				}
			}
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (Throwable t) {
					;
				}
			}
		}
	}

	/**
	 * <p>
	 * Encodes data from the given source file contents and writes them in the
	 * given target file.
	 * </p>
	 * 
	 * @param source
	 *            The source file, from which decoded data are read.
	 * @param target
	 *            The target file, in which encoded data are written.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @since 1.3
	 */
	public static void encode(File source, File target) throws IOException {
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try {
			inputStream = new FileInputStream(source);
			outputStream = new FileOutputStream(target);
			Base64.encode(inputStream, outputStream);
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (Throwable t) {
					;
				}
			}
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (Throwable t) {
					;
				}
			}
		}
	}

	/**
	 * <p>
	 * Decodes data from the given source file contents and writes them in the
	 * given target file.
	 * </p>
	 * 
	 * @param source
	 *            The source file, from which encoded data are read.
	 * @param target
	 *            The target file, in which decoded data are written.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @since 1.3
	 */
	public static void decode(File source, File target) throws IOException {
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try {
			inputStream = new FileInputStream(source);
			outputStream = new FileOutputStream(target);
			decode(inputStream, outputStream);
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (Throwable t) {
					;
				}
			}
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (Throwable t) {
					;
				}
			}
		}
	}

	/**
	 * Copies data from a stream to another.
	 * 
	 * @param inputStream
	 *            The input stream.
	 * @param outputStream
	 *            The output stream.
	 * @throws IOException
	 *             If a unexpected I/O error occurs.
	 */
	private static void copy(InputStream inputStream, OutputStream outputStream)
			throws IOException {
		// 1KB buffer
		byte[] b = new byte[1024];
		int len;
		while ((len = inputStream.read(b)) != -1) {
			outputStream.write(b, 0, len);
		}
	}
	
	
	
}