const $i = document.getElementById.bind(document);
const $c = document.getElementsByClassName.bind(document);
const $q = document.querySelector.bind(document);
const $qa = document.querySelectorAll.bind(document);
const $t = document.createTextNode.bind(document);

const $r = function(element) {
    if(element && element.parentNode)
        element.parentNode.removeChild(element);
};

const $ri = function(name) {
    $r($i(name));
};

const $ib = function(ref, elem) {
    ref.parentNode.insertBefore(elem, ref);
};

const $rc = function(element) {
    while(element.lastChild)
        element.removeChild(element.lastChild);
};

const $e = function(info, attrs, child, created) {
    info = info || {};

    if(typeof info === 'string') {
        info = {tag: info};
        if(attrs)
            info.attrs = attrs;
        if(child)
            info.child = child;
        if(created)
            info.created = created;
    }

    const elem = document.createElement(info.tag || 'div');

    if(info.attrs) {
        const attrs = info.attrs;

        for(let key in attrs) {
            const attr = attrs[key];
            if(attr === undefined || attr === null)
                continue;

            switch(typeof attr) {
                case 'function':
                    if(key.substring(0, 2) === 'on')
                        key = key.substring(2).toLowerCase();
                    elem.addEventListener(key, attr);
                    break;

                case 'object':
                    if(attr instanceof Array) {
                        if(key === 'class')
                            key = 'classList';

                        const prop = elem[key];
                        let addFunc = null;

                        if(prop instanceof Array)
                            addFunc = prop.push.bind(prop);
                        else if(prop instanceof DOMTokenList)
                            addFunc = prop.add.bind(prop);

                        if(addFunc !== null) {
                            for(let j = 0; j < attr.length; ++j)
                                addFunc(attr[j]);
                        } else {
                            if(key === 'classList')
                                key = 'class';
                            elem.setAttribute(key, attr.toString());
                        }
                    } else {
                        for(const attrKey in attr)
                            elem[key][attrKey] = attr[attrKey];
                    }
                    break;

                default:
                    if(key === 'className')
                        key = 'class';
                    elem.setAttribute(key, attr.toString());
                    break;
            }
        }
    }

    if(info.child) {
        let children = info.child;

        if(!Array.isArray(children))
            children = [children];

        for(const child of children) {
            switch(typeof child) {
                case 'string':
                    elem.appendChild($t(child));
                    break;

                case 'object':
                    if(child instanceof Element)
                        elem.appendChild(child);
                    else if(child.getElement) {
                        const childElem = child.getElement();
                        if(childElem instanceof Element)
                            elem.appendChild(childElem);
                        else
                            elem.appendChild($e(child));
                    } else
                        elem.appendChild($e(child));
                    break;

                default:
                    elem.appendChild($t(child.toString()));
                    break;
            }
        }
    }

    if(info.created)
        info.created(elem);

    return elem;
};
const $er = (type, props, ...children) => $e({ tag: type, attrs: props, child: children });

const $ar = function(array, index) {
    array.splice(index, 1);
};
const $ari = function(array, item) {
    let index;
    while(array.length > 0 && (index = array.indexOf(item)) >= 0)
        $ar(array, index);
};
const $arf = function(array, predicate) {
    let index;
    while(array.length > 0 && (index = array.findIndex(predicate)) >= 0)
        $ar(array, index);
};

const $as = function(array) {
    if(array.length < 2)
        return;

    for(let i = array.length - 1; i > 0; --i) {
        let j = Math.floor(Math.random() * (i + 1)),
            tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
};

var $insertTags = function(target, tagOpen, tagClose) {
    tagOpen = tagOpen || '';
    tagClose = tagClose || '';

    if(document.selection) {
        target.focus();
        var selected = document.selection.createRange();
        selected.text = tagOpen + selected.text + tagClose;
        target.focus();
    } else if(target.selectionStart || target.selectionStart === 0) {
        var startPos = target.selectionStart,
            endPos = target.selectionEnd,
            scrollTop = target.scrollTop;

        target.value = target.value.substring(0, startPos)
            + tagOpen
            + target.value.substring(startPos, endPos)
            + tagClose
            + target.value.substring(endPos, target.value.length);

        target.focus();
        target.selectionStart = startPos + tagOpen.length;
        target.selectionEnd = endPos + tagOpen.length;
        target.scrollTop + scrollTop;
    } else {
        target.value += tagOpen + tagClose;
        target.focus();
    }
};
