import {getByteLength} from './common';

let crcTable!: number[];

export default class Zip {
    private blob: BlobPart[] = [];
    private files: {header: BlobPart[], offset: number, name: string}[] = [];
    private offset = 0;

    constructor() {
        if(crcTable !== undefined!) return;
        crcTable = [];
        for(let c, n = 0; n < 256; n++) {
            c = n;
            for(let k = 0; k < 8; k++)
                c = ((c & 1) ? ((c >>> 1) ^ 0xEDB88320) : (c >>> 1)); //tslint:disable-line:strict-boolean-expressions
            crcTable[n] = c;
        }
    }

    addFile(name: string, content: string): void {
        let crc = -1;
        let length = 0;
        const nameLength = getByteLength(name);
        for(let i = 0, strlen = content.length; i < strlen; ++i) {
            let c = content.charCodeAt(i);
            if(c > 0xD800 && c < 0xD8FF) //surrogate pairs
                c = (c - 0xD800) * 0x400 + content.charCodeAt(++i) - 0xDC00 + 0x10000;
            let l = c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : c < 0x200000 ? 4 : c < 0x4000000 ? 5 : 6;
            length += l;
            let byte = l === 1 ? c : ((0xFF00 >> l) % 256) | (c >>> (l - 1) * 6);
            --l;
            while(true) {
                crc = (crc >>> 8) ^ crcTable[(crc ^ byte) & 0xFF];
                if(--l >= 0) byte = ((c >>> (l * 6)) & 0x3F) | 0x80;
                else break;
            }
        }
        crc = (crc ^ (-1)) >>> 0;
        const file = {
            header: [Uint16Array.of(0, 0, 0, 0, 0), Uint32Array.of(crc, length, length), Uint16Array.of(nameLength, 0)],
            offset: this.offset, name
        };
        this.blob.push(Uint32Array.of(0x04034B50));
        this.blob.push(...file.header);
        this.blob.push(name, content);
        this.offset += nameLength + length + 30;
        this.files.push(file);
    }

    build(): Blob {
        const start = this.offset;
        for(const file of this.files) {
            this.blob.push(Uint16Array.of(0x4B50, 0x0201, 0));
            this.blob.push(...file.header);
            this.blob.push(Uint16Array.of(0, 0, 0, 0, 0), Uint32Array.of(file.offset), file.name);
            this.offset += getByteLength(file.name) + 46;
        }
        this.blob.push(Uint16Array.of(0x4B50, 0x0605, 0, 0, this.files.length, this.files.length),
            Uint32Array.of(this.offset - start, start), Uint16Array.of(0));
        return new Blob(this.blob);
    }
}