const MszLoadingIcon = function() {
    const element = <div class="msz-loading-icon"/>;
    for(let i = 0; i < 9; ++i)
        element.appendChild(<div class="msz-loading-icon-block"/>);

    // this is moderately cursed but it'll do
    const blocks = [
        element.children[3],
        element.children[0],
        element.children[1],
        element.children[2],
        element.children[5],
        element.children[8],
        element.children[7],
        element.children[6],
    ];

    let tsLastUpdate;
    let counter = 0;
    let playing = false;
    let delay = 50;
    let playResolve;
    let pauseResolve;

    const update = tsCurrent => {
        try {
            if(tsLastUpdate !== undefined && (tsCurrent - tsLastUpdate) < delay)
                return;
            tsLastUpdate = tsCurrent;

            for(let i = 0; i < blocks.length; ++i)
                blocks[(counter + i) % blocks.length].classList.toggle('msz-loading-icon-block-hidden', i < 3);

            ++counter;
        } finally {
            if(playResolve)
                try {
                    playResolve();
                } finally {
                    playResolve = undefined;
                    playing = true;
                }

            if(pauseResolve)
                try {
                    pauseResolve();
                } finally {
                    pauseResolve = undefined;
                    playing = false;
                }

            if(playing)
                requestAnimationFrame(update);
        }
    };

    const play = () => {
        return new Promise(resolve => {
            if(playing || playResolve) {
                resolve();
                return;
            }

            playResolve = resolve;
            requestAnimationFrame(update);
        });
    };
    const pause = () => {
        return new Promise(resolve => {
            if(!playing || pauseResolve) {
                resolve();
                return;
            }

            pauseResolve = resolve;
        });
    };
    const stop = async () => {
        await pause();
        counter = 0;
    };
    const restart = async () => {
        await stop();
        await play();
    };
    const reverse = () => {
        blocks.reverse();
    };
    const setBlock = (num, state=null) => {
        element.children[num].classList.toggle('msz-loading-icon-block-hidden', !state);
    };
    const batsu = () => {
        setBlock(0, true);setBlock(1, false);setBlock(2, true);
        setBlock(3, false);setBlock(4, true);setBlock(5, false);
        setBlock(6, true);setBlock(7, false);setBlock(8, true);
    };
    const maru = () => {
        setBlock(0, true);setBlock(1, true);setBlock(2, true);
        setBlock(3, true);setBlock(4, false);setBlock(5, true);
        setBlock(6, true);setBlock(7, true);setBlock(8, true);
    };

    return {
        get element() { return element; },
        get playing() { return playing; },
        get delay() { return delay; },
        set delay(value) {
            if(typeof value !== 'number')
                value = parseFloat(value);
            if(isNaN(value) || !isFinite(value))
                return;
            if(value < 0)
                value = Math.abs(value);
            delay = value;
        },

        play,
        pause,
        stop,
        restart,
        reverse,
        batsu,
        maru,
    };
};

const MszLoading = function(options=null) {
    if(typeof options !== 'object')
        throw 'options must be an object';

    let {
        element, size, colour,
        width, height, inline,
        containerWidth, containerHeight,
        gap, margin, hidden,
    } = options ?? {};

    if(typeof element === 'string')
        element = document.querySelector(element);
    if(!(element instanceof HTMLElement))
        element = <div class="msz-loading"/>;

    if(!element.classList.contains('msz-loading'))
        element.classList.add('msz-loading');
    if(inline)
        element.classList.add('msz-loading-inline');
    if(hidden)
        element.classList.add('hidden');

    if(typeof size === 'number' && size > 0)
        element.style.setProperty('--msz-loading-size', size);
    if(typeof containerWidth === 'string')
        element.style.setProperty('--msz-loading-container-width', containerWidth);
    if(typeof containerHeight === 'string')
        element.style.setProperty('--msz-loading-container-height', containerHeight);
    if(typeof gap === 'string')
        element.style.setProperty('--msz-loading-gap', gap);
    if(typeof margin === 'string')
        element.style.setProperty('--msz-loading-margin', margin);
    if(typeof width === 'string')
        element.style.setProperty('--msz-loading-width', width);
    if(typeof height === 'string')
        element.style.setProperty('--msz-loading-height', height);
    if(typeof colour === 'string')
        element.style.setProperty('--msz-loading-colour', colour);

    let icon;
    if(element.childElementCount < 1) {
        icon = new MszLoadingIcon;
        icon.play();
        element.appendChild(<div class="msz-loading-frame">{icon}</div>);
    }

    return {
        get element() { return element; },

        get hasIcon() { return icon !== undefined; },
        get icon() { return icon; },

        get visible() { return !element.classList.contains('hidden'); },
        set visible(state) { element.classList.toggle('hidden', !state); },
    };
};