Mapeate.Vistas = function(mapeate, entradas){

    const self = this, 
          vistas = {};
    let iniciado = false, 
        sobreescribir_por_defecto, 
        elemento_de_arrastre = null, 
        arrastrar_hilo;

    const constructor = () => {

        mapeate.print("info", "mapeate_vistas_construyendose");

        mapeate.print("ok", "mapeate_vistas_construido");
    
    };

    this.iniciar = callback => {

        const terminar = estado => typeof callback == "function" && callback(estado);

        mapeate.print("info", "mapeate_vistas_iniciando");

        if(iniciado){
            mapeate.print("warn", "mapeate_vistas_ya_iniciado");
            terminar(false);
            return false;
        };
        iniciado = true;

        sobreescribir_por_defecto = mapeate.configuracion(["sobreescribir_vistas", "sobreescribir"])

        mapeate.ejecutar_array_asincrono([
            "archivos_de_vistas_por_defecto", 
            "archivos_de_vistas"
        ], (clave, callback) => self.cargar(mapeate.configuracion(clave), true, callback), () => {
            mapeate.print("ok", "mapeate_vistas_iniciado");
            arrastrar_hilo = mapeate.anadir_hilo_de_proceso(arrastrar);
            terminar(true);
        });

        return true;
    };

    const arrastrar = () => {

        if(elemento_de_arrastre){

            const cursor = mapeate.base.coger_posicion_del_raton();

            elemento_de_arrastre.elemento.style.marginTop = (cursor.y + elemento_de_arrastre.y) + "px";
            if(elemento_de_arrastre.left)
                elemento_de_arrastre.elemento.style.marginLeft = (cursor.x + elemento_de_arrastre.x) + "px";
            else 
                elemento_de_arrastre.elemento.style.marginRight = (
                    elemento_de_arrastre.elemento.parentNode.offsetWidth - cursor.x - elemento_de_arrastre.x
                ) + "px";

        };
        
    };

    const cargar = (entradas, sobreescribir, callback, i) => {

        if(!entradas || i >= entradas.length){
            typeof callback == "function" && callback();
            return;
        };

        const terminar = () => cargar(entradas, sobreescribir, callback, i + 1);

        if(!entradas[i]){
            terminar();
            return;
        };

        if(typeof entradas[i] == "object"){
            if(entradas[i] instanceof Array)
                cargar(entradas[i], sobreescribir, terminar, 0);
            else{
                for(const clave in entradas[i])
                    if(typeof entradas[i] == "string" && (sobreescribir || vistas[clave] === undefined))
                        vistas[clave] = entradas[i];
                terminar();
            };
        }else if(typeof entradas[i] == "string"){
            if(!(entradas[i] = entradas[i].trim())){
                terminar();
                return;
            };

            if(/^(\/|[a-z0-9]{3,5}\:\/{2})[^\r\n]*$/i.test(entradas[i]))
                mapeate.leer_archivo(entradas[i], salida => cargar([salida], sobreescribir, terminar, 0));
            else{

                if(/^(\[(.|[\r|\n])*\]|\{(.|[\n\n])*\})$/.test(entradas[i]) && mapeate.procesar_json(
                    entradas[i], 
                    json => cargar(json instanceof Array ? json : [json], sobreescribir, terminar, 0), 
                    terminar
                ))
                    return;

                while(entradas[i].length){
                    
                    const matches = entradas[i].match(/<\!\-{2} *\[{2}([^\[\]]+)\]{2} *\-{2}>/);

                    if(!matches)
                        break;

                    const l = (entradas[i] = entradas[i].substring(matches.index + matches[0].length)).indexOf(matches[0]);

                    if(l == -1)
                        continue;
                    
                    if(sobreescribir || vistas[matches[1]] === undefined)
                        vistas[matches[1]] = entradas[i].substring(0, l);
                    entradas[i] = entradas[i].substring(l + matches[0].length);

                };

                terminar();

            };

        }else 
            terminar();

    };

    this.cargar = (entradas, sobreescribir, callback) => cargar(
        typeof entradas == "object" && entradas instanceof Array ? entradas : [entradas], 
        typeof sobreescribir == "boolean" ? sobreescribir : sobreescribir_por_defecto, 
        callback, 
        0
    );

    this.crear_elementos_de_menu = (vista, opciones) => opciones.map(opcion => mapeate.string_variables(vistas[vista], {
        ...opcion, 
        ...(opcion.onclick ? {
            link : "#", 
            onclick : mapeate.string_variables(opcion.onclick)
        } : {
            onclick : "null"
        }), 
        ...(opcion.target ? {} : {target : "_self"}), 
        ...(typeof opcion.visible == "boolean" ? {} : {visible : true}), 
        nombre_texto : mapeate.i18n(opcion.nombre, opcion)
    })).join("");

    this.establecer = (posicion, vista, entradas, callback) => mapeate.precargar(posicion, posicion => {
        
        const precarga_hash = mapeate.hash();

        posicion.innerHTML = mapeate.string_variables(
            (vistas[vista] || vista) + '<div data-precarga="' + precarga_hash + '"></div>', 
            entradas
        );

        mapeate.precargar("div[data-precarga=" + precarga_hash + "]", elemento => {
            elemento.remove();
            mapeate.ejecutar(callback);
        });

    });

    this.coger = (claves, variables) => {

        const l = (claves = mapeate.coger_array_de_strings(claves)).length;
        let vista = null;

        for(let i = 0; i < l; i ++)
            if(vistas[claves[i]] !== undefined){
                vista = vistas[claves[i]];
                break;
            };

        return mapeate.string_variables(vista === null ? claves[0] || null : vista, variables);
    };

    this.posicionar = (elemento, evento) => {

        elemento.parentNode.querySelectorAll("[data-seleccionado=true]").forEach(seleccionado => seleccionado.setAttribute("data-seleccionado", false));
        elemento.setAttribute("data-seleccionado", true);
        elemento.parentNode.parentNode.setAttribute("Data-posicion", elemento.getAttribute("data-i18n"));

    };

    this.arrastrar = (elemento, evento) => {
        evento.preventDefault();

        const [x, y] = mapeate.base.coger_posicion_del_cursor(evento), 
              caja = elemento.parentNode.parentNode.getBoundingClientRect(), 
              limites = elemento.parentNode.getBoundingClientRect(), 
              is_left = elemento.parentNode.getAttribute("data-posicion").includes("_izquierda");
        let z = 0;
        
        elemento_de_arrastre = {
            elemento : elemento.parentNode, 
            x : (
                is_left ? limites.x - x - caja.x : 
                limites.x + elemento.parentNode.offsetWidth - x - caja.x
            ), 
            y : limites.y - y - caja.y, 
            left : is_left
        };

        elemento.parentNode.parentNode.childNodes.forEach(hijo => {
            if(hijo && hijo.tagName){
                
                const valor = Number(window.getComputedStyle(hijo).getPropertyValue("z-index"));

                valor && z < valor && (z = valor);

            };
        });

        elemento.parentNode.style.zIndex = z + 1;

    };

    this.soltar = (elemento, evento) => {
        evento.preventDefault();
        elemento_de_arrastre = null;
    };
 
    constructor();

};