Namespace.declare("tla.EAMD",
    class UcpComponentSupport extends Thing {
        static start() {
            var UcpComponentSupport = window[this.name] = window[this.name] || new this().init();
        }
        static dependencies() {
            return [
                new IOR().init("/EAMD.ucp/Components/com/ceruleanCircle/EAM/2_systems/BasicDataTypes/0.4.0/BasicDataTypes.component.xml"),
                new IOR().init("/EAMD.ucp/Components/com/ceruleanCircle/EAM/5_ux/StructrES6Client/0.5.0/StructrES6Client.component.xml"),
                new IOR().init("/EAMD.ucp/Components/com/ceruleanCircle/EAM/3_services/WebBeans/0.4.0/WebBeans.component.xml")
            ];
        }


        constructor() {
            super();
            Thinglish.addLazyGetter(this, "DefaultUcpComponentRepository");
        }


        init() {
            console.log("UcpComponents ready");
            this.ucpComponentIORs = this.discover();
            Thinglish.loadAndStartAll(this.ucpComponentIORs);
            return this;
        }

        discover() {
            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];

            return saveLinkIterator.map((link) => {
                return new IOR().init(link.href)
            });
        }

        load(url) {
            console.log("loading UcpComponent " + this.type.name + " from " + url);
            var result = super.load(url).then(
                (componentClass) => {
                    console.log("Call Structr: UcpComponentDescriptor?path=" + componentClass.IOR.loader.descriptorPath);

                    // @todo bad: controller object on WODA Class... singleton? then ok....
                    componentClass.controller = new UcpController().init(componentClass);
                    var dependencies = this.DefaultUcpComponentRepository.getDependencies(componentClass)

                    return componentClass;
                }

            );
            return result;
        }

        /**
         * @deprecated since version 0.5.0
         */
        lookup(name) {
            if (name.indexOf("(") != -1)
                throw Error("Possible security violation: '" + name + "' does not seem to be a class name");
            var aClass = eval(name);
            if (!aClass)
                return null;
            aClass.controller = new UcpController().init(aClass);
            return aClass;
        }

        /**
         * @deprecated since version 0.5.0
         */
        importScript(path, callback) {
            var p = Namespaces.loader.load(path)
            if (callback)
                return p.then(
                    () => {
                        return callback();
                    });
            return p;

        }

        /**
         * @deprecated since version 0.5.0
         */
        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;
        }

    }
);

var UcpComponent = Namespace.declare("tla.EAMD",
    class UcpComponent extends Interface {
        constructor() {
            super();
            // @todo intense testing
            // Thinglish.implement(Thing, UcpComponent);
            // @todo implement general EventSupport for everyThing
            // Thinglish.implement(this, EventSupport);
        }

        static init() {
                return this;
            }
        get controller() {
                return this.type.class.controller
        }
        set controller(newValue) {
                this.type.class.controller = newValue;
        }
        get _eventSupport() {
                if (!this._eventSupport)
                        this._eventSupport = new EventService();
                return this._eventSupport;
        }
        set _eventSupport(newValue) {
                console.log("Not zet implemented");
        }
/*
        getController() {
            return this.type.class.controller;
        }
*/
    }
);

var UcpController = Namespace.declare("tla.EAMD",
    class UcpController {
        constructor() {
            //super();
        }

        init(aClass) {
            this.ucpComponentClass = aClass;
            return this;
        }

        get singleton() { return this._singleton; }
        set singleton(newValue) {
            /**
             * @deprecated since version 0.5.0, here for backward compatibility
             */
            if (this.ucpComponentClass.IOR) {
                if (newValue.init)
                    newValue.init(this.ucpComponentClass.controller);
            }
            //
            this._singleton = newValue;
        }
        get basePath() { return this.ucpComponentClass.IOR.loader.basePath + "/"; }

        get _eventSupport() {
                if (!this._private)
                        this._private = {};
                if (!this._private.eventSupport)
                        this._private.eventSupport = new EventService();
                return this._private.eventSupport;
        }
        set _eventSupport(newValue) {
                this._private.eventSupport = newValue;
        }

        get selectedItem() {
                if (!this._private)
                        this._private = {};
                return this._private.selectedItem;
        }
        set selectedItem(newValue) {
                this._private.selectedItem = newValue;
        }
        

        registerView(unit) {

            var weBean = unit.weBean;
            /*
            weBean = weBean.replace(/\$-\{/g, '${');
            weBean = weBean.replace(/"/g, "'");
            weBean = weBean.replace(/<x-([^( >)]*)([ >])/g, "<div data-is='x-$1'$2");
            weBean = weBean.replace(/<\/x-(.*)>/g, "</div>");
            this.weBean = weBean;
            */
            var divTemplate = document.createElement('div');
            divTemplate.innerHTML = weBean;
            var template = divTemplate.children[0];
            

            this.weBean = document.weBeans.register(template);

            this.id = template.getAttribute("id");
            this.customTag = template.getAttribute("is");
            //this.template = template;

        }


    }
);

var ManagedProperties = Namespace.declare("tla.EAMD",
    class ManagedProperties {
        constructor() {
            this._private = {};
            this._protected = {};
        }

        init() {
            return this;
        }
    }
);

var Model = Namespace.declare("tla.EAMD",
    class Model extends Interface {
        isLoaded() {
            return true;
        }

        load() {
            console.error("Not yet Implemented");
        };
        save() {
            console.error("Not yet Implemented");
        };

        modelWillChange() {}
    }
);

var UcpModel = Namespace.declare("tla.EAMD",
    class UcpModel {
        constructor() {
            Thinglish.initType(this, Namespaces.lookupInObject(this, "constructor.type.package"));
            //            this.type = "";
            //            this.id = "";
            Thinglish.implement(this, Model);

            //            Thinglish.implement(this, EventSupport);
            this._eventSupport = new EventService();
            this._eventSupport.addEventListener("onModelChange", this, this.modelWillChange.bind(this));

            this.isInitialized = false;
        }

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

            if (data == null)
                return;

            var typeName = data.type;
            //            if (typeName == null)
            //                return;

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

            var ignoredProperties = ["_private", "_protected", "json", "weBean", "model", "properties", "ondata", "renderer"];
            newProperties._private = data;

            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._private[propertyName] = value;

                    //                    var getter = new Function("var value = this.properties._private['" + propertyName + "']; console.log(\"get "+propertyName+"=\"+value); return value;");
                    var getter = new Function(`
                        var value = this.properties._private['${propertyName}']; 
                        return value;`);
                    /*
                    var getter = new Function(`
                        var value = this.properties._private['${propertyName}']; 
                        console.log(\"${typeName}.get ${propertyName}=\"+value); 
                        if (value.isLoaded) {
                            console.log("${propertyName} is loaded?  "+value.isLoaded()); 

                        }

                        return value;`
                    );

                    var getter = new Function(`
                        var value = this.properties._private[`+propertyName+`];
                        if (value == null)
                            return null;

                        if (Thinglish.isInstanceOf(value, Model)) {
                            if (!value.isLoaded()) {
                                value.load();
                            }
                        }
                        return value;
                        `
                    );


                    var getter = new Function(`
                        var value = this.properties._private[\"${propertyName}\"];
                        console.log("get "+propertyName+"="+value);
                        return value;
                        `
                    );
                    */
                    //			console.log("  getter: "+getter);
                    getter = getter.bind(this);

                    var setter = new Function("newValue", `
                        if (this.properties._private["${propertyName}"] == newValue) return;
                        this.properties._private["${propertyName}"] = newValue;
                        if (!this._changeLog)
                            this._changeLog = {};
                        this._changeLog["${propertyName}"] = newValue;
                        this._eventSupport.getEvent("onModelChange").fire(this, {
                                "oldVlaue":this.properties._private["${propertyName}"],
                                "newValue":newValue,"property":"${propertyName}"
                        });
                    `);

                    /*
                                        var setter = new Function("newValue",
                                            'if (this.properties._private["' + propertyName + '"] == newValue) return; ' +
                                            'this.properties._private["' + propertyName + '"] = newValue;' +
                                            'this._eventSupport.getEvent("onModelChange").fire(this,{"oldVlaue":this.properties._private["' + 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;
        }

        isLoaded() {
            return true;
        }

        load() {
            console.error("Not yet Implemented");
        };
        save() {
            console.error("Not yet Implemented");
        };

        modelWillChange() {

        }

        checkChangeLog() {
            Object.keys(this._changeLog).forEach(
                p => {
                    if (this.isDeepEqual(this._changeLog[p], this.properties[p]))
                        delete this._changeLog[p];
                });
        }

        isDeepEqual(a, b) {
            if (a == b)
                return true;

            if (Array.isArray(a)) {
                var aIndex = new IndexedObjects();
                aIndex.addArray(a);
                if (!Array.isArray(b)) return false;

                var bIndex = new IndexedObjects();
                bIndex.addArray(b);

                var aKeyArray = aIndex.getKeys();
                if (aKeyArray.length > 0) {
                    return aKeyArray.each(
                        key => {
                            return this.isDeepEqual(aIndex.lookup(key), bIndex.lookup(key));
                        });
                }
            } else {
                var aKeyArray = Object.keys(a);
                if (aKeyArray.length > 0) {
                    return aKeyArray.each(
                        key => {
                            return this.isDeepEqual(a[key], b[key]);
                        });
                }
            }
        }

    }
);

var UcpView = Namespace.declare("tla.EAMD",
    class UcpView extends Interface {
        constructor() {
            super();
        }

        init() {
            return this;
        }
    }
);


var UcpComponentRepository = Namespace.declare("tla.EAM.layer3",
    class UcpComponentRepository extends Interface {
        static dependencies() {
            return [
                new IOR().init("/EAMD.ucp/Components/com/ceruleanCircle/EAM/5_ux/StructrES6Client/0.5.0/StructrES6Client.component.xml"),
            ];
        }
        constructor() {
            super();
        }


        init() {
            console.log("UcpComponentrepository ready");
            this.ucpComponentIORs = this.discover();
            this.loadAndStartAll(this.ucpComponentIORs);
            return this;
        }


        loadDescriptorFromStructr(componentClass) {
            console.error("Not yet Implemented");
        }

        getDependencies(componentClass) {
            console.error("Not yet Implemented");
        }

        getRepositoryURL() {
            console.error("Not yet Implemented");
        }

    }
);


var DefaultUcpComponentRepository = Namespace.declare("tla.EAMD",
    class StructrUcpComponentRepository extends Thing {
        static dependencies() {
            return [
                new IOR().init("/EAMD.ucp/Components/com/ceruleanCircle/EAM/5_ux/StructrES6Client/0.5.0/StructrES6Client.component.xml"),
            ];
        }
        constructor() {
            super();
            Thinglish.implement(this, UcpComponentRepository);
        }


        init() {
            console.log("StructrUcpComponentRepository ready");
            this.ucpComponentIORs = this.discover();
            this.loadAndStartAll(this.ucpComponentIORs);
            return this;
        }

        loadDescriptorFromStructr(componentClass) {
            if (componentClass.type.ucpComponentDescriptor)
                return componentClass.type.ucpComponentDescriptor;

            var parameter = {
                path: componentClass.IOR.loader.descriptorPath
            }
            if (!Structr) return null;

            return Structr.find("UcpComponentDescriptor/ui", null, parameter).then(
                (ucpComponents) => {
                    if (!ucpComponents)
                        return null;

                    var ucpComponentDescriptor = null;
                    if (ucpComponents.length > 0) {
                        ucpComponentDescriptor = ucpComponents[0];
                    }
                    componentClass.type.ucpComponentDescriptor = ucpComponentDescriptor;

                    if (!ucpComponentDescriptor) {
                        console.warn("UcpComponentDescriptor not found on UcpComponentRepository: " + componentClass.IOR.loader.descriptorPath);
                    } else
                        console.log(ucpComponentDescriptor);


                    return ucpComponentDescriptor
                });
        }

        // @todo move to DefaultUcpCompoentnDescriptor
        getDependencies(componentClass) {
            if (componentClass.type.ucpComponentDescriptor)
                return componentClass.type.ucpComponentDescriptor;

            return this.loadDescriptorFromStructr(componentClass).then(
                ucpComponentDescriptor => {
                    if (!ucpComponentDescriptor) {
                        //console.warn("Descriptor not found for: " + componentClass.IOR.loader.descriptorPath);
                        // @todo import Descriptor into Repository
                        return null;
                    } else {
                        console.log("found Descriptor: " + ucpComponentDescriptor.properties.name);
                        ucpComponentDescriptor.class = componentClass;
                        return ucpComponentDescriptor.properties.uses.map(
                            (dependency => {
                                console.log("found dependency: " + dependency.name);

                                // async
                                var ucpComponentDescriptor = Structr.find("UcpComponentDescriptor", dependency.id, null).then(
                                    (ucpComponents) => {

                                        var ucpComponentDescriptor = null;
                                        if (ucpComponents.length > 0) {
                                            ucpComponentDescriptor = ucpComponents[0];
                                            // bug: adds WhatPanel descriptor to WODA, @todo but where does the whatPanel get its descriptor...?
                                            //componentClass.type.ucpComponentDescriptor = ucpComponentDescriptor;
                                        }

                                        console.log(ucpComponentDescriptor.properties.path);
                                        Thinglish.loadAndStartAll([new IOR().init(ucpComponentDescriptor.properties.path)]);
                                        return ucpComponentDescriptor
                                    });



                                // @todo create StructrObjectIOR and put it into componentClass.type._dependencies.push(...)
                            }));
                    }
                });

        }

        getRepositoryURL() {
            return new URL(Structr.defaultServer + EAMDucpLoader.EAMDcomponentDir);
        }

        updateAllDescriptors() {
            var allClasses = Namespaces.discover();
            var updatedClasses = [];
            allClasses.forEach(
                aClass => {
                    if (aClass.namespace)
                        console.log(aClass.namespace.package);
                    else
                        console.log("Class has no namespcae: ", aClass.name);

                    if (aClass.type) {
                        console.log(aClass.type.ucpComponentDescriptor);
                        if (aClass.type.ucpComponentDescriptor) {
                            aClass.type.ucpComponentDescriptor.updateDependenciesinRepository();
                            updatedClasses.push(aClass.type.ucpComponentDescriptor);
                        }
                    } else
                        console.log("Class has no type: ", aClass.name);

                });
            updatedClasses.forEach(
                aClass => {
                    // @todo if descriptor was changed....  use model checkChangeLog and isDeepEqual (untested yet)
                    if (aClass.properties.uses.length > 0) {
                        console.log("Save Descriptor: ", aClass);
                        //aClass.save();
                    }
                });
        }



    }
);


var UcpComponentDescriptor = Namespace.declare("tla.EAMD",
    class UcpComponentDescriptor extends Interface {
        constructor() {
            super();
        }

        init() {
            return this;
        }



    }
);

var DefaultUcpComponentDescriptor = Namespace.declare("tla.EAMD",
    class DefaultUcpComponentDescriptor extends UcpModel {
        constructor() {
            super();
            Thinglish.implement(this, UcpComponentDescriptor);
        }

        init(ucpComponentStructrObject) {
            var data = ucpComponentStructrObject.properties._private;
            super.init(data);
            this.name = this.properties._private.name;
            this.id = this.properties._private.id;
            this.type.name = this.properties._private.type;

            /*
            if (this.properties._private.ucpComponent.type) {
                this.properties._private.ucpComponent.type.name = "UcpComponent";
                this.properties._private.ucpComponent.type.package = "org.structr.Schema.UcpComponent";
                this.properties._private.parent.type.name = "Version";
                this.properties._private.parent.type.name = "org.structr.Schema.Version";
            }
            */
            if (!this.class) {
                var type = this.name.split(".")[0];
                try {
                    this.class = eval(type);
                } catch (error) {}
            }
            this.save = ucpComponentStructrObject.save;
            this.load = ucpComponentStructrObject.load;

            this.initializeUnits();
            /*.then(
                units => {
                    console.log("units loaded ", units);
                    Promise.all(
                        units.map(
                            unit => {
                                return unit.weBean;
                            }));
                });
            */

            return this;
        }

        isLoaded() {
            console.error("Not yet Implemented");
        }

        load() {
            console.error("Cannot be here! load() is overwritten in init()");
        }
        save() {
            console.error("Cannot be here! save() is overwritten in init()");
        }


        getAbsolutePathForUnit(unit) {
            var unitHref = unit.properties.href;
            if (unitHref.startsWith("./"))
                unitHref = this.class.IOR.loader.basePath + unitHref.substr(1);
            return unitHref;
        }

        updateDependenciesinRepository() {
            var mergedDependencies = new IndexedObjects();


            var loadedDependencies = this.properties.uses;
            loadedDependencies.forEach(
                d => {
                    mergedDependencies.add(d);
                    // console.log(d); 
                });

            if (this.class.dependencies) {
                var codeDependencies = this.class.dependencies().map(
                    d => {
                        if (!d) {
                            console.warn("code dependency not correctly loaded: ", this, " dependency ", d);
                            return null;
                        }

                        var codeDependency = {
                            name: d.class.type.ucpComponentDescriptor.properties.name,
                            id: d.class.type.ucpComponentDescriptor.properties.id,
                            //                   type:"UcpComponentDescriptor"
                        };
                        mergedDependencies.add(codeDependency);
                        return codeDependency;
                    });
            }

            this.properties.uses = mergedDependencies.getValues();

            this.properties.uses.forEach(
                d => {
                    console.log(d);
                });
        }

        initializeUnits() {
            var units = this.properties.units;
            return Promise.all(units.map(
                unit => {
                    // load unit details from Structr
                    if (!unit.isLoaded())
                        return unit.load();

                    return unit;

                })).then(
                units => {
                    if (this.class)
                        console.log("unit details loaded: ", this.class.IOR.loader.basePath);
                    else
                        console.warn("unit details loaded but no Class found for ", this);

                    return units.map(
                        unit => {
                            unit.absolutePath = this.getAbsolutePathForUnit(unit);
                            if (unit.properties.unitType.startsWith(".weBean")) {
                                if (!this.webBeanUnits) this.webBeanUnits = [];
                                this.webBeanUnits.push(unit);
                                unit.weBean = this.loadWeBean(unit);
                            }
                            if (unit.properties.name.startsWith(this.class.name + ".weBean.htm")) {
                                this.defaultWebBeanUnit = unit;
                            }
                            if (unit.properties.name.startsWith(this.class.name + ".htm"))
                                this.startPageUnit = unit;
                            return unit;
                        });
                });

        }

        loadWeBean(unit) {
            if (!unit) return null;

            var wl = new WeBeanLoader();
            var ior = wl.getIOR(unit.absolutePath);
            return ior.load().then(
                weBean => {
                    //console.log("weBean loaded ",weBeans);
                    //                    var divTemplate = document.createElement('div');
                    //                    divTemplate.innerHTML = weBean;
                    //                    unit.weBeanElement = divTemplate.children[0];
                    unit.weBean = weBean;

                    if (!this.class.controller)
                        this.class.controller = new UcpController();

                    this.class.controller.registerView(unit);

                    console.log("starting ", this.name);
                    Thinglish.implement(this.class, UcpComponent);
                    Thinglish.startClass(this.class);
                    //document.weBeans.overwriteAllBeans();
                    return weBean;
                });
        }
    }
);