import {
    create2MGFragments,
    create2MGFromBlockDisk,
    FORMAT,
    HeaderData,
    read2MGHeader
} from 'js/formats/2mg';
import { BlockDisk, ENCODING_BLOCK } from 'js/formats/types';
import { concat } from 'js/util';
import { BYTES_BY_SECTOR_IMAGE } from './testdata/16sector';

const INVALID_SIGNATURE_IMAGE = new Uint8Array([
    0x11, 0x22, 0x33, 0x44
]);

const INVALID_HEADER_LENGTH_IMAGE = new Uint8Array([
    // ID
    0x32, 0x49, 0x4d, 0x47,
    // Creator ID
    0x58, 0x47, 0x53, 0x21,
    // Header size
    0x0a, 0x00
]);

const VALID_PRODOS_IMAGE = concat(new Uint8Array([
    // ID
    0x32, 0x49, 0x4d, 0x47,
    // Creator ID
    0x58, 0x47, 0x53, 0x21,
    // Header size
    0x40, 0x00,
    // Version number
    0x01, 0x00,
    // Image format (ProDOS)
    0x01, 0x00, 0x00, 0x00,
    // Flags
    0x00, 0x00, 0x00, 0x00,
    // ProDOS blocks
    0x18, 0x01, 0x00, 0x00,
    // Data offset
    0x40, 0x00, 0x00, 0x00,
    // Data length (in bytes)
    0x00, 0x30, 0x02, 0x00,
    // Comment offset
    0x00, 0x00, 0x00, 0x00,
    // Comment length
    0x00, 0x00, 0x00, 0x00,
    // Creator data offset
    0x00, 0x00, 0x00, 0x00,
    // Creator data length
    0x00, 0x00, 0x00, 0x00,
    // Padding
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
]), BYTES_BY_SECTOR_IMAGE);

describe('2mg format', () => {
    describe('read2MGHeader', () => {
        it('throws if the signature is invalid', () => {
            expect(() => read2MGHeader(INVALID_SIGNATURE_IMAGE.buffer)).toThrow(/signature/);
        });

        it('throws if the header length is invalid', () => {
            expect(() => read2MGHeader(INVALID_HEADER_LENGTH_IMAGE.buffer)).toThrowError(/header length/);
        });

        it('throws if block count is not correct for ProDOS image', () => {
            const image = new Uint8Array(VALID_PRODOS_IMAGE);
            image[0x14] = image[0x14] + 1;
            expect(() => read2MGHeader(image.buffer)).toThrowError(/blocks/);
        });

        it('throws if comment comes before end of disk data', () => {
            const image = new Uint8Array(VALID_PRODOS_IMAGE);
            image[0x20] = 1;
            expect(() => read2MGHeader(image.buffer)).toThrowError(/is before/);
        });

        it('throws if creator data comes before end of disk data', () => {
            const image = new Uint8Array(VALID_PRODOS_IMAGE);
            image[0x28] = 1;
            expect(() => read2MGHeader(image.buffer)).toThrowError(/is before/);
        });

        it('throws if data length is too big for file', () => {
            const image = new Uint8Array(VALID_PRODOS_IMAGE);
            image[0x1D] += 2;  // Increment byte length by 512
            image[0x14] += 1;  // Increment block length by 1
            expect(() => read2MGHeader(image.buffer)).toThrowError(/extends beyond/);
        });

        it('returns a header for a valid ProDOS image', () => {
            expect(read2MGHeader(VALID_PRODOS_IMAGE.buffer)).not.toBeNull();
        });

        it('returns a filled-in header for a valid ProDOS image', () => {
            const header = read2MGHeader(VALID_PRODOS_IMAGE.buffer);
            expect(header.creator).toBe('XGS!');
            expect(header.bytes).toBe(143_360);
            expect(header.offset).toBe(64);
            expect(header.format).toBe(1);
            expect(header.readOnly).toBeFalsy();
            expect(header.volume).toBe(0);
            expect(header.comment).toBeUndefined();
            expect(header.creatorData).toBeUndefined();
        });
    });

    describe('create2MGFragments', () => {
        it('creates a valid image from header data and blocks', () => {
            const header = read2MGHeader(VALID_PRODOS_IMAGE.buffer);
            const { prefix, suffix } = create2MGFragments(header, { blocks: header.bytes / 512 });
            expect(prefix).toEqual(VALID_PRODOS_IMAGE.slice(0, 64));
            expect(suffix).toEqual(new Uint8Array());
        });

        it('throws an error if block count does not match byte count', () => {
            const headerData: HeaderData = {
                creator: 'A2JS',
                bytes: 32768,
                format: FORMAT.ProDOS,
                readOnly: false,
                offset: 64,
                volume: 0,
            };
            expect(() => create2MGFragments(headerData, { blocks: 63 })).toThrowError(/does not match/);
        });

        it('throws an error if not a ProDOS volume', () => {
            const headerData: HeaderData = {
                creator: 'A2JS',
                bytes: 143_360,
                format: FORMAT.DOS,
                readOnly: false,
                offset: 64,
                volume: 254,
            };
            expect(() => create2MGFragments(headerData, { blocks: 280 })).toThrowError(/not supported/);
        });

        it('uses defaults', () => {
            const { prefix, suffix } = create2MGFragments(null, { blocks: 280 });
            const image = concat(prefix, BYTES_BY_SECTOR_IMAGE, suffix);
            const headerData = read2MGHeader(image.buffer);
            expect(headerData).toEqual({
                creator: 'A2JS',
                bytes: 143_360,
                format: FORMAT.ProDOS,
                readOnly: false,
                offset: 64,
                volume: 0,
            });
        });

        it.each([
            ['Hello, sailor', undefined],
            ['Hieyz wizka', new Uint8Array([4, 3, 2, 1])],
            [undefined, new Uint8Array([4, 3, 2, 1])]
        ])('can create comment %p and creator data %p', (testComment, testData) => {
            const headerData: HeaderData = {
                creator: 'A2JS',
                bytes: 0,
                format: FORMAT.ProDOS,
                readOnly: false,
                offset: 64,
                volume: 254,
            };
            if (testComment) {
                headerData.comment = testComment;
            }
            if (testData) {
                headerData.creatorData = testData;
            }
            const { prefix, suffix } = create2MGFragments(headerData, { blocks: 0 });
            const image = concat(prefix, suffix);
            const { comment, creatorData } = read2MGHeader(image.buffer);
            expect(comment).toEqual(testComment);
            expect(creatorData).toEqual(testData);
        });
    });

    describe('create2MGFromBlockDisk', () => {
        it('can create a 2mg disk', () => {
            const header = read2MGHeader(VALID_PRODOS_IMAGE.buffer);
            const blocks = [];
            for (let idx = 0; idx < BYTES_BY_SECTOR_IMAGE.length; idx += 512) {
                blocks.push(BYTES_BY_SECTOR_IMAGE.slice(idx, idx + 512));
            }
            const disk: BlockDisk = {
                blocks,
                metadata: { name: 'Good disk' },
                readOnly: false,
                encoding: ENCODING_BLOCK,
                format: 'hdv',
            };
            const image = create2MGFromBlockDisk(header, disk);
            expect(VALID_PRODOS_IMAGE.buffer).toEqual(image);
        });
    });
});
