#include args.js
#include utility.js

const MamiViewsControl = function(options) {
    options = MamiArguments.verify(options, [
        MamiArguments.check('body', undefined, value => value instanceof Element, true),
    ]);

    const targetBody = options.body;
    targetBody.classList.toggle('views', true);

    const views = [];

    const extractElement = elementInfo => {
        let element;
        if(elementInfo instanceof Element) {
            element = elementInfo;
        } else if('getElement' in elementInfo) {
            element = elementInfo.getElement();
        } else throw 'elementInfo is not a valid type';

        if(!(element instanceof Element))
            throw 'element is not an instance of Element';
        if(element === targetBody)
            throw 'element may not be the same as targetBody';

        return element;
    };

    const doTransition = async (transition, ctx) => {
        if(transition === undefined)
            return;

        if(typeof transition !== 'function')
            return;

        await transition(ctx);
    };

    const updateZIncides = () => {
        let index = 0;
        for(const view of views)
            extractElement(view).style.zIndex = (++index).toString();
    };

    const push = async (elementInfo, transition) => {
        if(typeof elementInfo !== 'object')
            throw 'elementInfo must be an object';

        if(!views.includes(elementInfo))
            views.push(elementInfo);
        if(typeof elementInfo.onViewPush === 'function')
            await elementInfo.onViewPush();

        const element = extractElement(elementInfo);
        element.classList.toggle('views-item', true);
        element.classList.toggle('views-background', false);

        if(!targetBody.contains(element))
            targetBody.appendChild(element);

        if(typeof elementInfo.onViewForeground === 'function')
            await elementInfo.onViewForeground();

        updateZIncides();

        if(views.length > 1) {
            const prevElemInfo = views[views.length - 2];
            const prevElem = extractElement(prevElemInfo);

            prevElem.classList.toggle('views-background', true);

            if(typeof prevElemInfo.onViewBackground === 'function')
                await prevElemInfo.onViewBackground();

            await doTransition(transition, {
                toInfo: elementInfo,
                toElem: element,
                fromInfo: prevElemInfo,
                fromElem: prevElem,
            });
        }
    };

    const pop = async transition => {
        const elementInfo = views.pop();
        if(elementInfo === undefined)
            throw 'views is empty';

        const element = extractElement(elementInfo);

        element.classList.toggle('views-background', true);

        if(views.length > 0) {
            const nextElemInfo = views[views.length - 1];
            const nextElem = extractElement(nextElemInfo);

            nextElem.classList.toggle('views-background', false);

            if(typeof nextElemInfo.onViewForeground === 'function')
                await nextElemInfo.onViewForeground();

            await doTransition(transition, {
                toInfo: nextElemInfo,
                toElem: nextElem,
                fromInfo: elementInfo,
                fromElem: element,
            });
        }

        if(typeof elementInfo.onViewBackground === 'function')
            await elementInfo.onViewBackground();

        if(targetBody.contains(element))
            targetBody.removeChild(element);

        if(typeof elementInfo.onViewPop === 'function')
            await elementInfo.onViewPop();

        updateZIncides();

        return elementInfo;
    };

    const current = () => {
        if(views.length < 1)
            return undefined;

        return views[views.length - 1];
    };

    const raise = async (elementInfo, transition) => {
        if(typeof elementInfo !== 'object')
            throw 'elementInfo must be an object';

        if(current() === elementInfo)
            return;

        const index = views.indexOf(elementInfo);
        if(index >= 0)
            views.splice(index, 1);

        return await push(elementInfo, transition);
    };

    return {
        push: push,
        pop: pop,
        raise: raise,
        count: () => views.length,
        current: current,
        currentElement: () => {
            const currentInfo = current();
            if(currentInfo === undefined)
                return undefined;

            return extractElement(currentInfo);
        },
        includes: elementInfo => views.includes(elementInfo),
        isCurrent: elementInfo => current() === elementInfo,
        unshift: async elementInfo => {
            if(views.length < 1)
                return await push(elementInfo, null);

            if(typeof elementInfo !== 'object')
                throw 'elementInfo must be an object';

            const element = extractElement(elementInfo);

            if(!views.includes(elementInfo))
                views.unshift(elementInfo);

            element.classList.toggle('views-item', true);
            element.classList.toggle('views-background', true);

            if(!targetBody.contains(element))
                targetBody.appendChild(element);

            updateZIncides();
        },
        shift: async () => {
            if(views.length < 2)
                return await pop(null);

            const elementInfo = views.shift();
            const element = extractElement(elementInfo);

            if(targetBody.contains(element))
                targetBody.removeChild(element);

            if(typeof elementInfo.onViewPop === 'function')
                await elementInfo.onViewPop();

            updateZIncides();

            return elementInfo;
        },
    };
};
