import { word } from 'js/types';
import { ProDOSVolume } from '.';
import type { VDH } from './vdh';

const BLOCK_ENTRIES = 4096;

export class BitMap {
    private vdh: VDH;
    private blocks: Uint8Array[];

    constructor(volume: ProDOSVolume) {
        this.vdh = volume.vdh();
        this.blocks = volume.blocks();
    }

    freeBlocks() {
        const free: word[] = [];
        let blockOffset = 0;
        let byteOffset = 0;
        let bitOffset = 0;
        let bitMapBlock = this.blocks[this.vdh.bitMapPointer + blockOffset];
        for (let idx = 0; idx < this.vdh.totalBlocks; idx++) {
            const currentByte = bitMapBlock[byteOffset];
            const mask = 1 << bitOffset;
            if (currentByte & mask) {
                free.push(idx);
            }
            bitOffset += 1;
            if (bitOffset > 7) {
                bitOffset = 0;
                byteOffset += 1;
                if (byteOffset > (BLOCK_ENTRIES >> 3)) {
                    byteOffset = 0;
                    blockOffset += 1;
                    bitMapBlock = this.blocks[this.vdh.bitMapPointer + blockOffset];
                }
            }
        }
        return free;
    }

    allocBlock() {
        for (let idx = 0; idx < this.vdh.totalBlocks; idx++) {
            const blockOffset = Math.floor(idx / BLOCK_ENTRIES);
            const bitMapBlock = this.blocks[this.vdh.bitMapPointer + blockOffset];
            const byteOffset = (idx - blockOffset * BLOCK_ENTRIES) >> 8;
            const bits = bitMapBlock[byteOffset];
            if (bits !== 0xff) {
                let mask = 0x01;
                for (let bitOffset = 0; bitOffset < 8; bitOffset++) {
                    if (!(bits & mask)) {
                        bitMapBlock[byteOffset] |= mask;
                        return idx;
                    }
                    mask <<= 1;
                }
            }
        }
        throw new Error('Disk full');
    }

    freeBlock(block: word) {
        if (block >= this.vdh.totalBlocks) {
            throw new Error('Block out of range');
        }
        const blockOffset = Math.floor(block / BLOCK_ENTRIES);
        const byteOffset = (block - blockOffset * BLOCK_ENTRIES) >> 8;
        const bitOffset = block & 0x7;

        const bitMapBlock = this.blocks[this.vdh.bitMapPointer + blockOffset];

        bitMapBlock[byteOffset] &= 0xff ^ (0x01 << bitOffset);
    }
}
