package org.maachang.util;

/**
 * Base64. <BR>
 * <BR>
 * Base64のエンコード/デコートをサポートします.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangBase 1.00
 */
public class Base64 {

    /**
     * 無効コード値.
     */
    private static final int NOT_DEC = 0x7fffffff;

    /**
     * 余りデコード値.
     */
    private static final char REMAINDER_ENC = '=';

    /**
     * エンコード表.
     */
    private static final char[] ENC_CD = { '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 static final int[] DEC_CD = { 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x0000003e, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x0000003f,

            0x00000034, 0x00000035, 0x00000036, 0x00000037, 0x00000038,
            0x00000039, 0x0000003a, 0x0000003b, 0x0000003c, 0x0000003d,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x00000040, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x00000000, 0x00000001, 0x00000002, 0x00000003,
            0x00000004, 0x00000005, 0x00000006, 0x00000007, 0x00000008,
            0x00000009, 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000d,
            0x0000000e,

            0x0000000f, 0x00000010, 0x00000011, 0x00000012, 0x00000013,
            0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018,
            0x00000019, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x0000001a, 0x0000001b, 0x0000001c, 0x0000001d,
            0x0000001e, 0x0000001f, 0x00000020, 0x00000021, 0x00000022,
            0x00000023, 0x00000024, 0x00000025, 0x00000026, 0x00000027,
            0x00000028,

            0x00000029, 0x0000002a, 0x0000002b, 0x0000002c, 0x0000002d,
            0x0000002e, 0x0000002f, 0x00000030, 0x00000031, 0x00000032,
            0x00000033, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff,

            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
            0x7fffffff };

    /**
     * コンストラクタ.
     */
    private Base64() {
    }

    /**
     * エンコード処理. <BR>
     * <BR>
     * 対象バイナリ情報をBase64にエンコードします. <BR>
     * 
     * @param binary
     *            エンコード対象のバイナリ情報を設定します.
     * @return String Base64にエンコードされた文字情報が返されます.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final String encode(byte[] binary)
            throws IllegalArgumentException {
        int i, j, k;
        int len;
        int allLen;
        int etc;

        String ret = null;
        char[] ary = null;

        if (binary == null || (allLen = binary.length) <= 0) {
            throw new IllegalArgumentException("引数は不正です");
        }

        etc = allLen % 3;
        len = allLen / 3;
        ary = new char[(len * 4) + ((etc != 0) ? 4 : 0)];

        for (i = 0, j = 0, k = 0; i < len; i++, j += 3, k += 4) {

            ary[k] = Base64.ENC_CD[(int) ((binary[j] & 0x000000fc) >> 2)];
            ary[k + 1] = Base64.ENC_CD[(int) (((binary[j] & 0x00000003) << 4) | ((binary[j + 1] & 0x000000f0) >> 4))];
            ary[k + 2] = Base64.ENC_CD[(int) (((binary[j + 1] & 0x0000000f) << 2) | ((binary[j + 2] & 0x000000c0) >> 6))];
            ary[k + 3] = Base64.ENC_CD[(int) (binary[j + 2] & 0x0000003f)];

        }

        switch (etc) {
        case 1:

            j = len * 3;
            k = len * 4;
            ary[k] = Base64.ENC_CD[(int) ((binary[j] & 0x000000fc) >> 2)];
            ary[k + 1] = Base64.ENC_CD[(int) ((binary[j] & 0x00000003) << 4)];
            ary[k + 2] = Base64.REMAINDER_ENC;
            ary[k + 3] = Base64.REMAINDER_ENC;

            break;

        case 2:

            j = len * 3;
            k = len * 4;
            ary[k] = Base64.ENC_CD[(int) ((binary[j] & 0x000000fc) >> 2)];
            ary[k + 1] = Base64.ENC_CD[(int) (((binary[j] & 0x00000003) << 4) | ((binary[j + 1] & 0x000000f0) >> 4))];
            ary[k + 2] = Base64.ENC_CD[(int) (((binary[j + 1] & 0x0000000f) << 2))];
            ary[k + 3] = Base64.REMAINDER_ENC;

            break;

        }

        ret = new String(ary, 0, ary.length);
        ary = null;

        return ret;
    }

    /**
     * デコード処理. <BR>
     * <BR>
     * Base64情報をデコードします. <BR>
     * 
     * @param base64
     *            対象のBase64データを設定します.
     * @return byte[] 変換されたバイナリ情報が返されます.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final byte[] decode(String base64)
            throws IllegalArgumentException {
        int i, j, k;
        int len;
        int allLen;
        int etc;

        byte[] ret = null;

        if (base64 == null || (allLen = base64.length()) <= 0) {
            throw new IllegalArgumentException("引数は不正です");
        }

        for (i = allLen - 1, etc = 0; i >= 0; i--) {
            if (base64.charAt(i) == Base64.REMAINDER_ENC) {
                etc++;
            } else {
                break;
            }
        }

        len = allLen / 4;
        ret = new byte[(len * 3) - etc];

        len -= 1;
        for (i = 0, j = 0, k = 0; i < len; i++, j += 4, k += 3) {

            ret[k] = (byte) (((Base64.DEC_CD[base64.charAt(j)] & 0x0000003f) << 2) | ((Base64.DEC_CD[base64
                    .charAt(j + 1)] & 0x00000030) >> 4));
            ret[k + 1] = (byte) (((Base64.DEC_CD[base64.charAt(j + 1)] & 0x0000000f) << 4) | ((Base64.DEC_CD[base64
                    .charAt(j + 2)] & 0x0000003c) >> 2));
            ret[k + 2] = (byte) (((Base64.DEC_CD[base64.charAt(j + 2)] & 0x00000003) << 6) | (Base64.DEC_CD[base64
                    .charAt(j + 3)] & 0x0000003f));

        }

        switch (etc) {
        case 0:

            j = len * 4;
            k = len * 3;
            ret[k] = (byte) (((Base64.DEC_CD[base64.charAt(j)] & 0x0000003f) << 2) | ((Base64.DEC_CD[base64
                    .charAt(j + 1)] & 0x00000030) >> 4));
            ret[k + 1] = (byte) (((Base64.DEC_CD[base64.charAt(j + 1)] & 0x0000000f) << 4) | ((Base64.DEC_CD[base64
                    .charAt(j + 2)] & 0x0000003c) >> 2));
            ret[k + 2] = (byte) (((Base64.DEC_CD[base64.charAt(j + 2)] & 0x00000003) << 6) | (Base64.DEC_CD[base64
                    .charAt(j + 3)] & 0x0000003f));
            break;

        case 1:

            j = len * 4;
            k = len * 3;
            ret[k] = (byte) (((Base64.DEC_CD[base64.charAt(j)] & 0x0000003f) << 2) | ((Base64.DEC_CD[base64
                    .charAt(j + 1)] & 0x00000030) >> 4));
            ret[k + 1] = (byte) (((Base64.DEC_CD[base64.charAt(j + 1)] & 0x0000000f) << 4) | ((Base64.DEC_CD[base64
                    .charAt(j + 2)] & 0x0000003c) >> 2));
            break;

        case 2:

            j = len * 4;
            k = len * 3;
            ret[k] = (byte) (((Base64.DEC_CD[base64.charAt(j)] & 0x0000003f) << 2) | ((Base64.DEC_CD[base64
                    .charAt(j + 1)] & 0x00000030) >> 4));
            break;

        }

        return ret;

    }

    /**
     * 対象文字列がBase64であるかチェック. <BR>
     * <BR>
     * 対象文字列がBase64であるかチェックします. <BR>
     * 
     * @param code
     *            チェック対象の文字列を設定します.
     * @return boolean チェック結果が返されます.
     */
    public static final boolean isBase64(String code) {
        int i;
        int len;

        boolean ret = true;

        try {
            len = code.length();
            if (len % 4 != 0) {
                ret = false;
            } else {
                for (i = 0; i < len; i++) {
                    if (DEC_CD[code.charAt(i)] == NOT_DEC) {
                        ret = false;
                        break;
                    }
                }
            }
        } catch (Exception e) {
            ret = false;
        }

        return ret;

    }
}
