/*
 * Created on 09.11.2004
 * 
 *  This file is part of susimail project, see http://susi.i2p/
 *  
 *  Copyright (C) 2004-2005  <susi23@mail.i2p>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *  
 * $Revision: 1.6 $
 */
package i2p.susi.webmail.encoding;

import i2p.susi.util.ReadBuffer;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import net.i2p.data.DataHelper;

/**
 * @author susi
 */
public class Base64 implements Encoding {
	
	/* (non-Javadoc)
	 * @see i2p.susi23.util.Encoding#getName()
	 */
	public String getName() {
		return "base64";
	}
	/**
	 * @return Base64-encoded String.
	 * @throws EncodingException 
	 */
	public String encode( byte in[] ) throws EncodingException
	{
		try {
			return encode( new ByteArrayInputStream( in ) );
		}catch (IOException e) {
			throw new EncodingException( e.getMessage() );
		}
	}
	/**
	 * @see Base64#encode(byte[])
	 */
	public String encode(String str) throws EncodingException {
		try {
			return encode( new ByteArrayInputStream( DataHelper.getUTF8(str) ) );
		}catch (IOException e) {
			throw new EncodingException( e.getMessage() );
		}
	}
	/**
	 * 
	 * @param in
	 * @see Base64#encode(String)
	 */
	private String encode( InputStream in ) throws IOException, EncodingException
	{
		StringBuilder strBuf = new StringBuilder();
		
		int buf[] = new int[3];
		int out[] = new int[4];
		int l = 0;
		while( true ) {
			int read = in.available();
			if( read == 0 )
				break;
			int i = 0;
			buf[0] = buf[1] = buf[2] = 0;
			while( read > 0 && i < 3 ) {
				buf[i] = in.read();
				if( buf[i] < 0 || buf[i] > 255 )
					throw new EncodingException( "Encoding supports only values 0..255 (" + buf[i] + ")" );
				i++;
				read--;
			}
			out[0] = encodeByte( ( buf[0] >> 2 ) & 63 );
			out[1] = encodeByte( ( ( buf[0] & 3 ) << 4 ) | ( ( buf[1] >> 4 ) & 15 ) );
			out[2] = encodeByte( ( ( buf[1] & 15 ) << 2 ) | ( ( buf[2] >> 6 ) & 3 ) );
			out[3] = encodeByte( buf[2] & 63 );
			strBuf.append( (char)out[0] );
			strBuf.append( (char)out[1] );
			if( i > 1 ) {
				strBuf.append( (char)out[2] );
			}
			else
				strBuf.append( "=" );
			if( i > 2 )
				strBuf.append( (char)out[3] );
			else
				strBuf.append( "=" );
			i += 3;
			l += 4;
			if( l >= 76 ) {
				strBuf.append( "\r\n" );
				l -= 76;
			}
		}

		return strBuf.toString();
	}

	/**
	 * @param b
	 * @return Encoded single byte.
	 */
	private static int encodeByte(int b) {
		/*
	       0 A            17 R            34 i            51 z
	       1 B            18 S            35 j            52 0
	       2 C            19 T            36 k            53 1
	       3 D            20 U            37 l            54 2
	       4 E            21 V            38 m            55 3
	       5 F            22 W            39 n            56 4
	       6 G            23 X            40 o            57 5
	       7 H            24 Y            41 p            58 6
	       8 I            25 Z            42 q            59 7
	       9 J            26 a            43 r            60 8
	      10 K            27 b            44 s            61 9
	      11 L            28 c            45 t            62 +
	      12 M            29 d            46 u            63 /
	      13 N            30 e            47 v
	      14 O            31 f            48 w         (pad) =
	      15 P            32 g            49 x
	      16 Q            33 h            50 y
	      */
		if( b < 26 )
			b += 'A';
		else if( b < 52 )
			b += 'a' - 26;
		else if( b < 62 )
			b += '0' - 52;
		else if( b == 62 )
			b = '+';
		else
			b = '/';
		return b;
	}

	private static byte decodeByte( byte b ) throws DecodingException {
		if( b >= 'A' && b <= 'Z' )
			b -= 'A';
		else if( b >= 'a' && b <= 'z' )
			b = (byte) (b - 'a' + 26);
		else if( b >= '0' && b <= '9' )
			b = (byte) (b - '0' + 52);
		else if( b == '+' )
			b = 62;
		else if( b == '/' )
			b = 63;
		else if( b == '=' )
			b = 0;
		else
			throw new DecodingException( "Decoding base64 failed (invalid data)." );
		// System.err.println( "decoded " + (char)a + " to " + b );
		return b;
	}

	/**
	 * @param text 
	 * @return Buffer containing a decoded String.
	 */
	public ReadBuffer decode(String text) throws DecodingException {
		return text != null ? decode( DataHelper.getUTF8(text) ) : null;
	}

	/**
	 * @see Base64#decode(String)
	 */
	public ReadBuffer decode(byte[] in) throws DecodingException {
		return decode( in, 0, in.length );
	}

	/**
	 * @see Base64#decode(String)
	 */
	public ReadBuffer decode(byte[] in, int offset, int length) throws DecodingException {
		byte out[] = new byte[length * 3 / 4 + 1 ];
		int written = 0;
		while( length > 0 ) {
			if( in[offset] == '\r' || in[offset] == '\n' || 
					in[offset] == ' ' || in[offset] == '\t' ) {
				offset++;
				length--;
				continue;
			}
			if( length >= 4 ) {
				// System.out.println( "decode: " + (char)in[offset] + (char)in[offset+1]+ (char)in[offset+2]+ (char)in[offset+3] );
				byte b1 = decodeByte( in[offset++] );
				byte b2 = decodeByte( in[offset++] );
				out[written++] = (byte) (( b1 << 2 ) | ( ( b2 >> 4 ) & 3 ) );
				byte b3 = in[offset++];
				if( b3 != '=' ) {
					b3 = decodeByte( b3 );
					out[written++] = (byte)( ( ( b2 & 15 ) << 4 ) | ( ( b3 >> 2 ) & 15 ) );
				}
				byte b4 = in[offset++];
				if( b4 != '=' ) {
					b4 = decodeByte( b4 );
					out[written++] = (byte)( ( ( b3 & 3 ) << 6 ) | b4 & 63 );
				}
				length -= 4;
			}
			else {
				System.err.println( "" );
				throw new DecodingException( "Decoding base64 failed (trailing garbage)." );
			}
		}
		return new ReadBuffer(out, 0, written);
	}

	/*
	 * @see Base64#decode(String)
	 */
	public ReadBuffer decode(ReadBuffer in) throws DecodingException {
		return decode( in.content, in.offset, in.length );
	}
}
