/**
 * TensorProcessing module
 *
 * Handles diffusion tensor and vector field processing.
 * This module contains functions for:
 * - Converting vector fields to RGBA representation
 * - Loading and processing V1 vector data with optional flips
 */

import type { NVImage } from './index'
import { log } from '@/logger'
import { NiiDataType } from '@/nvimage/utils'

/**
 * Convert vector field from Float32 to RGBA representation.
 * Note: We use RGBA rather than RGB and use least significant bits to store vector polarity.
 * This allows a single bitmap to store BOTH (unsigned) color magnitude and signed vector direction.
 *
 * @param nvImage - The NVImage instance
 * @param inImg - Input Float32Array containing vector field data
 * @returns Uint8Array with RGBA encoded vector data
 */
export function float32V1asRGBA(nvImage: NVImage, inImg: Float32Array): Uint8Array {
    if (inImg.length !== nvImage.nVox3D * 3) {
        log.warn('float32V1asRGBA() expects ' + nvImage.nVox3D * 3 + 'voxels, got ', +inImg.length)
    }
    const f32 = inImg.slice()
    // Note we will use RGBA rather than RGB and use least significant bits to store vector polarity
    // this allows a single bitmap to store BOTH (unsigned) color magnitude and signed vector direction
    nvImage.hdr.datatypeCode = NiiDataType.DT_RGBA32
    nvImage.nFrame4D = 1
    for (let i = 4; i < 7; i++) {
        nvImage.hdr.dims[i] = 1
    }
    nvImage.hdr.dims[0] = 3 // 3D
    const imgRaw = new Uint8Array(nvImage.nVox3D * 4) //* 3 for RGB
    let mx = 1.0
    for (let i = 0; i < nvImage.nVox3D * 3; i++) {
        // n.b. NaN values created by dwi2tensor and tensor2metric tensors.mif -vector v1.mif
        if (isNaN(f32[i])) {
            continue
        }
        mx = Math.max(mx, Math.abs(f32[i]))
    }
    const slope = 255 / mx
    const nVox3D2 = nvImage.nVox3D * 2
    let j = 0
    for (let i = 0; i < nvImage.nVox3D; i++) {
        // n.b. it is really necessary to overwrite imgRaw with a new datatype mid-method
        const x = f32[i]
        const y = f32[i + nvImage.nVox3D]
        const z = f32[i + nVox3D2]
        ;(imgRaw as Uint8Array)[j] = Math.abs(x * slope)
        ;(imgRaw as Uint8Array)[j + 1] = Math.abs(y * slope)
        ;(imgRaw as Uint8Array)[j + 2] = Math.abs(z * slope)
        const xNeg = Number(x > 0) * 1
        const yNeg = Number(y > 0) * 2
        const zNeg = Number(z > 0) * 4
        let alpha = 248 + xNeg + yNeg + zNeg
        if (Math.abs(x) + Math.abs(y) + Math.abs(z) < 0.1) {
            alpha = 0
        }
        ;(imgRaw as Uint8Array)[j + 3] = alpha
        j += 4
    }
    return imgRaw
}

/**
 * Load and process diffusion tensor vector (V1) data with optional flips.
 * The vectors must be of unit length.
 * Modifies the nvImage.img property with the processed RGBA data.
 *
 * @param nvImage - The NVImage instance
 * @param isFlipX - Flip X component (default: false)
 * @param isFlipY - Flip Y component (default: false)
 * @param isFlipZ - Flip Z component (default: false)
 * @example nv1.loadVolumes(volumeList); nv1.volumes[1].loadImgV1();
 * @returns true if successful, false if V1 data is not available
 * @see {@link https://niivue.com/demos/features/modulate.html | live demo usage}
 */
export function loadImgV1(nvImage: NVImage, isFlipX: boolean = false, isFlipY: boolean = false, isFlipZ: boolean = false): boolean {
    let v1 = nvImage.v1
    if (!v1 && nvImage.nFrame4D === 3 && nvImage.img.constructor === Float32Array) {
        v1 = nvImage.img.slice()
    }
    if (!v1) {
        log.warn('Image does not have V1 data')
        return false
    }
    if (isFlipX) {
        for (let i = 0; i < nvImage.nVox3D; i++) {
            v1[i] = -v1[i]
        }
    }
    if (isFlipY) {
        for (let i = nvImage.nVox3D; i < 2 * nvImage.nVox3D; i++) {
            v1[i] = -v1[i]
        }
    }
    if (isFlipZ) {
        for (let i = 2 * nvImage.nVox3D; i < 3 * nvImage.nVox3D; i++) {
            v1[i] = -v1[i]
        }
    }
    nvImage.img = float32V1asRGBA(nvImage, v1)
    return true
}
