import type { byte, memory, MemberOf, word } from '../types';
import type { GamepadConfiguration } from '../ui/types';
import { InfoChunk } from './woz';

export const SUPPORTED_SECTORS = [13, 16] as const;
export type SupportedSectors = MemberOf<typeof SUPPORTED_SECTORS>;

export const DRIVE_NUMBERS = [1, 2] as const;
export type DriveNumber = MemberOf<typeof DRIVE_NUMBERS>;

/**
 * Arguments for the disk format processors.
 */
export interface DiskOptions {
    name: string;
    side?: string | undefined;
    volume: byte;
    readOnly: boolean;
    data?: memory[][];
    rawData?: ArrayBuffer;
    blockVolume?: boolean;
}

/**
 * JSON file entry format
 */
export interface DiskDescriptor {
    name: string;
    disk?: number;
    filename: string;
    e?: boolean;
    category: string;
}

/**
 * JSON binary image (not used?)
 */
export interface JSONBinaryImage {
    type: 'binary';
    start: word;
    length: word;
    data: byte[];
    gamepad?: GamepadConfiguration;
}

/**
 * Information about a disk image not directly related to the
 * disk contents. For example, the name or even a scan of the
 * disk label are "metadata", but the volume number is not.
 */
export interface DiskMetadata {
    /** Displayed disk name */
    name: string;
    /** (Optional) Disk side (Front/Back, A/B) */
    side?: string | undefined;
}

/**
 * Return value from disk format processors. Describes raw disk
 * data which the DiskII card can process.
 */
export interface Disk {
    metadata: DiskMetadata;
    readOnly: boolean;
}

export const NO_DISK = 'empty';
export const ENCODING_NIBBLE = 'nibble';
export const ENCODING_BITSTREAM = 'bitstream';
export const ENCODING_BLOCK = 'block';

export interface FloppyDisk extends Disk {
    encoding: typeof ENCODING_NIBBLE | typeof ENCODING_BITSTREAM | typeof NO_DISK;
}

export interface NoFloppyDisk extends FloppyDisk {
    encoding: typeof NO_DISK;
}

export interface NibbleDisk extends FloppyDisk {
    encoding: typeof ENCODING_NIBBLE;
    format: Exclude<NibbleFormat, 'woz'>;
    volume: byte;
    tracks: memory[];
}

export interface WozDisk extends FloppyDisk {
    encoding: typeof ENCODING_BITSTREAM;
    format: 'woz';
    trackMap: number[];
    rawTracks: Uint8Array[];
    info: InfoChunk | undefined;
}

export interface BlockDisk extends Disk {
    encoding: typeof ENCODING_BLOCK;
    format: BlockFormat;
    blocks: Uint8Array[];
}

/**
 * File types supported by floppy devices in nibble mode.
 */
export const NIBBLE_FORMATS = [
    '2mg',
    'd13',
    'do',
    'dsk',
    'po',
    'nib',
] as const;

/**
 * File types supported by floppy devices in bitstream mode.
 */
export const BITSTREAM_FORMATS = [
    'woz',
] as const;

/**
 * All file types supported by floppy devices.
 */
export const FLOPPY_FORMATS = [
    ...NIBBLE_FORMATS,
    ...BITSTREAM_FORMATS,
] as const;

/**
 * File types supported by block devices.
 */
export const BLOCK_FORMATS = [
    '2mg',
    'hdv',
    'po',
] as const;

/**
 * All supported disk formats.
 */
export const DISK_FORMATS = [
    ...FLOPPY_FORMATS,
    ...BLOCK_FORMATS,
] as const;

export type FloppyFormat = MemberOf<typeof FLOPPY_FORMATS>;
export type NibbleFormat = MemberOf<typeof NIBBLE_FORMATS>;
export type BitstreamFormat = 'woz';
export type BlockFormat = MemberOf<typeof BLOCK_FORMATS>;
export type DiskFormat = MemberOf<typeof DISK_FORMATS>;

/** Type guard for nibble disk formats. */
export function isNibbleDiskFormat(f: DiskFormat): f is NibbleFormat {
    return NIBBLE_FORMATS.includes(f as NibbleFormat);
}

/** Type guard for block disk formats. */
export function isBlockDiskFormat(f: DiskFormat): f is BlockFormat {
    return BLOCK_FORMATS.includes(f as BlockFormat);
}

export function isNoFloppyDisk(disk: Disk): disk is NoFloppyDisk {
    return (disk as NoFloppyDisk)?.encoding === NO_DISK;
}

/** Type guard for NibbleDisks */
export function isNibbleDisk(disk: Disk): disk is NibbleDisk {
    return (disk as NibbleDisk)?.encoding === ENCODING_NIBBLE;
}

/** Type guard for NibbleDisks */
export function isWozDisk(disk: Disk): disk is WozDisk {
    return (disk as WozDisk)?.encoding === ENCODING_BITSTREAM;
}

/**
 * Base format for JSON defined disks
 */

export class JSONDiskBase {
    type: DiskFormat;
    name: string;
    disk?: string;
    category?: string;
    volume?: byte;
    readOnly?: boolean;
    gamepad?: GamepadConfiguration;
}

/**
 * JSON Disk format with base64 encoded tracks with sectors
 */

export interface Base64JSONDisk extends JSONDiskBase {
    type: Exclude<DiskFormat, 'nib'>;
    encoding: 'base64';
    data: string[][];
}

/**
 * JSON Disk format with base64 encoded nibblized tracks
 */

export interface Base64JSONNibbleDisk extends JSONDiskBase {
    type: 'nib';
    encoding: 'base64';
    data: string[];
}

/**
 * JSON Disk format with byte array tracks
 */

export interface BinaryJSONDisk extends JSONDiskBase {
    type: DiskFormat;
    encoding: 'binary';
    data: memory[][];
}

/**
 * General JSON Disk format
 */

export type JSONDisk = Base64JSONDisk | Base64JSONNibbleDisk | BinaryJSONDisk;

/**
 * Process Disk message payloads for worker
 */

export const PROCESS_BINARY = 'PROCESS_BINARY';
export const PROCESS_JSON_DISK = 'PROCESS_JSON_DISK';
export const PROCESS_JSON = 'PROCESS_JSON';

/** Binary disk file message */
export interface ProcessBinaryMessage {
    type: typeof PROCESS_BINARY;
    payload: {
        driveNo: DriveNumber;
        fmt: FloppyFormat;
        options: DiskOptions;
    };
}

/** Processed JSON file message (used for localStorage) */
export interface ProcessJsonDiskMessage {
    type: typeof PROCESS_JSON_DISK;
    payload: {
        driveNo: DriveNumber;
        jsonDisk: JSONDisk;
    };
}

/** Raw JSON file message */
export interface ProcessJsonMessage {
    type: typeof PROCESS_JSON;
    payload: {
        driveNo: DriveNumber;
        json: string;
    };
}

export type FormatWorkerMessage =
    ProcessBinaryMessage |
    ProcessJsonDiskMessage |
    ProcessJsonMessage;

/**
 * Format work result message type
 */

export const DISK_PROCESSED = 'DISK_PROCESSED';

export interface DiskProcessedResponse {
    type: typeof DISK_PROCESSED;
    payload: {
        driveNo: DriveNumber;
        disk: FloppyDisk | null;
    };
}

export type FormatWorkerResponse =
    DiskProcessedResponse;

export interface MassStorageData {
    metadata: DiskMetadata;
    ext: DiskFormat;
    readOnly: boolean;
    volume?: byte;
    data: ArrayBuffer;
}

/**
 * Block device common interface
 */
export interface MassStorage<T> {
    setBinary(drive: number, name: string, ext: T, data: ArrayBuffer): boolean;
    getBinary(drive: number, ext?: T): MassStorageData | null;
}
