import { LoadFileError } from "@babylonjs/core/Misc/fileTools";
import { Logger } from "@babylonjs/core/Misc/logger";
import { MmdAnimation } from "../Animation/mmdAnimation";
import { MmdBoneAnimationTrack, MmdCameraAnimationTrack, MmdMorphAnimationTrack, MmdMovableBoneAnimationTrack, MmdPropertyAnimationTrack } from "../Animation/mmdAnimationTrack";
import { MmdDataDeserializer } from "../Parser/mmdDataDeserializer";
/**
 * BvmdLoader is a loader that loads MMD animation data in BVMD format
 *
 * BVMD format is a optimized binary format for MMD animation data
 */
export class BvmdLoader {
    _scene;
    _loggingEnabled;
    /** @internal */
    log;
    /** @internal */
    warn;
    /** @internal */
    error;
    /**
     * Create a new BvmdLoader
     * @param scene Scene for loading file
     */
    constructor(scene) {
        this._loggingEnabled = false;
        this.log = this._logDisabled;
        this.warn = this._warnDisabled;
        this.error = this._errorDisabled;
        this._scene = scene;
    }
    /**
     * Load MMD animation data from BVMD array buffer
     * @param name Animation name
     * @param buffer BVMD array buffer
     * @returns Animation data
     * @throws {LoadFileError} when validation fails
     */
    loadFromBuffer(name, buffer) {
        const deserializer = new MmdDataDeserializer(buffer);
        deserializer.initializeTextDecoder("utf-8");
        const signature = deserializer.getDecoderString(4, false);
        if (signature !== "BVMD") {
            throw new LoadFileError("BVMD signature is not valid.");
        }
        const version = [deserializer.getInt8(), deserializer.getInt8(), deserializer.getInt8()];
        if (version[0] !== 2 || version[1] !== 0 || version[2] !== 0) {
            throw new LoadFileError(`BVMD version ${version[0]}.${version[1]}.${version[2]} is not supported.`);
        }
        const boneTrackCount = deserializer.getUint32();
        const boneTracks = new Array(boneTrackCount);
        for (let i = 0; i < boneTrackCount; ++i) {
            const trackName = deserializer.getDecoderString(deserializer.getUint32(), true);
            const frameCount = deserializer.getUint32();
            const frameNumberByteOffset = deserializer.getPaddedArrayOffset(4, frameCount);
            const rotationByteOffset = deserializer.getPaddedArrayOffset(4, frameCount * 4);
            const rotationInterpolationByteOffset = deserializer.getPaddedArrayOffset(1, frameCount * 4);
            const boneTrack = boneTracks[i] = new MmdBoneAnimationTrack(trackName, frameCount, buffer, frameNumberByteOffset, rotationByteOffset, rotationInterpolationByteOffset);
            if (!deserializer.isDeviceLittleEndian) {
                deserializer.swap32Array(boneTrack.frameNumbers);
                deserializer.swap32Array(boneTrack.rotations);
            }
        }
        const movableBoneTrackCount = deserializer.getUint32();
        const movableBoneTracks = new Array(movableBoneTrackCount);
        for (let i = 0; i < movableBoneTrackCount; ++i) {
            const trackName = deserializer.getDecoderString(deserializer.getUint32(), true);
            const frameCount = deserializer.getUint32();
            const frameNumberByteOffset = deserializer.getPaddedArrayOffset(4, frameCount);
            const positionByteOffset = deserializer.getPaddedArrayOffset(4, frameCount * 3);
            const positionInterpolationByteOffset = deserializer.getPaddedArrayOffset(1, frameCount * 12);
            const rotationByteOffset = deserializer.getPaddedArrayOffset(4, frameCount * 4);
            const rotationInterpolationByteOffset = deserializer.getPaddedArrayOffset(1, frameCount * 4);
            const movableBoneTrack = movableBoneTracks[i] = new MmdMovableBoneAnimationTrack(trackName, frameCount, buffer, frameNumberByteOffset, positionByteOffset, positionInterpolationByteOffset, rotationByteOffset, rotationInterpolationByteOffset);
            if (!deserializer.isDeviceLittleEndian) {
                deserializer.swap32Array(movableBoneTrack.frameNumbers);
                deserializer.swap32Array(movableBoneTrack.positions);
                deserializer.swap32Array(movableBoneTrack.rotations);
            }
        }
        const morphTrackCount = deserializer.getUint32();
        const morphTracks = new Array(morphTrackCount);
        for (let i = 0; i < morphTrackCount; ++i) {
            const trackName = deserializer.getDecoderString(deserializer.getUint32(), true);
            const frameCount = deserializer.getUint32();
            const frameNumberByteOffset = deserializer.getPaddedArrayOffset(4, frameCount);
            const weightByteOffset = deserializer.getPaddedArrayOffset(4, frameCount);
            const morphTrack = morphTracks[i] = new MmdMorphAnimationTrack(trackName, frameCount, buffer, frameNumberByteOffset, weightByteOffset);
            if (!deserializer.isDeviceLittleEndian) {
                deserializer.swap32Array(morphTrack.frameNumbers);
                deserializer.swap32Array(morphTrack.weights);
            }
        }
        const propertyFrameCount = deserializer.getUint32();
        const ikBoneNameCount = deserializer.getUint32();
        const propertyFrameNumberByteOffset = deserializer.getPaddedArrayOffset(4, propertyFrameCount);
        const propertyVisibleByteOffset = deserializer.getPaddedArrayOffset(1, propertyFrameCount);
        const propertyIkBoneNames = new Array(ikBoneNameCount);
        const propertyIkStateByteOffsets = new Array(ikBoneNameCount);
        for (let i = 0; i < ikBoneNameCount; ++i) {
            propertyIkBoneNames[i] = deserializer.getDecoderString(deserializer.getUint32(), true);
            propertyIkStateByteOffsets[i] = deserializer.getPaddedArrayOffset(1, propertyFrameCount);
        }
        const propertyTrack = new MmdPropertyAnimationTrack(propertyFrameCount, propertyIkBoneNames, buffer, propertyFrameNumberByteOffset, propertyVisibleByteOffset, propertyIkStateByteOffsets);
        if (!deserializer.isDeviceLittleEndian) {
            deserializer.swap32Array(propertyTrack.frameNumbers);
        }
        const cameraFrameCount = deserializer.getUint32();
        const cameraFrameNumberByteOffset = deserializer.getPaddedArrayOffset(4, cameraFrameCount);
        const cameraPositionByteOffset = deserializer.getPaddedArrayOffset(4, cameraFrameCount * 3);
        const cameraPositionInterpolationByteOffset = deserializer.getPaddedArrayOffset(1, cameraFrameCount * 12);
        const cameraRotationByteOffset = deserializer.getPaddedArrayOffset(4, cameraFrameCount * 3);
        const cameraRotationInterpolationByteOffset = deserializer.getPaddedArrayOffset(1, cameraFrameCount * 4);
        const cameraDistanceByteOffset = deserializer.getPaddedArrayOffset(4, cameraFrameCount);
        const cameraDistanceInterpolationByteOffset = deserializer.getPaddedArrayOffset(1, cameraFrameCount * 4);
        const cameraFovByteOffset = deserializer.getPaddedArrayOffset(4, cameraFrameCount);
        const cameraFovInterpolationByteOffset = deserializer.getPaddedArrayOffset(1, cameraFrameCount * 4);
        const cameraTrack = new MmdCameraAnimationTrack(cameraFrameCount, buffer, cameraFrameNumberByteOffset, cameraPositionByteOffset, cameraPositionInterpolationByteOffset, cameraRotationByteOffset, cameraRotationInterpolationByteOffset, cameraDistanceByteOffset, cameraDistanceInterpolationByteOffset, cameraFovByteOffset, cameraFovInterpolationByteOffset);
        if (!deserializer.isDeviceLittleEndian) {
            deserializer.swap32Array(cameraTrack.frameNumbers);
            deserializer.swap32Array(cameraTrack.positions);
            deserializer.swap32Array(cameraTrack.rotations);
            deserializer.swap32Array(cameraTrack.distances);
            deserializer.swap32Array(cameraTrack.fovs);
        }
        return new MmdAnimation(name, boneTracks, movableBoneTracks, morphTracks, propertyTrack, cameraTrack);
    }
    /**
     * Load MMD animation data from BVMD file or URL
     * @param name Animation name
     * @param fileOrUrl BVMD file or URL
     * @param onLoad Callback function that is called when load is complete
     * @param onProgress Callback function that is called while loading
     * @param onError Callback function that is called when loading is failed
     * @returns File request
     */
    load(name, fileOrUrl, onLoad, onProgress, onError) {
        return this._scene._loadFile(fileOrUrl, (data, _responseURL) => {
            onLoad(this.loadFromBuffer(name, data));
        }, onProgress, true, true, onError);
    }
    /**
     * Load MMD animation data from BVMD file or URL asynchronously
     * @param name Animation name
     * @param fileOrUrl BVMD file or URL
     * @param onProgress Callback function that is called while loading
     * @returns Animation data
     */
    loadAsync(name, fileOrUrl, onProgress) {
        return new Promise((resolve, reject) => {
            this.load(name, fileOrUrl, resolve, onProgress, (request, exception) => reject({ request, exception }));
        });
    }
    /**
     * Enable or disable debug logging (default: false)
     */
    get loggingEnabled() {
        return this._loggingEnabled;
    }
    set loggingEnabled(value) {
        this._loggingEnabled = value;
        if (value) {
            this.log = this._logEnabled;
            this.warn = this._warnEnabled;
            this.error = this._errorEnabled;
        }
        else {
            this.log = this._logDisabled;
            this.warn = this._warnDisabled;
            this.error = this._errorDisabled;
        }
    }
    _logEnabled(message) {
        Logger.Log(message);
    }
    _logDisabled() {
        // do nothing
    }
    _warnEnabled(message) {
        Logger.Warn(message);
    }
    _warnDisabled() {
        // do nothing
    }
    _errorEnabled(message) {
        Logger.Error(message);
    }
    _errorDisabled() {
        // do nothing
    }
}
