package org.xbib.datastructures.json.iterator.extra;

import org.xbib.datastructures.json.iterator.JsonIterator;
import org.xbib.datastructures.json.iterator.spi.Slice;
import org.xbib.datastructures.json.iterator.output.JsonStream;

import java.io.IOException;
import java.util.Arrays;

/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance
 * with RFC 2045.<br><br>
 * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster
 * on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes)
 * compared to <code>sun.misc.Encoder()/Decoder()</code>.<br><br>
 *
 * On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and
 * about 50% faster for decoding large arrays. This implementation is about twice as fast on very small
 * arrays (&lt 30 bytes). If source/destination is a <code>String</code> this
 * version is about three times as fast due to the fact that the Commons Codec result has to be recoded
 * to a <code>String</code> from <code>byte[]</code>, which is very expensive.<br><br>
 *
 * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
 * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
 * as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown
 * whether Sun's <code>sun.misc.Encoder()/Decoder()</code> produce temporary arrays but since performance
 * is quite low it probably does.<br><br>
 *
 * The encoder produces the same output as the Sun one except that the Sun's encoder appends
 * a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the
 * length and is probably a side effect. Both are in conformance with RFC 2045 though.<br>
 * Commons codec seem to always att a trailing line separator.<br><br>
 *
 * <b>Note!</b>
 * The encode/decode method pairs (types) come in three versions with the <b>exact</b> same algorithm and
 * thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different
 * format types. The methods not used can simply be commented out.<br><br>
 *
 * There is also a "fast" version of all decode methods that works the same way as the normal ones, but
 * har a few demands on the decoded input. Normally though, these fast verions should be used if the source if
 * the input is known and it hasn't bee tampered with.
 */

abstract class Base64 {
    private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
    static final byte[] BA;
    static final int[] IA = new int[256];
    static {
        Arrays.fill(IA, -1);
        for (int i = 0, iS = CA.length; i < iS; i++) {
            IA[CA[i]] = i;
        }
        IA['='] = 0;
        BA = new byte[CA.length];
        for (int i = 0; i < CA.length; i++) {
            BA[i] = (byte)CA[i];
        }
    }

    static int encodeToChar(byte[] sArr, char[] dArr, final int start) {
        final int sLen = sArr.length;

        final int eLen = (sLen / 3) * 3;              // Length of even 24-bits.
        final int dLen = ((sLen - 1) / 3 + 1) << 2;   // Returned character count

        // Encode even 24-bits
        for (int s = 0, d = start; s < eLen;) {
            // Copy next three bytes into lower 24 bits of int, paying attension to sign.
            int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);

            // Encode the int into four chars
            dArr[d++] = CA[(i >>> 18) & 0x3f];
            dArr[d++] = CA[(i >>> 12) & 0x3f];
            dArr[d++] = CA[(i >>> 6) & 0x3f];
            dArr[d++] = CA[i & 0x3f];
        }

        // Pad and encode last bits if source isn't even 24 bits.
        int left = sLen - eLen; // 0 - 2.
        if (left > 0) {
            // Prepare the int
            int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);

            // Set last four chars
            dArr[start + dLen - 4] = CA[i >> 12];
            dArr[start + dLen - 3] = CA[(i >>> 6) & 0x3f];
            dArr[start + dLen - 2] = left == 2 ? CA[i & 0x3f] : '=';
            dArr[start + dLen - 1] = '=';
        }

        return dLen;
    }

    static int encodeToBytes(byte[] sArr, JsonStream stream) throws IOException {
        final int sLen = sArr.length;

        final int eLen = (sLen / 3) * 3;              // Length of even 24-bits.
        final int dLen = ((sLen - 1) / 3 + 1) << 2;   // Returned character count

        // Encode even 24-bits
        for (int s = 0; s < eLen;) {
            // Copy next three bytes into lower 24 bits of int, paying attension to sign.
            int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);

            // Encode the int into four chars
            stream.write(BA[(i >>> 18) & 0x3f], BA[(i >>> 12) & 0x3f], BA[(i >>> 6) & 0x3f], BA[i & 0x3f]);
        }

        // Pad and encode last bits if source isn't even 24 bits.
        int left = sLen - eLen; // 0 - 2.
        if (left > 0) {
            // Prepare the int
            int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);

            // Set last four chars
            stream.write(BA[i >> 12], BA[(i >>> 6) & 0x3f], left == 2 ? BA[i & 0x3f] : (byte)'=', (byte)'=');
        }

        return dLen;
    }

    static void encodeLongBits(long bits, JsonStream stream) throws IOException {
        int i = (int) bits;
        byte b1 = BA[(i >>> 18) & 0x3f];
        byte b2 = BA[(i >>> 12) & 0x3f];
        byte b3 = BA[(i >>> 6) & 0x3f];
        byte b4 = BA[i & 0x3f];
        stream.write((byte)'"', b1, b2, b3, b4);
        bits = bits >>> 24;
        i = (int) bits;
        b1 = BA[(i >>> 18) & 0x3f];
        b2 = BA[(i >>> 12) & 0x3f];
        b3 = BA[(i >>> 6) & 0x3f];
        b4 = BA[i & 0x3f];
        stream.write(b1, b2, b3, b4);
        bits = (bits >>> 24) << 2;
        i = (int) bits;
        b1 = BA[i >> 12];
        b2 = BA[(i >>> 6) & 0x3f];
        b3 = BA[i & 0x3f];
        stream.write(b1, b2, b3, (byte)'"');
    }

    static long decodeLongBits(JsonIterator iter) throws IOException {
        Slice slice = iter.readStringAsSlice();
        if (slice.len() != 11) {
            throw iter.reportError("decodeLongBits", "must be 11 bytes for long bits encoded double");
        }
        byte[] encoded = slice.data();
        int sIx = slice.head();
        long i = IA[encoded[sIx++]] << 18 | IA[encoded[sIx++]] << 12 | IA[encoded[sIx++]] << 6 | IA[encoded[sIx++]];
        long bits = i;
        i = IA[encoded[sIx++]] << 18 | IA[encoded[sIx++]] << 12 | IA[encoded[sIx++]] << 6 | IA[encoded[sIx++]];
        bits = i << 24 | bits;
        i =  IA[encoded[sIx++]] << 12 | IA[encoded[sIx++]] << 6 | IA[encoded[sIx]];
        bits = i << 46 | bits;
        return bits;
    }

    static int findEnd(final byte[] sArr, final int start) {
        for (int i = start; i < sArr.length; i++)
            if (IA[sArr[i] & 0xff] < 0)
                return i;
        return sArr.length;
    }

    private final static byte[] EMPTY_ARRAY = new byte[0];

    static byte[] decodeFast(final byte[] sArr, final int start, final int end) {
        // Check special case
        int sLen = end - start;
        if (sLen == 0)
            return EMPTY_ARRAY;

        int sIx = start, eIx = end - 1;    // Start and end index after trimming.

        // Trim illegal chars from start
        while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0)
            sIx++;

        // Trim illegal chars from end
        while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0)
            eIx--;

        // get the padding count (=) (0, 1 or 2)
        int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0;  // Count '=' at end.
        int cCnt = eIx - sIx + 1;   // Content count including possible separators
        int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;

        int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
        byte[] dArr = new byte[len];       // Preallocate byte[] of exact length

        // Decode all but the last 0 - 2 bytes.
        int d = 0;
        for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
            // Assemble three bytes into an int from four "valid" characters.
            int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];

            // Add the bytes
            dArr[d++] = (byte) (i >> 16);
            dArr[d++] = (byte) (i >> 8);
            dArr[d++] = (byte) i;

            // If line separator, jump over it.
            if (sepCnt > 0 && ++cc == 19) {
                sIx += 2;
                cc = 0;
            }
        }

        if (d < len) {
            // Decode last 1-3 bytes (incl '=') into 1-3 bytes
            int i = 0;
            for (int j = 0; sIx <= eIx - pad; j++)
                i |= IA[sArr[sIx++]] << (18 - j * 6);

            for (int r = 16; d < len; r -= 8)
                dArr[d++] = (byte) (i >> r);
        }

        return dArr;
    }
}
