class UseUcpComponents {
    constructor() {
        this.ucpComponents = {};
        this.ucpComponents.array = [];
        //		this.callbackArray = [];
        this.weBeanOnLoadListener = [];

        this.dependencies = [
            "/EAMD.ucp/Components/com/ceruleanCircle/EAM/3_services/WebBeans/0.4.0/WebBeans.component.xml"
        ];

        var pathElements = this.dependencies[0].split("/");
        this.EAMDRootDirName = pathElements[1];
        this.componentRootDirName = pathElements[2];
        this.EAMDRootDir = "/" + this.EAMDRootDirName;
        this.componentRootDir = this.EAMDRootDir + "/" + this.componentRootDirName;

        this.allViews = new IndexedObjects();
        this.allViews.indexAttribute = "name";
        this.isDone = false;
        this.logging = console.log;
    }

    init() {

        var links = document.getElementsByTagName("link");


        var saveLinkIterator = [];
        for (var i = 0; i < links.length; i++)
            if (links[i].getAttribute("rel") === "ucpComponent")
                saveLinkIterator[saveLinkIterator.length] = links[i];


        for (var i in this.dependencies) {
            this.importDependency(this.dependencies[i]);
        }

        for (var i = 0; i < saveLinkIterator.length; i++) {
            if (saveLinkIterator[i].getAttribute("rel") === "ucpComponent") {
                this.importDependency(saveLinkIterator[i].getAttribute("href"));
            }
        }


        return this;
    }


    importDependency(path) {
        var controller = new Controller().init(path)
        var component = new UcpComponent().init(controller);
        component.index = this.ucpComponents.length;

        this.ucpComponents.array[this.ucpComponents.array.length] = component;
        this.ucpComponents[component.name] = component;

        controller.import();
    }


    lookup(name) {
        return this.ucpComponents[name]
    }

    lookupView(name) {
        var index = name.indexOf("[");
        var type = name.substring(0, index);
        index = name.substring(index);

        var currentElement = document.getElementById(name);
        var view = this.allViews.lookup(name);

        if (view == null)
            return currentElement;


        if (view.toBeGarbageCollected)
            console.log("View '" + view.name + "' is not in document any more");

        var ucpComponent = this.lookup(type);
        if (ucpComponent != null && view != null)
            view.controller = ucpComponent.controller;


        if (currentElement != null) {
            if (view.element != currentElement) {
                view.element = currentElement;
                currentElement.weBeanNode = view;
            }

            if (currentElement.id != view.name)
                this.allViews.addAlias(currentElement.id, view);

        } else {
            // this view does not exist in the current document any more.	

            if (view.element == null) {
                view.toBeGarbageCollected = true;
                return view;
            }

            currentElement = document.getElementById(view.element.id);

            if (currentElement == null) {
                view.toBeGarbageCollected = true;
                return view;
            }

            if (currentElement.id != view.name) {
                this.allViews.addAlias(currentElement.id, view);
            }

        }



        return view;
    }


    checkDone(controller) {
        //		this.logLoadStatus();
        if (this.ucpComponents.array.every(
                function isLoaded(c) {
                    if (c.defaultWeBean == null)
                        return false
                    return c.defaultWeBean.loaded
                }
            )) {
            this.done();
            return true;
        }
        return false;
    }

    logLoadStatus() {
        console.log("");
        console.log("Controller  imported	loaded	| WeBean	imported	loaded");
        this.ucpComponents.array.every(
            function isLoaded(c) {
                if (c.defaultWeBean != null)
                    console.log("Controller: 	" + c.controller.imported + "	" + c.controller.loaded + "	| WeBean	" + c.defaultWeBean.imported + "	" + c.defaultWeBean.loaded + "	" + c.name)
                return true;
            }
        )
    }

    done() {
        if (this.isDone)
            return;

        this.isDone = true;

        document.weBeans.overwriteAllBeans();

        if (this.onload == null)
            return;

        this.onload();
    }


    registerAppInitFunction(f) {
        new LoadEvent().init(this,f);
            if (this.checkDone())
                this.done();
        }

    registerWeBeanOnLoadedListener(weBeanName, listenerFunction) {
        if (this.ucpComponents[weBeanName].defaultWeBean != null)
            if (this.ucpComponents[weBeanName].defaultWeBean.loaded) {
                //weBean exists and is already loaded, so immediatly call back

                listenerFunction(this.ucpComponents[weBeanName].defaultWeBean);
                return;
            }

            // initialize if not yet done
        if (this.weBeanOnLoadListener[weBeanName] == null)
            this.weBeanOnLoadListener[weBeanName] = [];

        this.weBeanOnLoadListener[weBeanName].push(listenerFunction.bind(this));
    }

    importScript(path, onload) {
        var script = document.createElement("script");
        script.src = path;
        script.type = "text/javascript";
        script.language = "JavaScript";
        script.onload = onload;

        document.head.appendChild(script);
        return script;
    }

    importLink(path, rel, type, onload) {
        var link = document.createElement("link");
        link.href = path;
        link.rel = rel;
        link.type = type;
        link.onload = onload;

        document.head.appendChild(link);
        return link;
    }


}



class Controller {
    init(path) {
        this.loaded = false;
        this.imported = false;
        this.descriptorPath = path;
        this.pathElements = path.split("/");
        this.descriptorName = this.pathElements[this.pathElements.length - 1];
        this.name = this.descriptorName.split(".")[0];
        this.extension = ".class.js";
        this.path = "src/js/";

        if (this.descriptorName.endsWith(this.extension)) {
            this.descriptorName = this.name + ".component.xml";
            this.basePath = this.descriptorPath.substr(0, this.descriptorPath.indexOf(this.path));
            this.descriptorPath = this.basePath + this.descriptorName;
        } else {
            this.basePath = this.descriptorPath.substr(0, this.descriptorPath.indexOf(this.descriptorName));
        }
        this.filename = this.basePath + this.path + this.name + this.extension;

        this.singleton = null;
        this._eventSupport = new MyEventSupport();


        // @fix @todo This is just a work around because the dom-nodes loose their state by using set outerHTML. Remove when rework has been done		
        //		this.views = [];

        this.model2ViewMap = new Map();
        this.model2ViewMap.allowCollections = true;
        this._viewModelCounter = 0;

        return this;
    }

    import () {
        var script = document.createElement("script");
        script.setAttribute("type", "text/javascript");
        script.setAttribute("src", this.filename);
        script.setAttribute("onload", "UcpComponentSupport.ucpComponents['" + this.name + "'].controller.controllerLoaded('" + this.name + "',this)");

        document.head.appendChild(script);
        this.script = script;
        this.imported = true;
    }

    controllerLoaded(name, object) {
        this.loaded = true;
        console.log("Controller loaded: " + name);
        if (this.singleton != null)
            this.singleton.init(this);


        this.component.defaultWeBean = new DefaultWeBean().init(this);

        if (TheSafetySwitch.check())
            this.component.defaultWeBean.import();
    }

    addWeBeanNode(view, customElement, data) {
        if (this.duringModelChange)
            return;


        console.log("add WeBeanNode: " + view.name);
        UcpComponentSupport.allViews.add(view);



        if (this.singleton == null) {
            this.singleton = eval("new " + this.name + "()");
            console.log("Controller Singleton instantiated: " + this.filename);
            this.singleton.init(this);
        }

        if (data == null)
            data = view.data


        var model = this.setModel(data, view.name);

        //		var viewModel = new Model().init(this.name,data);
        //		viewModel._eventSupport.addEventListener("onModelChange",this,this.modelChanged.bind(this));


        this.addView(model, view);
    }


    addView(model, view) {
        view.data = model.properties;
        view.model = model;
        view.ondata = this.setModel.bind(this);

        this.model2ViewMap.add(model, view);

        if (this.singleton)
            if (view.element && this.singleton.updateView)
                this.singleton.updateView(model, view);

    }


    updateView(view, model) {
        view._data = model.properties;
        view.model = model;

        if (view.element != null) {
            view.update();
            if (this.singleton)
                if (view.element && this.singleton.updateView)
                    this.singleton.updateView(model, view);
        }
    }



    setModel(data, id) {
        if (data == null)
            return;

        if (id == null) {
            if (data.id == null)
                return;
            id = data.id;
        }

        if (id == null || id === "")
            id = "viewModel_" + this._viewModelCounter++;


        var model = this.model2ViewMap.getKey(id);

        if (model == null) {
            model = new Model().init(this.name, data);

            if (model.id === "");
            model.id = id;

            model._eventSupport.addEventListener("onModelChange", this, this.modelChanged.bind(this));
        } else {
            if (model.properties != data) {
                model.init(this.name, data);
                var views = this.model2ViewMap.getValue(model.id);
                for (var v in views) {
                    this.updateView(views[v], model);
                }
            }
        }

        return model;

    }




    getAllModels(data) {
        return
    }

    modelChanged(event, data) {
        console.log("model will Change");
        var model = event.source;
        var controller = event.target;
        var views = this.model2ViewMap.getValue(model.id);

        this.duringModelChange = true;

        for (var v in views) {
            var view = views[v];

            this.updateView(views[v], model);

            //			if (view.element == null ) {

            //outdated View... lookup current view
            //if (view.element.parentElement == null)
            //	view = UseUcpComponents.lookupView(view.name);

            //			}
            //			else {
            //				view.toBeGarbageCollected = true;
            //				UcpComponentSupport.lookupView(view.name);
            //			}
        }

        this.duringModelChange = false;
    }



}


class DefaultWeBean {
    init(controller) {
        this.loaded = false;
        this.imported = false;
        this.component = controller.component;
        this.name = controller.component.name;
        this.extension = ".weBean.html";
        this.path = controller.basePath + "src/html/weBeans/";
        this.filename = this.path + this.name + this.extension;
        this.bean = null;
        return this;
    }
    import () {
        var link = document.createElement("link");
        link.setAttribute("rel", "import");
        link.setAttribute("href", this.filename);
        link.setAttribute("onload", "UcpComponentSupport.ucpComponents['" + this.name + "'].defaultWeBean.weBeanLoaded('" + this.name + "')");
        link.onerror = function(error) { console.error(error) };
        //        link.setAttribute("onerror", "function(error) {console.error(error)}");
        document.head.appendChild(link);
        this.imported = true;
        this.link = link;

        if (!('import' in this.link)) {
            console.log("link import not supported: in " + this.name);
            var iframe = document.createElement("iframe");
            iframe.src = this.filename;
            iframe.hidden = "";
            iframe.setAttribute("onload", "UcpComponentSupport.ucpComponents['" + this.name + "'].defaultWeBean.weBeanLoaded('" + this.name + "')");
            document.head.appendChild(iframe);
            this.link = iframe;
        }
    }

    weBeanLoaded(name) {
        if (this.loaded)
            return;

        console.log("WebBean loaded: " + name);

        var element = this.link;
        this.bean = document.weBeans.webBeanLoaded(element);

        this.bean.onrender = this.component.controller.addWeBeanNode.bind(this.component.controller);

        this.loaded = true;

        if (UcpComponentSupport.weBeanOnLoadListener[this.name] != null) {
            UcpComponentSupport.weBeanOnLoadListener[this.name](this);
        }


        UcpComponentSupport.checkDone(this);
    }




}


class UcpComponent {
    init(controller) {
        this.controller = controller;
        this.name = this.controller.name;
        this.descriptorPath = this.controller.descriptorPath;
        this.controller.component = this;
        this.structrComponent = null;

        var pathElements = this.descriptorPath.split("/");
        this.descriptorName = pathElements.splice(-1, 1)[0];
        this.rootPath = pathElements.join("/");
        this.version = pathElements.splice(-1, 1)[0];
        pathElements.splice(0, 3);
        this.type = pathElements.join(".");
        pathElements.splice(-1, 1)[0]; // = this.name
        this.namespcae = pathElements.join(".");

        return this;
    }

    retrieveComponent(callbackFunction) {
        if (TheSafetySwitch.check())
            return Structr.find(
                "UcpComponentDescriptor", "", { "path": this.controller.descriptorPath },
                function(data) {
                    this.component = data.result[0];
                    if (callbackFunction != null)
                        callbackFunction(this.structrComponent);
                }.bind(this)
            )
    }
}


class Map {
    constructor() {
        this.allowCollections = false;
        this.keys = new IndexedObjects();
    }

    init() {
        return this;
    }

    getMapEntry(key) {
        var indexedObject = this.keys.lookup(key);
        if (indexedObject == null)
            return null;
        return indexedObject.mapEntry;

    }

    getKey(key) {
        var indexedObject = this.keys.lookup(key);
        if (indexedObject == null)
            return null;
        return indexedObject.mapEntry.key;

    }

    getAllKeys() {
        var keys = [];
        this.keys.filter("mapEntry");

    }

    getValue(key) {
        var indexedObject = this.keys.lookup(key);
        if (indexedObject == null)
            return null;
        return indexedObject.mapEntry.value;

    }

    set(key, value) {
        var indexedObject = this.keys.lookup(key);
        if (indexedObject == null) {
            var mapEnty = { mapEntry: { key: key, value: value } }
            mapEnty[this.keys.indexAttribute] = key[this.keys.indexAttribute];

            this.keys.add(mapEnty)
        } else {
            indexedObject.value = value;
        }
    }

    add(key, value) {

        //		console.log("model2View: "+key[this.keys.indexAttribute]);

        if (!this.allowCollections)
            return;
        var indexedObject = this.keys.lookup(key[this.keys.indexAttribute]);
        if (indexedObject == null) {
            this.set(key, [value]);
        } else {
            if (indexedObject.mapEntry.value == null)
                indexedObject.mapEntry.value = [];

            if (indexedObject.mapEntry.value.push == null) {
                //not a collection? so make it a collection
                indexedObject.mapEntry.value = [indexedObject.value];
            }
            indexedObject.mapEntry.value.push(value);
        }
    }

    isCollection(key) {
        var indexedObject = this.keys.lookup(key);
        if (indexedObject == null)
            return false;

        if (indexedObject.value == null)
            return false;

        if (indexedObject.value.push == null)
            return false;
        else
            return true;

    }

}

class IndexedObjects {
    constructor() {
        this.index = {};
        this.indexAttribute = "id"
    }

    init(indexAttribute) {
        this.indexAttribute = indexAttribute;
        return this;
    }

    add(indexedObject) {
        this.index[indexedObject[this.indexAttribute]] = indexedObject;
    }

    addAlias(alias, indexedObject) {
        this.index[alias] = indexedObject;
        if (indexedObject.indexedObjectAliases == null)
            indexedObject.indexedObjectAliases = [];
        indexedObject.indexedObjectAliases.push(alias)
    }

    remove(indexedObject) {
        this.index[indexedObject[this.indexAttribute]] = null;
    }

    lookup(id) {
        return this.index[id];
    }

    filter(attribute, equalsTo, isExtraxtAttribute) {
        var collection = []
        if (attribute == null)
            attribute = this.indexAttribute;


        for (var i in this.index) {
            var indexedObject = this.index[i];

            var value = indexedObject[attribute];
            if (value != null) {
                if (equalsTo === "*" || value == equalsTo || equalsTo == null) {
                    var entry = value;
                    if (attribute != this.indexAttribute) {
                        entry = {};
                        if (isExtraxtAttribute)
                            entry[attribute] = value;
                        else
                            entry["value"] = indexedObject;

                        entry[this.indexAttribute] = indexedObject[this.indexAttribute];
                    }
                    collection.push(entry);
                }
            }
        }
        return collection;
    }
}


class ManagedProperties {
    constructor() {}

    init() {
        return this;
    }
}

class Model {
    constructor() {
        this.type = "";
        this.id = "";


        this._eventSupport = new MyEventSupport();
        this._eventSupport.addEventListener("onModelChange", this, this.modelWillChange.bind(this));

        this.isInitialized = false;

    }

    init(typeName, data) {
        var newProperties = new ManagedProperties();

        if (typeName == null)
            return;
        this.type = typeName;

        if (data == null)
            return;

        if (data.id != null)
            this.id = data.id;

        var ignoredProperties = ["json", "weBean", "model", "properties", "ondata", "renderer"];

        for (var prop in data) {
            // if prop is contained in ignoredProperties
            if (!prop.startsWith("_") && ignoredProperties.every(function(i) { return (i !== prop) })) {
                var value = data[prop];
                var propertyName = "_" + prop;
                newProperties[propertyName] = value;

                var getter = new Function("return this.properties['" + propertyName + "'];");
                //			console.log("  getter: "+getter);
                getter = getter.bind(this);


                var setter = new Function("newValue",
                    'if (this.properties["' + propertyName + '"] == newValue) return; ' +
                    'this.properties["' + propertyName + '"] = newValue;' +
                    'this._eventSupport.getEvent("onModelChange").fire(this,{"oldVlaue":this.properties["' + propertyName + '"],"newValue":newValue,"property":"' + propertyName + '"});');
                //			console.log("  setter: "+setter);
                setter = setter.bind(this);


                var This = this;
                Object.defineProperty(newProperties, prop, {
                    get: getter,
                    set: setter,
                    enumerable: true,
                    configurable: false
                });
            }
        }
        this.properties = newProperties;

        this.isInitialized = true;

        return this;
    }

    modelWillChange() {}


    getModelById() {}

    getModelByParameter(parameter) {}

    getSchema() {}

    getPage() {}


    setSchema(value) {}


    setModel(value) {}

}



class NewClassTemplate {
    constructor() {}

    init() {
        return this;
    }
}


class MyEventSupport {
    constructor() {
        this.registry = {};
    }

    addEventListener(eventName, eventTarget, handlerFunction) {

        if  (this.registry[eventName] == null)
            this.registry[eventName] = new MyEvent().init(eventName);

        var event = this.registry[eventName];
        event.addHandler(handlerFunction);
        event.target = eventTarget;
    }

    getEvent(eventName) {
        return this.registry[eventName];
    }

}


class MyEvent {
    constructor() {
        this.name = "";
        this.handlers = [];
    }

    init(name) {
        this.name = name;
        return this;
    }

    addHandler(handlerFunction) {
        this.handlers.push(handlerFunction);
    }

    fire(eventSource, item) {
        this.source = eventSource;

        if (eventSource.duringFire == true)
            return;

        eventSource.duringFire = true;
        for (var i in this.handlers) {
            this.handlers[i](this, item);
        }
        eventSource.duringFire = null;
    }

}


class SavetySwitch {
    constructor() {
        this.i = 0;
        this.limit = 25;
    }

    check() {
        if (this.i++ < this.limit) {
            console.log("SafetySwitch(" + this.i + ") ok...")
            return true;
        } else {
            console.error("SafetySwitch(" + this.i + ") over Limit!")
            return false;
        }
    }
}



class Interface {

    constructor() {
        this.type = "Interface";
        this.implements = [];
        this.extends = [];
    }

    init() {
        return this;
    }

    static implement(obj) {
        var p = Object.getPrototypeOf(obj);


        if (!('type' in p))
            p.type = "";

        if (!obj.type)
            obj.type = this.name;
        else
            obj.type = this.name + ":" + obj.type;

        if (!('implements' in p))
            p.implements = [];

        if (!obj.implements)
            obj.implements = p.implements;

        obj.implements.push(this.name);


    }
}

/* Interface
 *
 */
class ControllerSingletonInterface {
    constructor() {
        Interface.implement(this);
    }

    init() {
        return this;
    }
}



class ManagedObject {
    constructor() {}

    init() {
        return this;
    }
}


class LoadEvent {
    constructor() {
        this.previousOnload = null;
        this.onloadFunction = null;
    }

    init(target, onloadFunction) {
        if (!target || !onloadFunction) {
           console.warn("LoadEvent: init(target,onloadFunction) adds target.onload = onloadFunction")
        }
        this.previousOnload = target.onload;
        this.onloadFunction = onloadFunction;
        target.onload = this.onload.bind(this);
    
        return this;
    }

    onload(event) {
        if (typeof this.previousOnload == 'function')
            this.previousOnload(event);
        if (typeof this.onloadFunction == 'function')
            this.onloadFunction(event);
    }
}




TheSafetySwitch = new SavetySwitch();




window.onload = function() {
    console.log("Finally did it");


    UcpComponentSupport = new UseUcpComponents()
    UcpComponentSupport.init();
    //	UcpComponentSupport.registerAppInitFunction(AppInit);
    //	document.UcpComponentSupport = UcpComponentSupport;

}