import { assert } from "../../../../../core/assert.js";
import { EndianType } from "../../../../../core/binary/EndianType.js";
import { BinaryDataType } from "../../../../../core/binary/type/BinaryDataType.js";
import { DataTypeByteSizes } from "../../../../../core/binary/type/DataTypeByteSizes.js";
import { compute_binary_data_type_from_typed_array } from "../../../../../core/collection/array/typed/compute_binary_data_type_from_typed_array.js";
import { DataTypeIndices } from "../../../../../core/binary/type/DataTypeIndices.js";
import { Sampler2D } from "../Sampler2D.js";


/**
 *
 * @param {number} index
 * @returns {BinaryDataType}
 */
function dataTypeByTypeIndex(index) {
    for (let type in DataTypeIndices) {
        const typeIndex = DataTypeIndices[type];
        if (typeIndex === index) {
            return type;
        }
    }

    throw new Error(`No type found with index ${index}`);
}

/**
 *
 * @param {BinaryBuffer} buffer
 * @param {BinaryDataType} type
 * @param {number} length
 */
function readTypedArray(buffer, type, length) {
    const oldEndianness = buffer.endianness;
    buffer.endianness = EndianType.LittleEndian;

    let result = null;
    switch (type) {
        case BinaryDataType.Uint8:
            result = new Uint8Array(length);
            buffer.readUint8Array(result, 0, length);
            break;
        case BinaryDataType.Uint16:
            result = new Uint16Array(length);
            buffer.readUint16Array(result, 0, length);
            break;
        case BinaryDataType.Uint32:
            result = new Uint32Array(length);
            buffer.readUint32Array(result, 0, length);
            break;
        case BinaryDataType.Int8:
            result = new Int8Array(length);
            buffer.readInt8Array(result, 0, length);
            break;
        case BinaryDataType.Int16:
            result = new Int16Array(length);
            buffer.readInt16Array(result, 0, length);
            break;
        case BinaryDataType.Int32:
            result = new Int32Array(length);
            buffer.readInt32Array(result, 0, length);
            break;
        case BinaryDataType.Float32:
            result = new Float32Array(length);
            buffer.readFloat32Array(result, 0, length);
            break;
        case BinaryDataType.Float64:
            result = new Float64Array(length);
            buffer.readFloat64Array(result, 0, length);
            break;
        default:
            result = null;
    }

    //restore
    buffer.endianness = oldEndianness;

    if (result === null) {
        throw new TypeError(`Unsupported data type ${type}`);
    }

    return result;
}

/**
 *
 * @param {BinaryBuffer} buffer
 * @param {Sampler2D} texture
 */
export function serializeTexture(buffer, texture) {
    const width = texture.width;
    const height = texture.height;
    const itemSize = texture.itemSize;

    buffer.writeUint32(width);
    buffer.writeUint32(height);
    buffer.writeUint8(itemSize); //number of channels

    const type = compute_binary_data_type_from_typed_array(texture.data);
    buffer.writeUint8(DataTypeIndices[type]);
    const array = texture.data;

    const byteBuffer = array.buffer;

    if (byteBuffer === undefined) {
        throw new TypeError(`No buffer found on data array, only TypedArrays are supported. It appears that data array is not a TypedArray`);
    }

    const itemByteSize = DataTypeByteSizes[type];

    assert.isNumber(itemByteSize, 'itemByteSize');

    const byteSize = width * height * itemSize * itemByteSize;

    buffer.writeBytes(new Uint8Array(byteBuffer), 0, byteSize);
}


/**
 *
 * @param {BinaryBuffer} buffer
 * @returns {Sampler2D}
 */
export function deserializeTexture(buffer) {
    const width = buffer.readUint32();
    const height = buffer.readUint32();

    const itemSize = buffer.readUint8();

    const dataTypeId = buffer.readUint8();

    const type = dataTypeByTypeIndex(dataTypeId);

    const length = width * height * itemSize;

    const array = readTypedArray(buffer, type, length);


    return new Sampler2D(array, itemSize, width, height);
}
