////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
//
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC
// file. If your distribution of this software does not contain
// a LICENSE.DOC file then you have no rights to use this
// software in any manner and should contact DSTC at the address
// below to determine an appropriate licensing arrangement.
//
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
//
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
//
////////////////////////////////////////////////////////////////////////////

package com.dstc.security.x509;

import java.io.*;

/**
 * A FilterOutputStream for base-64 encoded data.
 *
 * <pre>
 *  24 bits -> 4 printable characters
 *  each 6 bit (0..63) -> 1 printable character
 *
 *  if (final_quantum = 8 or 16 bits) pad with zeros on right
 *  and mark output characters not needed for actual input with =
 *
 *  Value Encoding  Value Encoding  Value Encoding  Value Encoding
 *  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
 * </pre>
 *
 * @author Ming Yung
 */
public class Base64OutputStream extends FilterOutputStream
{
  private byte[] footer;
  private static final int WIDTH = 18;
  private int count = 0;
  private byte[] buffer;
  private int bufferLen;

  public Base64OutputStream(OutputStream os, String header, String footer)
  {
    super(os);
    this.buffer = new byte[3];
    try
    {
      if (header != null)
      {
        this.out.write(header.getBytes());
        this.out.write('\n');
      }

      if (footer != null)
        this.footer = footer.getBytes();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

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

  private void writeBase64(byte[] input, int offset) throws IOException
  {
    out.write(table[(input[offset] & 0xfc) >> 2]);
    out.write(table[((input[offset] & 0x03) << 4) | 
                      ((input[offset + 1] & 0xf0) >> 4)]);
    out.write(table[((input[offset + 1] & 0x0f) << 2) | 
                      ((input[offset + 2] & 0xc0) >> 6)]);
    out.write(table[input[offset + 2] & 0x3f]);
  }

  public void write(byte[] data) throws IOException
  {
    int current = 0;
    
    if (bufferLen != 0)
    {
      int extra = buffer.length - bufferLen;
      //should check if enough to copy
      System.arraycopy(data, current, buffer, bufferLen, extra);
      writeBase64(buffer, 0);
      bufferLen = 0;
      current += extra;
    }

    while (true)
    {
      writeBase64(data, current);
      current += 3;
      count++;

      if (count == WIDTH)
      {
        out.write('\n');
        count = 0;
      }

      if (current >= data.length - 3)
      {
        bufferLen = data.length - current;
        System.arraycopy(data, current, buffer, 0, bufferLen);                    
        break;
      }
    }
  }

  private void writeLastBlock() throws IOException
  {
    if (bufferLen == 0) 
      return;

    for (int i=bufferLen; i<buffer.length; i++)
    {
      buffer[i] = 0;
    }

    char[] temp = new char[4];
        
    temp[0] = table[(buffer[0] & 0xfc) >> 2];
    temp[1] = table[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xf0) >> 4)];
    temp[2] = table[((buffer[1] & 0x0f) << 2) | ((buffer[2] & 0xc0) >> 6)];
    temp[3] = table[buffer[2] & 0x3f];

    switch (bufferLen)
    {
      case (1):
        temp[2] = '=';
        temp[3] = '=';
        break;

      case (2):
        temp[3] = '=';
        break;
    }

    this.out.write((new String(temp)).getBytes());
  }

  public void flush() throws IOException
  {
    writeLastBlock();

    out.write('\n');

    if (footer != null)
    {
      out.write(footer);
      out.write('\n');
    }

    super.flush();
  }
}
