import {AssetId} from './Asset';

declare function require(name: 'fastestsmallesttextencoderdecoder'): {
    TextEncoder: typeof TextEncoder,
    TextDecoder: typeof TextDecoder
};

declare function require(name: 'base64-js'): {
    fromByteArray: (data: Uint8Array) => string;
};

// Use JS implemented TextDecoder and TextEncoder if it is not provided by the
// browser.
let _TextDecoder: typeof TextDecoder;
let _TextEncoder: typeof TextEncoder;
if (typeof TextDecoder === 'undefined' || typeof TextEncoder === 'undefined') {
    // Wait to require the text encoding polyfill until we know it's needed.
    const encoding = require('fastestsmallesttextencoderdecoder');
    _TextDecoder = encoding.TextDecoder;
    _TextEncoder = encoding.TextEncoder;
} else {
    _TextDecoder = TextDecoder;
    _TextEncoder = TextEncoder;
}

const memoizedToString = (function () {
    /**
     * The maximum length of a chunk before encoding it into base64.
     *
     * 32766 is a multiple of 3 so btoa does not need to use padding characters
     * except for the final chunk where that is fine. 32766 is also close to
     * 32768 so it is close to a size an memory allocator would prefer.
     * @constant {number}
     */
    const BTOA_CHUNK_MAX_LENGTH = 32766;

    /**
     * An array cache of bytes to characters.
     * @constant {?Array.<string>}
     */
    let fromCharCode: string[] | null = null;

    const strings = {};
    return (assetId: AssetId, data: Uint8Array) => {
        if (!Object.prototype.hasOwnProperty.call(strings, assetId)) {
            if (typeof btoa === 'undefined') {
                // Use a library that does not need btoa to run.
                const base64js = require('base64-js');
                strings[assetId] = base64js.fromByteArray(data);
            } else {
                // Native btoa is faster than javascript translation. Use js to
                // create a "binary" string and btoa to encode it.
                if (fromCharCode === null) {
                    // Cache the first 256 characters for input byte values.
                    fromCharCode = new Array(256);
                    for (let i = 0; i < 256; i++) {
                        fromCharCode[i] = String.fromCharCode(i);
                    }
                }

                const {length} = data;
                let s = '';
                // Iterate over chunks of the binary data.
                for (let i = 0, e = 0; i < length; i = e) {
                    // Create small chunks to cause more small allocations and
                    // less large allocations.
                    e = Math.min(e + BTOA_CHUNK_MAX_LENGTH, length);
                    let s_ = '';
                    for (let j = i; j < e; j += 1) {
                        s_ += fromCharCode[data[j]];
                    }
                    // Encode the latest chunk so the we create one big output
                    // string instead of creating a big input string and then
                    // one big output string.
                    s += btoa(s_);
                }
                strings[assetId] = s;
            }
        }
        return strings[assetId];
    };
}());

export {memoizedToString, _TextEncoder, _TextDecoder};
