/*
 * Copyright 2005 Ralf Joachim
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.castor.core.util;

import java.io.ByteArrayOutputStream;

/**
 * Class decodes a Base64 encoded string back into the original byte representation that can be read
 * as byte array.
 *
 * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a>
 * @version $Revision: 6907 $ $Date: 2005-08-05 13:58:36 -0600 (Fri, 05 Aug 2005) $
 * @since 0.9.9
 */
public final class Base64Decoder {
  /** Mask buffer to or with first sextet. */
  private static final int SEXTET_1_MASK = 0x03FFFF;

  /** Mask buffer to or with second sextet. */
  private static final int SEXTET_2_MASK = 0xFC0FFF;

  /** Mask buffer to or with third sextet. */
  private static final int SEXTET_3_MASK = 0xFFF03F;

  /** Mask buffer to or with forth sextet. */
  private static final int SEXTET_4_MASK = 0xFFFFC0;

  /** Number of bits to shift for one sextet. */
  private static final int SHIFT_1_SEXTET = 6;

  /** Number of bits to shift for two sextet. */
  private static final int SHIFT_2_SEXTET = 12;

  /** Number of bits to shift for three sextet. */
  private static final int SHIFT_3_SEXTET = 18;

  /** Second sextets in buffer. */
  private static final int SEXTET_2 = 2;

  /** Third sextets in buffer. */
  private static final int SEXTET_3 = 3;

  /** Forth sextets in buffer. */
  private static final int SEXTET_4 = 4;

  /** Mask an octet. */
  private static final int OCTET_MASK = 0xFF;

  /** Number of bits to shift for one octet. */
  private static final int SHIFT_1_OCTET = 8;

  /** Number of bits to shift for two octet. */
  private static final int SHIFT_2_OCTET = 16;

  /** White space character (out of range 0 - 63). */
  private static final byte SPC = 127;

  /** Padding character (out of range 0 - 63). */
  private static final byte PAD = 64;

  /** Array to translate base64 characters into sextet byte values. */
  private static final byte[] MAP = {SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 00-07
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 08-0F
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 10-17
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 18-1F
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 20-27
      SPC, SPC, SPC, 62, SPC, SPC, SPC, 63, // 28-2F ' + /'
      52, 53, 54, 55, 56, 57, 58, 59, // 30-37 '01234567'
      60, 61, SPC, SPC, SPC, PAD, SPC, SPC, // 38-3F '89 = '
      SPC, 0, 1, 2, 3, 4, 5, 6, // 40-47 ' ABCDEFG'
      7, 8, 9, 10, 11, 12, 13, 14, // 48-4F 'HIJKLMNO'
      15, 16, 17, 18, 19, 20, 21, 22, // 50-57 'PQRSTUVW'
      23, 24, 25, SPC, SPC, SPC, SPC, SPC, // 58-5F 'XYZ '
      SPC, 26, 27, 28, 29, 30, 31, 32, // 60-67 ' abcdefg'
      33, 34, 35, 36, 37, 38, 39, 40, // 68-6F 'hijklmno'
      41, 42, 43, 44, 45, 46, 47, 48, // 70-77 'pqrstuvw'
      49, 50, 51, SPC, SPC, SPC, SPC, SPC, // 78-7F 'xyz '
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 80-87
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 88-8F
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 90-97
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // 98-9F
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // A0-A7
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // A8-AF
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // B0-B7
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // B8-BF
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // C0-C7
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // C8-CF
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // D0-D7
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // D8-DF
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // E0-E7
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // E8-EF
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // F0-F7
      SPC, SPC, SPC, SPC, SPC, SPC, SPC, SPC, // F8-FF
  };

  /** 24-bit buffer to translate 4 sextets into 3 octets. */
  private int _buffer = 0;

  /** Number of octets in buffer. */
  private int _sextets = 0;

  /** Stream buffer for decoded octets waiting to be read. */
  private ByteArrayOutputStream _stream = new ByteArrayOutputStream();

  /**
   * Decode given string into a decoded byte array.
   * 
   * @param str Base64 String to be decoded.
   * @return All decoded octets as byte array.
   */
  public static byte[] decode(final String str) {
    Base64Decoder dec = new Base64Decoder();
    dec.translate(str);
    return dec.getByteArray();
  }

  /**
   * Construct a default Base64Decoder waiting on calls to its translate() method.
   */
  public Base64Decoder() {}

  /**
   * Translate every base64 character from given string into a sextet byte value by using above
   * translation array. The sextets are then shiftet into an buffer until the buffer contains 4
   * sextets which are then decoded into 3 octets. The translate and decode process is continued
   * until all characters of given string are evaluated. If there are remaing sextets in the buffer
   * they also will be converted into octets at the end. All the converted octets are added to the
   * list for later read.
   * 
   * @param string Base64 String to be decoded.
   */
  public void translate(final String string) {
    int len = string.length();
    int index = 0;
    int data = MAP[string.charAt(index)];
    while ((index < len) && (data != PAD)) {
      if (data != SPC) {
        if (_sextets == 0) {
          _buffer = (_buffer & SEXTET_1_MASK) | (data << SHIFT_3_SEXTET);
        } else if (_sextets == 1) {
          _buffer = (_buffer & SEXTET_2_MASK) | (data << SHIFT_2_SEXTET);
        } else if (_sextets == 2) {
          _buffer = (_buffer & SEXTET_3_MASK) | (data << SHIFT_1_SEXTET);
        } else {
          _buffer = (_buffer & SEXTET_4_MASK) | data;
        }

        if ((++_sextets) == SEXTET_4) {
          decode();
        }
      }

      if (++index < len) {
        data = MAP[string.charAt(index)];
      }
    }

    if (_sextets > 0) {
      decodeWithPadding();
    }
  }

  /**
   * Decode 3 octets from buffer and add them to list of octets to read.
   */
  private void decode() {
    _stream.write((byte) ((_buffer >> SHIFT_2_OCTET) & OCTET_MASK)); // octet 1
    _stream.write((byte) ((_buffer >> SHIFT_1_OCTET) & OCTET_MASK)); // octet 2
    _stream.write((byte) (_buffer & OCTET_MASK)); // octet 3
    _buffer = 0;
    _sextets = 0;
  }

  /**
   * Decode the remaining octets from buffer and add them to list of octets to read.
   */
  private void decodeWithPadding() {
    if (_sextets >= SEXTET_2) { // octet 1
      _stream.write((byte) ((_buffer >> SHIFT_2_OCTET) & OCTET_MASK));
    }
    if (_sextets >= SEXTET_3) { // octet 2
      _stream.write((byte) ((_buffer >> SHIFT_1_OCTET) & OCTET_MASK));
    }
    if (_sextets >= SEXTET_4) { // octet 3
      _stream.write((byte) (_buffer & OCTET_MASK));
    }
    _buffer = 0;
    _sextets = 0;
  }

  /**
   * Get all decoded octets as byte array.
   * 
   * @return All decoded octets as byte array.
   */
  public byte[] getByteArray() {
    return _stream.toByteArray();
  }
}
