/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * We would like to eventually use Sun's base64 encoder, but it is not
 * public yet.  It is in the sun.misc package in JDK1.2.
 */
package org.mozilla.jss.util;

import java.io.FilterOutputStream;
import java.io.PrintStream;
import java.io.IOException;

/**
 * An output stream filter that takes arbitrary bytes and outputs their
 * base64 encoding.  Call flush() or close() to write out the final padding.
 * The class also automatically puts line breaks in the output stream.
 */
public class Base64OutputStream extends FilterOutputStream {

	/**
	 * Create a stream that does not insert line breaks.  To have line
	 * breaks, use the other constructor.
	 */
	public Base64OutputStream(PrintStream out) {
		super(out);
		charsOnLine = 0;
		inputCount = 0;
		input = new byte[3];
		doLineBreaks = false;
	}

	/**
	 * @param quadsPerLine Number of 4-character blocks to write before
	 * 	outputting a line break.  For example, for 76-characters per line,
	 *	pass in 76/4 = 19.
	 */
	public Base64OutputStream(PrintStream out, int quadsPerLine) {
		this(out);
		doLineBreaks = true;
		Assert._assert(quadsPerLine>0, "quadsPerLine must be > 0");
		charsPerLine = quadsPerLine*4;
	}
		

	public void write(int oneByte) throws IOException {
		input[inputCount++] = (byte)(0xff & oneByte);
		if(inputCount==3) {
			outputOneAtom();
			if(doLineBreaks) {
				if(charsOnLine==charsPerLine) {
					((PrintStream)out).println();
					charsOnLine=0;
				}
				Assert._assert(charsOnLine < charsPerLine);
			}
		}
		Assert._assert(inputCount < 3);
	}

	/**
	 * This flushes the stream and closes the next stream downstream.
	 */
	public void close() throws IOException {
		flush();
		out.close();
	}

	/**
	 * Calling this will put the ending padding on the base64 stream,
	 * so don't call it until you have no data left.  The class does no
	 * unnecessary buffering, so you probably shouldn't call it at all.
	 */
	public void flush() throws IOException {
		if(inputCount > 0) {
			outputOneAtom();
		}
		if(doLineBreaks) {
			((PrintStream)out).println();
			charsOnLine = 0;
		}
	}

	public void write(byte[] buffer) throws IOException {
		write(buffer, 0, buffer.length);
	}

	public void write(byte[] buffer, int offset, int count) throws IOException {
		int i;
		int byteCount;

		for(i=offset, byteCount=0; byteCount < count; i++, byteCount++) {
			write(buffer[i]);
		}
	}

    private final static char encoding[] = {
    //   0   1   2   3   4   5   6   7
        'A','B','C','D','E','F','G','H', // 0
        'I','J','K','L','M','N','O','P', // 1
        'Q','R','S','T','U','V','W','X', // 2
        'Y','Z','a','b','c','d','e','f', // 3
        'g','h','i','j','k','l','m','n', // 4
        'o','p','q','r','s','t','u','v', // 5
        'w','x','y','z','0','1','2','3', // 6
        '4','5','6','7','8','9','+','/'  // 7
    };

	/**
	 * Output 3 bytes of input as 4 bytes of base-64 encoded output.
	 * If there are fewer than 3 bytes available, the output will be
	 * padded according to the spec.  Padding should only happen at the end
	 * of the stream.
	 */
	private void outputOneAtom() throws IOException {
		byte[] output = new byte[4];

		if(inputCount==0) {
			return;
		}

		// If we have fewer than 3 bytes, pad the rest with zeroes
		if(inputCount<3) {
			input[2]=0;
			if(inputCount<2) {
				input[1]=0;
			}
		}

		// Do a base64 encoding
		output[0] = (byte) encoding[ (input[0]>>>2) & 0x3f ];
		output[1] = (byte) encoding[ (input[0]<<4)&0x30 | (input[1]>>>4)&0xf ];
		output[2] = (byte) encoding[ (input[1]<<2)&0x3c | (input[2]>>>6)&0x3 ];
		output[3] = (byte) encoding[ input[2]&0x3f ];

		// add padding
		if(inputCount<3) {
			output[3] = (byte)'=';
			if(inputCount<2) {
				output[2] = (byte)'=';
			}
		}

		out.write(output);
		inputCount=0;
		if(doLineBreaks) {
			charsOnLine += 4;
		}
	}

	//////////////////////////////////////////////////////////////////////
	// Private data
	//////////////////////////////////////////////////////////////////////
	private byte[] input;	// buffered input, max 3 bytes (after 3 we write
							// them out base64-encoded)
	private short inputCount;	// number of bytes in input buffer
	private short charsOnLine;  // number of bytes on the current line
	private int charsPerLine;  // maximum characters per line
	private static final int DEFAULT_QUADS_PER_LINE=16; //64 chars
	private boolean doLineBreaks;
}
