export default class Stream {
    
    private _stream: number[];
    private _readPoiner: number = 0;
    private _writePointer: number = 0;

    constructor(stream : number[] = []) {
        this._stream = stream;
    }

    public getStream(): number[] {
        return this._stream;
    }

    public setStream(stream: number[]) {
        this._stream = stream;
    }

    public writeInt8(value: number) {
        const negationBit = value < 0 ? 1 : 0;
        const int8Value = Math.abs(value) & 0xFF;
        this._stream[this._writePointer >> 3] |= negationBit << (7 - (this._writePointer & 7));
        this._writePointer++;
        for (let i = 0; i < 8; i++) {
            const bit = (int8Value >> (7 - i)) & 1;
            this._stream[this._writePointer >> 3] |= bit << (7 - (this._writePointer & 7));
            this._writePointer++;
        }
    }

    public readInt8(): number {
        let value = 0;
        const negationBit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
        this._readPoiner++;
        for (let i = 0; i < 8; i++) {
            const bit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
            value |= bit << (7 - i);
            this._readPoiner++;
        }
        return negationBit ? -value : value;
    }

    public writeBit(value: number) {
        const bit = value & 1;
        this._stream[this._writePointer >> 3] |= bit << (7 - (this._writePointer & 7));
        this._writePointer++;
    }

    public readBit(): number {
        const bit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
        this._readPoiner++;
        return bit;
    }

    public writeBoolean(value: boolean) {
        this.writeBit(value ? 1 : 0);
    }

    public readBoolean(): boolean {
        return this.readBit() === 1;
    }
 
    public writeInt16(value: number) {
        this.writeInt8(value >> 8);
        this.writeInt8(value & 0xFF);
    }

    public readInt16(): number {
        const value = this.readInt8() << 8;
        return value | this.readInt8();
    }

    public writeInt32(value: number) {
        this.writeInt16(value >> 16);
        this.writeInt16(value & 0xFFFF);
    }

    public readInt32(): number {
        const value = this.readInt16() << 16;
        return value | this.readInt16();
    }

    public writeInt64(value: number) {
        const high = Math.floor(value / 0x100000000);
        const low = value >>> 0;
        this.writeInt32(high);
        this.writeInt32(low);
    }

    public readInt64(): number {
        const high = this.readInt32();
        const low = this.readInt32() >>> 0;
        return high * 0x100000000 + low;
    }    

    public writeFloat32(value: number) {
        const buffer = new ArrayBuffer(4);
        new DataView(buffer).setFloat32(0, value, true);
        const float32Value = new Uint32Array(buffer)[0];
        this.writeInt32(float32Value);
    }

    public readFloat32(): number {
        const float32Value = this.readInt32();
        const buffer = new ArrayBuffer(4);
        new Uint32Array(buffer)[0] = float32Value;
        return new DataView(buffer).getFloat32(0, true);
    }

    public writeString(value: string) {
        let length = value.length;
        this.writeInt32(length);
        for (let i = 0; i < value.length; i++) {
            this.writeInt16(value.charCodeAt(i));
        }
    }

    public readString(): string {
        let length = this.readInt32();
        /**
         * Фикс уязвимости с длинной строки, превышающей
         * возможность для чтения _stream
         */
        if (length < 0 || length > (this._stream.length - (this._readPoiner >> 3))) {
            console.info("Stream readString length invalid", length, this._stream.length, this._readPoiner);
            return "";
        }
        let value = "";
        for (let i = 0; i < length; i++) {
            value += String.fromCharCode(this.readInt16());
        }
        return value;
    }

    public writeBytes(value: number[]) {
        this.writeInt32(value.length);
        for (let i = 0; i < value.length; i++) {
            this.writeInt8(value[i]);
        }
    }

    public readBytes(): number[] {
        let length = this.readInt32();
        let value : any = [];
        for (let i = 0; i < length; i++) {
            value.push(this.readInt8());
        }
        return value;
    }

}