/**
 * ImageDataProcessor module
 *
 * Handles low-level image data processing:
 * - Endian byte swapping for cross-platform compatibility
 * - Data type conversion from NIfTI format codes to TypeScript typed arrays
 *
 * This module contains pure functions for transforming raw image data
 * according to NIfTI header specifications.
 */

import { NIFTI1, NIFTI2 } from 'nifti-reader-js'
import type { TypedVoxelArray } from './index'
import { NiiDataType, isPlatformLittleEndian } from '@/nvimage/utils'

/**
 * Result of data type conversion, containing the converted image data
 * and potentially updated header fields.
 */
export interface DataTypeConversionResult {
    img: TypedVoxelArray
    imaginary?: Float32Array
    updatedDatatypeCode?: number
    updatedNumBitsPerVoxel?: number
}

/**
 * Swap byte order of multi-byte data if foreign endian.
 * Modifies the ArrayBuffer in place.
 *
 * @param imgRaw - Raw image data buffer
 * @param hdr - NIfTI header containing datatype and endianness information
 */
export function swapBytesIfNeeded(imgRaw: ArrayBufferLike, hdr: NIFTI1 | NIFTI2): void {
    // No swapping needed for RGB formats or single-byte data
    if (hdr.datatypeCode === NiiDataType.DT_RGB24 || hdr.datatypeCode === NiiDataType.DT_RGBA32 || hdr.littleEndian === isPlatformLittleEndian() || hdr.numBitsPerVoxel <= 8) {
        return
    }

    if (hdr.numBitsPerVoxel === 16) {
        // inspired by https://github.com/rii-mango/Papaya
        const u16 = new Uint16Array(imgRaw)
        for (let i = 0; i < u16.length; i++) {
            const val = u16[i]
            u16[i] = ((((val & 0xff) << 8) | ((val >> 8) & 0xff)) << 16) >> 16 // since JS uses 32-bit when bit shifting
        }
    } else if (hdr.numBitsPerVoxel === 32) {
        // inspired by https://github.com/rii-mango/Papaya
        const u32 = new Uint32Array(imgRaw)
        for (let i = 0; i < u32.length; i++) {
            const val = u32[i]
            u32[i] = ((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff)
        }
    } else if (hdr.numBitsPerVoxel === 64) {
        // inspired by MIT licensed code: https://github.com/rochars/endianness
        const numBytesPerVoxel = hdr.numBitsPerVoxel / 8
        const u8 = new Uint8Array(imgRaw)
        for (let index = 0; index < u8.length; index += numBytesPerVoxel) {
            let offset = numBytesPerVoxel - 1
            for (let x = 0; x < offset; x++) {
                const theByte = u8[index + x]
                u8[index + x] = u8[index + offset]
                u8[index + offset] = theByte
                offset--
            }
        }
    }
}

/**
 * Convert raw image data to the appropriate TypedArray based on NIfTI datatype code.
 * Some data types are converted to simpler representations (e.g., INT8 → INT16).
 *
 * @param imgRaw - Raw image data buffer (after endian swapping if needed)
 * @param hdr - NIfTI header containing datatype information
 * @returns Conversion result with typed array and any header updates
 * @throws Error if datatype is not supported
 */
export function convertDataType(imgRaw: ArrayBufferLike, hdr: NIFTI1 | NIFTI2): DataTypeConversionResult {
    switch (hdr.datatypeCode) {
        case NiiDataType.DT_UINT8:
            return { img: new Uint8Array(imgRaw) }

        case NiiDataType.DT_INT16:
            return { img: new Int16Array(imgRaw) }

        case NiiDataType.DT_FLOAT32:
            return { img: new Float32Array(imgRaw) }

        case NiiDataType.DT_FLOAT64:
            return { img: new Float64Array(imgRaw) }

        case NiiDataType.DT_RGB24:
            return { img: new Uint8Array(imgRaw) }

        case NiiDataType.DT_UINT16:
            return { img: new Uint16Array(imgRaw) }

        case NiiDataType.DT_RGBA32:
            return { img: new Uint8Array(imgRaw) }

        case NiiDataType.DT_INT8: {
            // Convert INT8 to INT16 for easier handling
            const i8 = new Int8Array(imgRaw)
            const vx8 = i8.length
            const img = new Int16Array(vx8)
            for (let i = 0; i < vx8; i++) {
                img[i] = i8[i]
            }
            return {
                img,
                updatedDatatypeCode: NiiDataType.DT_INT16,
                updatedNumBitsPerVoxel: 16
            }
        }

        case NiiDataType.DT_BINARY: {
            // Convert binary (bit-packed) to UINT8
            const nvox = hdr.dims[1] * hdr.dims[2] * Math.max(1, hdr.dims[3]) * Math.max(1, hdr.dims[4])
            const img1 = new Uint8Array(imgRaw)
            const img = new Uint8Array(nvox)
            const lut = new Uint8Array(8)
            for (let i = 0; i < 8; i++) {
                lut[i] = Math.pow(2, i)
            }
            let i1 = -1
            for (let i = 0; i < nvox; i++) {
                const bit = i % 8
                if (bit === 0) {
                    i1++
                }
                if ((img1[i1] & lut[bit]) !== 0) {
                    img[i] = 1
                }
            }
            return {
                img,
                updatedDatatypeCode: NiiDataType.DT_UINT8,
                updatedNumBitsPerVoxel: 8
            }
        }

        case NiiDataType.DT_UINT32: {
            // Convert UINT32 to FLOAT64 (JavaScript number precision)
            const u32 = new Uint32Array(imgRaw)
            const vx32 = u32.length
            const img = new Float64Array(vx32)
            for (let i = 0; i < vx32 - 1; i++) {
                img[i] = u32[i]
            }
            return {
                img,
                updatedDatatypeCode: NiiDataType.DT_FLOAT64
            }
        }

        case NiiDataType.DT_INT32: {
            // Convert INT32 to FLOAT64 (JavaScript number precision)
            const i32 = new Int32Array(imgRaw)
            const vxi32 = i32.length
            const img = new Float64Array(vxi32)
            for (let i = 0; i < vxi32 - 1; i++) {
                img[i] = i32[i]
            }
            return {
                img,
                updatedDatatypeCode: NiiDataType.DT_FLOAT64
            }
        }

        case NiiDataType.DT_INT64: {
            // Convert INT64 to FLOAT64 (JavaScript number precision)
            const i64 = new BigInt64Array(imgRaw)
            const vx = i64.length
            const img = new Float64Array(vx)
            for (let i = 0; i < vx - 1; i++) {
                img[i] = Number(i64[i])
            }
            return {
                img,
                updatedDatatypeCode: NiiDataType.DT_FLOAT64
            }
        }

        case NiiDataType.DT_COMPLEX64: {
            // Saved as real/imaginary pairs: show real following fsleyes/MRIcroGL convention
            const f32 = new Float32Array(imgRaw)
            const nvx = Math.floor(f32.length / 2)
            const imaginary = new Float32Array(nvx)
            const img = new Float32Array(nvx)
            let r = 0
            for (let i = 0; i < nvx - 1; i++) {
                img[i] = f32[r]
                imaginary[i] = f32[r + 1]
                r += 2
            }
            return {
                img,
                imaginary,
                updatedDatatypeCode: NiiDataType.DT_FLOAT32
            }
        }

        default:
            throw new Error('datatype ' + hdr.datatypeCode + ' not supported')
    }
}
