//@todo cleanup all controller.eventSupport
/* global Structr, JavaScriptObject, EventSupport, Thing, DetailsView, App, OverView, IndexedObjectsMap, ActionsPanel */


Namespace.declare("tla.EAMD",
    class UcpComponentSupport extends Thing {
        static get implements() { return []; };
        static start() {
            const UcpComponentSupport = window[this.name] = window[this.name] || new this().init();
            Thinglish.implement(ONCE, EventSupport);
            ONCE.eventSupport.addEventListener("newThing", ONCE, ONCE.newThing.bind(this));
            window.Current = element => UcpComponentSupport.getUcpView4Element(element);
            //@todo remove hard coded path
            //Issue : IOR was undefined at get basePath()
        }

        static get dependencies() {
            return [
                "/EAMD.ucp/Components/tla/EAM/layer3/WODAViews/1.0.0/WODAViews.component.xml",
                "/EAMD.ucp/Components/com/ceruleanCircle/EAM/5_ux/StructrES6Client/0.5.0/StructrES6Client.component.xml",
                "/EAMD.ucp/Components/com/ceruleanCircle/EAM/3_services/WebBeans/2.0.0/WebBeans.component.xml"
            ];
        }

        applicationComponents() {
            return [
                "/EAMD.ucp/Components/tla/EAM/layer2/JavaScriptObject/1.0.0/JavaScriptObject.component.xml",
                "/EAMD.ucp/Components/tla/EAM/layer2/DefaultFile/1.0.0/DefaultFile.component.xml",
                "/EAMD.ucp/Components/tla/EAM/layer2/DefaultFolder/1.0.0/DefaultFolder.component.xml",
                "/EAMD.ucp/Components/tla/EAM/layer2/User/1.0.0/User.component.xml",
                "/EAMD.ucp/Components/tla/EAM/layer5/Workflow/1.0.0/Workflow.component.xml"
            ];
        }

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

        init(aDocument) {
            if (!aDocument) {
                aDocument = document;
            }
            console.log("UcpComponents ready");
            this.weBeanRegistry = WebBeans2.getInstance();
            document.body.innerHTML = this.weBeanRegistry.convertWebComponentsToDivs();

            this.ucpComponentIORs = this.findUcpComponentLinks(aDocument);
            Thinglish.loadAndStartAll(Once, this.ucpComponentIORs).then(() => {
                this.importLink("/EAMD.ucp/Components/tla/EAMD/UcpComponentSupport/1.0.0/css/UcpComponentSupport.css", "stylesheet", "text/css");
                //this.importLink("/EAMD.ucp/Components/com/twitter/Bootstrap/3.3.7/dist/css/custom/bootstrap.cerulean.min.css", "stylesheet", "text/css");
                console.log("loading application components");

                Thinglish.loadAndStartAll(Once, this.applicationComponents()).then(() => {
                    this.updateChildren(document.body);

                    console.log("everyThing loaded and ready to speak...");
                    try {
                        Workflow.getDefaultInstance();
                        ONCE.Store.register(App, App.start());
                        ONCE.Store.register(UcpComponentSupport, window.UcpComponentSupport);

                    } catch (e) {
                        Action.do(DetailsView.ACTION_SHOW, e);
                        console.error(e);
                    }
                });
            });
            return this;
        }

        async _resolveAndInitialize(ucpComponent) {
            if (ucpComponent) {
                if (!ucpComponent._private) ucpComponent._private = {};
                if (!ucpComponent._private.isInitialized) {
                    await ucpComponent.init();
                }
                return ucpComponent;
            }
            return null;
        }


        getUcpComponent4Object(object) {
            let component = null;
            if (!object) {
                return this._resolveAndInitialize(null);
            }

            //console.warn("getUcpComponent4Object:", object.name, object);

            if (Thinglish.isClass(object)) {
                let ucpComponent = object.type.ucpComponent;
                if (!ucpComponent) {
                    ucpComponent = new JavaScriptObject().init(object);
                    object.type.ucpComponent = ucpComponent;
                }
                return this._resolveAndInitialize(ucpComponent);
            }

            if (object instanceof UcpComponent) {
                return this._resolveAndInitialize(object);
            }
            if (object instanceof UcpView) {
                return this._resolveAndInitialize(object.ucpComponent);
            }
            //if (object instanceof UcpModel && !(object instanceof StructrObject))
            if (object instanceof HTMLElement) {
                const ucpView = this.getUcpView4Element(object);
                if (ucpView) {
                    return this._resolveAndInitialize(ucpView.ucpComponent);
                }
            }
            if (object instanceof UcpModel) {
                if (!object.isLoaded) {
                    return object.load().then(
                        object => {
                            if (object instanceof UcpComponent) {
                                return this._resolveAndInitialize(object);
                            }

                            component = object.ucpComponent;
                            if (component instanceof UcpComponent) {
                                return this._resolveAndInitialize(component);
                            }

                            component = new JavaScriptObject().init(object);
                            if (!object.ucpComponent) {
                                object.ucpComponent = component;
                            }
                            //component.model = object;
                            return this._resolveAndInitialize(component);
                        });
                }

                component = object.ucpComponent;
                if (component instanceof UcpComponent) {
                    return this._resolveAndInitialize(component);
                }
            }

            component = new JavaScriptObject().init(object);
            //component.model = object;
            return Promise.resolve(component);
        }

        /* @todo Replicated method from Class : UcpView , Method : get parentUcpView()
                 Need to merge it in future
              */
        getUcpView4Element(element) {
            let parentUcpView = null;

            while (element) {
                if (element.weBeanNode) {
                    parentUcpView = element.weBeanNode.view;
                    if (parentUcpView) {
                        this.maintainParent(parentUcpView, this);
                        return parentUcpView;
                    }
                }
                if (element.ucpView) {
                    //this.maintainParent(element.ucpView, this);
                    return element.ucpView;
                }
                element = element.parentElement;
            }
            return null;
        }

        //Declarative way for creating a component
        updateChildren(parentElement) {
            const viewList = this.getAllViews(parentElement);
            viewList.forEach(view => {
                const tag = view.tag;
                if (!tag.parentElement) {
                    return;
                }
                let weBean = this.weBeanRegistry.registry.getValue(view.is);
                if (!weBean) {
                    console.warn("no weBean found for \"", view.is, "\", ignoring this WebComponent");
                    return;
                } else {
                    [weBean] = weBean;
                }
                const ucpComponent = weBean.controller.ucpComponentClass.getInstance();
                const newView = ucpComponent.defaultView;

                newView.updateModelFromCustomTag(ucpComponent.ucpModel, tag);

                // equivalent to super.add(component) where super is a reference on the Container interface
                const parentUcpView = this.getUcpView4Element(tag);
                if (parentUcpView) {
                    parentUcpView._private.interfaces.Container.add(newView);
                }
                //if (this.ucpModel.ucpComponent.onNewChild)
                //this.ucpModel.ucpComponent.onNewChild(newView);

                //var newElement = newView.update(tag);
                const newElement = newView.replace(tag);
                //newElement.ucpView._private.interfaces.Container.add(newView);

            });

        }

        discover() {
            return window.UcpComponentSupport.getAllUcpViews();
        }

        onload(event) {
            console.debug(event);
            var aDocument = event.target.contentDocument;
            //this.init(aDocument);
            var ucpComponentIORs = this.findUcpComponentLinks(aDocument);
            var startFunction = (something => {
                console.debug("initialize iFrame loaded components")
            }).bind(event.target.contentWindow);
            return this.loadAndStartAll(ucpComponentIORs).then(startFunction)
        }

        findUcpComponentLinks(aDocument) {
            if (!aDocument) {
                aDocument = document;
            }
            const links = aDocument.getElementsByTagName("link");

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

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

        loadAndStartAll(ucpComponentIORs) {
            return Thinglish.loadAndStartAll(Once, ucpComponentIORs).then(() => {
                console.debug("everyThing loaded and ready to speak...");
            });
        }

        get eamLayer() {
            return 1;
        }
        getAllUcpViews() {
            return Array.from(document.querySelectorAll("[view-id]")).map(element => {
                return element.ucpView;
            });
        }

        getAllContainer(templateContent) {
            if (!templateContent) {
                templateContent = document;
            }
            var container = new IndexedObjects().init("name");
            Array.from(templateContent.querySelectorAll("[webean-role*=':']")).forEach(e => {
                var name = e.getAttribute("webean-role");
                if (name.endsWith(":firstChild")) {
                    e = e.parentElement;
                    e.setAttribute("webean-role", name.substr(0, name.indexOf(":firstChild")));
                }
                if (e) {
                    container.add({
                        id: e.getAttribute("id"),
                        name: e.getAttribute("webean-role"),
                        tag: e
                    });
                }
            });
            return container.getValues();
        }

        getAllViews(templateContent) {
            //template.content.querySelectorAll("[data-is*='x-']")
            if (!templateContent)
                templateContent = document;
            return Array.from(templateContent.querySelectorAll("[data-is*='-']")).map(e => {
                return {
                    id: e.getAttribute("id"),
                    is: e.getAttribute("data-is"),
                    tag: e,
                    element: e
                }
            });
        }

        /**
               * @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 Component = Namespace.declare("tla.EAM.layer3",
    class Component 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 eamLayer() {
            return 2;
        }
        get controller() {
            let currentControllerArray = null;
            currentControllerArray = this.Store.lookup(Controller);
            if (currentControllerArray) {
                return currentControllerArray[0];
            }

            return null;
            //            return this.type.class.controller
        }

        set controller(newValue) {
            let currentControllerArray = this.Store.lookup(Controller);
            if (currentControllerArray && currentControllerArray.length > 0)
                currentControllerArray[0] = newValue;
            else {
                //console.error("shit...ignored set controller");
                currentControllerArray = [newValue];
                this.Store.register(Controller, newValue);
            }
            return currentControllerArray;
            //            this.type.class.controller = newValue;
        }

    });

var Tree = Namespace.declare("tla.EAM.layer3",
    class Tree extends Interface {
        get eamLayer() {
            return 3;
        }

    });

var Container = Namespace.declare("tla.EAM.layer3",
    class Container extends Tree {
        get eamLayer() {
            return 1;
        }

        add(component) {
            if (!component) {
                return;
            }
            if (!this.children) {
                this.children = new Set();
            }
            this.children.push(component);
            if (component.parent && component.parent.id !== this.id) {
                console.debug("overwrite parent", component.parent.id, " on ", component.id);
            }
            component.parent = this;

        }

        get container() {
            return Thinglish.lookupInObject(this, "defaultView.container");
        }

        get children() {
            if (!this._private) {
                return null;
            }
            if (!this._private.children) {
                this.children = new Set();
            }
            return this._private.children;
        }

        set children(newValue) {
            this._private.children = newValue;
        }

        clear() {
            this.children = new Set();
        }

        updateChildren(parent) {
            console.log(parent);
        }

        static lookup(anInterface) {
            return UcpComponentSupport.lookup(anInterface);
        }

        static lookupFirst(anInterface) {
            const instanceList = this.lookup(anInterface);
            if (Array.isArray(instanceList) && instanceList.length > 0) {
                return instanceList[0];
            }
            return null;
        }

        static register(anInterface, containerInstance) {
            return UcpComponentSupport.Store.register(anInterface, containerInstance);
        }
    }
);


var BrowserFile = File;

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

        get eamLayer() { return 3; }

        get isFolder() {
            return false;
        }

        get path() {
            return this.parent;
        }


    }
);

var FileSmart = Namespace.declare("tla.EAM.layer3",
    class FileSmart extends File {
        get eamLayer() { return 3; }
    }
);

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

        get eamLayer() { return 3; }

        get isFolder() {
            return true;
        }

    }
);




var UcpComponent = Namespace.declare("tla.EAMD",
    class UcpComponent extends Thing {
        static get implements() { return [Component, EventSupport, Container, Folder]; };

        static get ACTION_SAVE() { return "actionId:protected:UcpComponent.save[Save Changes]:success"; }


        constructor() {
            super();
            if (!this.controller) {
                //                var controller = this.constructor.controller;
                const controller = new UcpController().init(this.constructor);
                if (!this.constructor.controller) {
                    this.constructor.controller = controller;
                }
                this.Store.register(UcpController, controller);
                this.controller = controller;
            }
            this._private.ucpModel = new UcpModel().init(this);
            /*
            this.model = {
              id: this._private.id
            }
            */
            this.model = this;

            this.ucpModel.ucpComponent = this;
            this.ucpModel.type = this.type;

            this.Store.register(UcpModel, this.model);

            //Thinglish.implement(this, Component);
            //Thinglish.implement(this, EventSupport);
            //Thinglish.implement(this, Container);
            //Thinglish.implement(this, Folder);
        }

        init(controller) {
            if (controller && !this.controller) {
                this.controller = controller;
            }
            this.model = this;
            //this.actionIndex = [UcpComponent.ACTION_SAVE, UcpComponent.ACTION_CHECK_SAVE];
            this._private.isInitialized = true;


            return this;
        }

        static get defaultImplementationClass() {
            if (!this._private) {
                this._private = {};
            }
            if (!this._private.defaultImplementationClass) {
                try {
                    let defaultClass = eval("Default" + this.name);
                    if (Thinglish.isClass(defaultClass)) {
                        this._private.defaultImplementationClass = defaultClass;
                    }
                } catch (error) {
                    console.debug("no DefaultImplementationClass for Interface: ", this.name);
                    return null;
                }
            }
            return this._private.defaultImplementationClass;
        }
        static set defaultImplementationClass(newValue) {
            if (!this._private) {
                this._private = {};
            }
            this._private.defaultImplementationClass = newValue;
        }

        async create(object) {
            return await this.ucpModel.create(object);
        }

        get typeDescriptor() {
            if (this.type === undefined) {
                this.type = { name: this.constructor.name };
            }
            if (!this.type.descriptor) {
                this.type.descriptor = Thinglish.getDescriptor(this, false, true);
                //this.type = this.type.descriptor.type;
                //this.type.descriptor.type = null;
            }
            if (!this.type.descriptor) {
                return null;
            }
            if (this.type.descriptor.isInitialized === false) {
                return null;
            }
            if (this.type.descriptor.object !== this) {
                this.type.descriptor = Thinglish.getDescriptor(this, false, true);
            }
            return this.type.descriptor;
        }

        get id() {
            if (!this._private) {
                return null;
            }
            return this._private.id;
        }

        set id(newValue) {
            this._private.ucpModel.id = newValue;
            this._private.id = newValue;
        }

        get model() {
            if (!this._private || !this._private.ucpModel) {
                return null;
            }
            return this._private.ucpModel._private.model;
        }

        set model(data) {
            //if (!data.id)
            //    data.id = this.id;
            this._private.ucpModel.properties = data;
            //this._private.ucpModel.controller = this.controller;
        }


        get displayName() {
            let displayName = null;
            if (this._private && this._private.displayName) {
                return this._private.displayName;
            }
            if (displayName === undefined && this.model !== undefined && this.model !== this) {
                displayName = this.model.displayName;
            }
            if (displayName === undefined && this.model !== undefined) {
                displayName = this.model.name;
            }

            if (displayName === undefined) {
                displayName = "UcpComponent: " + this.name;
            }

            return displayName;
        }
        set displayName(newValue) {
            this._private.displayName = newValue;
        }

        get ucpModel() {
            return Thinglish.lookupInObject(this, "_private.ucpModel");
        }

        set ucpModel(newValue) {
            if ((newValue instanceof UcpModel)) {
                this._private.ucpModel = newValue;
                return;
            }
            if (!(newValue instanceof UcpComponent)) {
                return;
            }
            if (!newValue.id) {
                newValue.id = this.id;
            }

            this._private.ucpModel.properties = newValue;
        }

        get defaultView() {
            if (!this.Store || !this.controller) {
                return null;
            }

            const defaultView = this.Store.lookup(DefaultView);
            if (defaultView) {
                return defaultView[0];
            }

            return this.controller.getDefaultView(this._private.ucpModel);
        }

        get itemView() {
            if (!this.controller) {
                return null;
            }
            return this.controller.getItemView(this.ucpModel);
        }

        get ditailView() {
            if (!this.controller) {
                return null;
            }
            return this.controller.ditailView(this.ucpModel);
        }

        get overView() {
            if (!this.controller) {
                return null;
            }
            return this.controller.getOverView(this.ucpModel);
        }

        get detailsView() {
            if (!this.controller) {
                return null;
            }
            return this.controller.getDetailsView(this.ucpModel);
        }

        getViews() {
            if (!this.controller) {
                return null;
            }
            return this.controller.getViews(this._private.ucpModel);
        }

        get documentElement() {
            if (!this.defaultView) {
                return null;
            }
            return this.defaultView.documentElement;
        }

        get properties() {
            return Thinglish.lookupInObject(this, "ucpModel.properties");
        }
        set properties(newValue) {
            return Thinglish.setInObject(this, "ucpModel.properties", newValue);
        }

        getProperties() {
            return this.properties;
        }

        get container() {
            if (!this.defaultView) {
                return null;
            }
            return this.defaultView.container;
        }

        //Programmatic way for creating component
        add(component) {
            if (!component) {
                return;
            }

            let view = (component.onBeingAddedUseView instanceof Function) ? component.onBeingAddedUseView(this) : null;

            if (component instanceof UcpView) {
                view = component;
                component = component.ucpComponent;
            }

            if (!view) {
                view = component.defaultView;
            }

            // equivalent to super.add(component) where super is a reference on the Container interface
            this._private.interfaces.Container.add(component);

            //if (this.handleSelection)
            //    component.controller.eventSupport.addEventListener("selected", this, this.handleSelection.bind(this));

            let thisView = this.defaultView;
            view = (this.onAddUseView) ? this.onAddUseView(component) : view;

            if (!view) {
                view = component.itemView;
            }

            if (thisView) {
                let container = this.container;
                if (container && container.element) {
                    container = container.element;
                }

                if (thisView.hasOwnProperty('add')) {
                    thisView.add(view);
                } else {
                    view.append(container);
                }

                view.parentUcpView = thisView;
                this.children.push(view.ucpComponent);
            }
        }

        select(item) {
            this.controller.selectedItem = item;
            if (this.handleSelection) {
                this.handleSelection({
                    source: this
                }, item);
            }
        }

        update() {
            this.controller.updateAllViews(this.ucpModel);
        }

        get actionIndex() {
            if (this._private.actionIndex === undefined) {
                this._private.actionIndex = {};
            }
            return this._private.actionIndex;
        }


        set actionIndex(newValue) {
            if (this._private.actionIndex === undefined) {
                this._private.actionIndex = {};
            }

            if (Array.isArray(newValue)) {
                newValue.forEach(
                    actionConstant => {
                        let action = Action.parse(actionConstant);
                        action.setObject(this);
                        if (action.visibility === "public") {
                            Action.theWorkflow.registerAction(action);
                        }
                        this._private.actionIndex[action.actionId] = action;

                        let actionId = action.actionId;
                        let aClassName = actionId.substr(0, actionId.lastIndexOf("."));
                        let aClass = window[aClassName];
                        if (aClass) {
                            aClass.type.actionIndex[action.actionId] = Action.parse(actionConstant);
                        }
                    }
                );
            }


        }

        get actions() {
            return Object.keys(this.actionIndex).map(k => this.actionIndex[k]);
        }

        checkSave() {

            let setPrimaryAction = Action.lookup(ActionsPanel.ACTION_SET_PRIMARY);
            if (!setPrimaryAction) {
                return;
            }
            let saveAction = Action.parse(UcpComponent.ACTION_SAVE);
            saveAction.setObject(this);
            let currentPrimaryAction = setPrimaryAction.object.primaryAction;

            let state = "do Nothing";
            if (this.ucpModel.updateObject) {
                if (currentPrimaryAction.actionId !== saveAction.actionId) {
                    state = "add";
                }
            } else {
                if (currentPrimaryAction.actionId === saveAction.actionId) {
                    state = "remove";
                }
            }

            let currentActions = null;
            switch (state) {
                case "add":
                    currentActions = setPrimaryAction.object.currentActions;
                    currentActions.unshift(currentPrimaryAction);
                    setPrimaryAction.object.actionButtons = currentActions;
                    setPrimaryAction.do(saveAction);
                    break;
                case "remove":
                    currentActions = setPrimaryAction.object.currentActions;
                    let newPrimaryAction = currentActions.shift();
                    setPrimaryAction.do(newPrimaryAction);
                    Action.do(ActionsPanel.ACTION_SET_BUTTONS, currentActions);
                    break;
                default:
                    break;
            }
        }


        save() {
            let result = this.ucpModel.save();
            this.checkSave();
            return result;
        }

    });

var Controller = Namespace.declare("tla.EAM.layer3",
    class Controller extends Interface {
        get eamLayer() {
            return 1;
        };
    });

var UcpController = Namespace.declare("tla.EAMD",
    class UcpController extends Thing {
        static get implements() { return [Component, EventSupport]; };
        constructor() {
            super();
            //Thinglish.implement(this, Controller);
            //Thinglish.implement(this, EventSupport);

            this.duringModelChange = false;

            this.model2ViewMap = new IndexedObjectsMap().init("id");
            this.model2ViewMap.allowCollections = true;
            this._viewModelCounter = 0;
            this.collectionIndex = 0;
        }

        init(aClass) {
            if (!this._private)
                this._private = {};

            this.ucpComponentClass = aClass;

            return this;
        }

        get basePath() {
            return Thinglish.lookupInObject(this, "ucpComponentClass.IOR.loader.basePath") + "/";
        }

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

        set selectedItem(newValue) {
            this._private.selectedItem = newValue;
            this.eventSupport.fire("selected", this, newValue);
        }

        getUcpComponents() {
            return this.model2ViewMap.getAllKeys().map(mapEntry => {
                return mapEntry.key.ucpComponent;
            });
        }

        registerView(unit, viewInterface) {
            if (!this.ucpComponentClass.type.weBeans)
                this.ucpComponentClass.type.weBeans = new IndexedObjects().init("id");

            var weBean = unit.template;

            unit.weBean = UcpComponentSupport.weBeanRegistry.register(weBean, this);
            this.ucpComponentClass.type.weBeans.add(unit.weBean);

        }

        /*
              get ucpComponentClass() {
                  if (!this._private.ucpComponentClass) return null;
                  return eval(this._private.ucpComponentClass.constructor.name);
              }
              */
        addWeBeanNode(weBeanNode, customElement, data) {
            //if (this.duringModelChange)
            //    return;

            if (weBeanNode.view)
                return;

            var existingCustomElement = document.getElementById(customElement.id);
            if (existingCustomElement && existingCustomElement != customElement)
                return;

            //this.duringModelChange = true;

            console.debug("add WeBeanNode: " + weBeanNode.name);
            //UcpComponentSupport.allViews.add(view);

            if (data == null)
                data = weBeanNode.data

            var id = weBeanNode.data.id;
            if (!id)
                id = weBeanNode.id;

            var model = this.setModel(data, id);

            var view = new DefaultView().init(this);
            view.weBean = weBeanNode;

            view.parentUcpView;
            this.addView(model, view);

        }

        addView(ucpModel, view) {
            //var view = new UcpView().init(this);
            //view.weBean = weBean;

            if (ucpModel.ucpComponent)
                ucpModel.ucpComponent.Store.register(view.constructor, view);

            this.model2ViewMap.add(ucpModel, view);
            //console.debug("added model2ViewMap",ucpModel.ucpComponent, view);
            //console.debug("  new model2ViewMap",this.model2ViewMap, this);
            //this.model2ViewMap.add(ucpModel.value, view);

            this.updateView(view, ucpModel);
            this.eventSupport.fire("newView", this, view);

            return view;
        }

        updateView(view, ucpModel) {
            this.duringModelChange = true;
            // setting ucpModel is not allowed in the view. set model does an update of all the views
            view.model = ucpModel;
            this.duringModelChange = false;
        }

        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++;
            }

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

            if (model == null) {
                model = this.ucpComponentClass.getInstance().model;
                model.properties = data;

                if (!model.id) {
                    model.id = id;
                }

                model.eventSupport.addEventListener("onModelChanged", this, this.modelChanged.bind(this));
            } else {
                if (model._private.properties !== data) {
                    model.properties = data;
                    const views = this.model2ViewMap.getValue(model.id);
                    for (let v in views) {
                        this.updateView(views[v], model);
                    }
                }
            }

            return model;

        }

        modelChanged(changeEvent, data) {
            var ucpModel = changeEvent.source;
            console.debug("model Changed", ucpModel.ucpComponent.id, data);
            this.updateAllViews(ucpModel);
        }

        updateAllViews(ucpModel) {
            //var controller = changeEvent.target;
            var views = this.model2ViewMap.getValue(ucpModel.id);

            this.duringModelChange = true;

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

                if (view.documentElement == null) {
                    this.model2ViewMap.remove(ucpModel.id, view);
                } else

                    this.updateView(view, ucpModel);
            }

            this.duringModelChange = false;
        }

        getViews(ucpModel) {
            return this.model2ViewMap.getValue(ucpModel.id);
        }

        getDefaultView(ucpModel) {
            const defaultView = ucpModel.ucpComponent.Store.lookup(DefaultView);
            if (defaultView) {
                return defaultView[0];
            }

            ucpModel.eventSupport.addEventListener("onModelChanged", this, this.modelChanged.bind(this));

            let view = null;
            if (Thinglish.lookupInObject(ucpModel, "ucpComponent.createDefaultView")) {
                view = ucpModel.ucpComponent.createDefaultView().init(this);
            } else {
                view = new DefaultView().init(this);
                //view = new JavaScriptObject().init(ucpModel.value).defaultView;
            }

            //if (this.ucpComponentClass.type.ucpComponentDescriptor)
            //    view.weBean = this.ucpComponentClass.type.ucpComponentDescriptor.defaultWebBeanUnit.weBean;

            view = this.addView(ucpModel, view);

            return view;
        }

        getItemView(ucpModel) {
            //var itemView = ucpModel.ucpComponent.Store.lookup(ItemView);
            //if (itemView) return itemView[0];

            //@todo make sure this is only done once
            ucpModel.eventSupport.addEventListener("onModelChanged", this, this.modelChanged.bind(this));

            let view = null;
            if (Thinglish.lookupInObject(ucpModel, "ucpComponent.createItemView")) {
                view = ucpModel.ucpComponent.createItemView().init(ucpModel.ucpComponent);
            } else {
                //view = new DefaultItem().init(this).defaultView;
                //var object = new JavaScriptObject().init(ucpModel.value).itemView;
                const model = ucpModel.value;
                let jsObject = ucpModel.ucpComponent.Store.lookup(JavaScriptObject);
                if (Array.isArray(jsObject)) {
                    [jsObject] = jsObject;
                }

                if (model instanceof JavaScriptObject) {
                    jsObject = model;
                }

                if (!(jsObject instanceof JavaScriptObject)) {
                    jsObject = new JavaScriptObject().init(ucpModel);
                    ucpModel.ucpComponent.Store.register(JavaScriptObject, jsObject);
                }

                view = jsObject.itemView;
            }

            view = this.addView(ucpModel, view);

            return view;
        }

        getOverView(ucpModel) {
            const overView = ucpModel.ucpComponent.Store.lookup(OverView);
            if (overView) {
                return overView[0];
            }

            //@todo make sure this is only done once
            ucpModel.eventSupport.addEventListener("onModelChanged", this, this.modelChanged.bind(this));

            /*
            var view = null;
            if (Thinglish.lookupInObject(ucpModel, "ucpComponent.createOverView")) {
                view = ucpModel.ucpComponent.createOverView().init(this);
            } else {
                //view = new DefaultOverview().init(this).defaultView;
                //var object = new JavaScriptObject().init(ucpModel.value).itemView;
                view = new JavaScriptObject().init(ucpModel).overView;
            }
            */

            let view = null;
            if (Thinglish.lookupInObject(ucpModel, "ucpComponent.createOverView")) {
                view = ucpModel.ucpComponent.createOverView().init(this);
            } else {
                //view = new DefaultItem().init(this).defaultView;
                //var object = new JavaScriptObject().init(ucpModel.value).itemView;
                const model = ucpModel.value;
                let jsObject = ucpModel.ucpComponent.Store.lookup(JavaScriptObject);
                if (Array.isArray(jsObject)) {
                    [jsObject] = jsObject;
                }

                if (model instanceof JavaScriptObject) {
                    jsObject = model;
                }

                if (!(jsObject instanceof JavaScriptObject)) {
                    jsObject = new JavaScriptObject().init(ucpModel);
                    ucpModel.ucpComponent.Store.register(JavaScriptObject, jsObject);
                }

                view = jsObject.overView;
            }


            view = this.addView(ucpModel, view);

            return view;
        }

        getDetailsView(ucpModel) {
            var detailsView = ucpModel.ucpComponent.Store.lookup(DetailsView);
            if (detailsView)
                return detailsView[0];

            //@todo make sure this is only done once
            ucpModel.eventSupport.addEventListener("onModelChanged", this, this.modelChanged.bind(this));

            /*
            var view = null;
            if (Thinglish.lookupInObject(ucpModel, "ucpComponent.createItemView")) {
                view = ucpModel.ucpComponent.createDetailsView().init(this);
            } else {
                //view = new DefaultDetails().init(this).defaultView;
                //var object = new JavaScriptObject().init(ucpModel.value).itemView;
                view = new JavaScriptObject().init(ucpModel).detailsView;
            }
            */

            var view = null;
            if (Thinglish.lookupInObject(ucpModel, "ucpComponent.createDetailsView")) {
                view = ucpModel.ucpComponent.createDetailsView().init(this);
            } else {
                //view = new DefaultItem().init(this).defaultView;
                //var object = new JavaScriptObject().init(ucpModel.value).itemView;
                var model = ucpModel.value;
                var jsObject = ucpModel.ucpComponent.Store.lookup(JavaScriptObject);
                if (Array.isArray(jsObject))
                    jsObject = jsObject[0];

                if (model instanceof JavaScriptObject)
                    jsObject = model;

                if (!(jsObject instanceof JavaScriptObject)) {
                    jsObject = new JavaScriptObject().init(ucpModel);
                    ucpModel.ucpComponent.Store.register(JavaScriptObject, jsObject);
                }

                view = jsObject.detailsView;
            }
            view = this.addView(ucpModel, view);

            return view;
        }

        getCustomView(viewInterface, ucpComponent) {
            let ucpModel = ucpComponent.ucpModel;
            let model = ucpModel.model;

            if (!viewInterface)
                viewInterface = DefaultView;

            //if (model extends UcpModel)

            let customView = ucpModel.ucpComponent.Store.lookup(viewInterface);
            if (customView)
                return customView[0];
            else {
                // @todo check why DefaultFolder doeas not use the JavaObject DefaultDetailsView
                customView = ucpModel.ucpComponent.defaultView;
            }

            //@todo make sure this is only done once
            ucpModel.eventSupport.addEventListener("onModelChanged", this, this.modelChanged.bind(this));

            var aViewClass = null;
            this.ucpComponentClass.type.weBeans.getValues().forEach(weBean => {
                if (weBean.viewInterface) {
                    if (weBean.viewInterface == viewInterface) {
                        aViewClass = weBean.customClass;
                    }
                }
            });

            var view = customView;
            if (aViewClass && !(view instanceof aViewClass))
                view = new aViewClass();

            if (view) {
                view = this.addView(ucpModel, view);
                view.init(ucpComponent);
            }

            return view;
        }
    });

var View = Namespace.declare("tla.EAM.layer3",
    class View extends Interface {
        get eamLayer() {
            return 5;
        }
    });

var UcpView = Namespace.declare("tla.EAMD",
    class UcpView extends Thing {
        static get implements() { return [View, Container, EventSupport]; };
        constructor() {
            super();
            if (!this.constructor._instanceCount)
                this.constructor._instanceCount = 0;
            this.viewIdNr = this.constructor._instanceCount;
            this.viewId = this.constructor.name + "_" + this.constructor._instanceCount++;
            /*
            if (!this._private)
                this._private = {};
            if (!this._protected)
                this._protected = {};
            */
            //Thinglish.implement(this, View);
            //Thinglish.implement(this, Container);
            //Thinglish.implement(this, EventSupport);
        }

        init(controller) {
            if (this._private.isInitialized)
                return this;
            if (!controller)
                return null;

            this.viewId = controller.ucpComponentClass.name + "_" + this.viewId;

            this._private.isInitialized = true;
            if (controller)
                this.controller = controller;

            if (this.controller.ucpComponentClass.type.weBeans) {
                this.weBean = this.controller.ucpComponentClass.type.weBeans.lookup(this.constructor.name);

                // if previous line failed, use the first existing weBean
                if (!this.weBean)
                    this.weBean = this.controller.ucpComponentClass.type.weBeans.getValues()[0];
            }

            return this;
        }

        loadWeBean() {
            if (Thinglish.lookupInObject(this, "ucpComponent.type.ucpComponentDescriptor.defaultWebBeanUnit.weBean"))
                return this.ucpComponent.type.ucpComponentDescriptor.defaultWebBeanUnit.weBean;

            //if (this.controller.ucpComponentClass.type.ucpComponentDescriptor)
            //return this.controller.ucpComponentClass.type.ucpComponentDescriptor.defaultWebBeanUnit.weBean;
            return null;
        }

        get displayName() {
            let displayName = null;
            if (this._private && this._private.displayName)
                return this._private.displayName;

            if (displayName == undefined && this.ucpComponent.model != undefined)
                displayName = this.ucpComponent.model.displayName;
            if (displayName == undefined && this.ucpComponent.model != undefined)
                displayName = this.ucpComponent.model.name;
            if (displayName == undefined)
                displayName = "UcpView: " + this.name;

            return displayName;

        }
        set displayName(newValue) {
            this.ucpComponent.properties.displayName = newValue;
        }

        // UcpView.add
        add(component) {
            var view = null;
            if (!component)
                return;
            if (component.onBeingAddedUseView)
                view = component.onBeingAddedUseView(this);

            if (!view)
                view = component.defaultView;
            if (component instanceof UcpView) {
                view = component;
                component = component.ucpComponent;
            }

            /* respect overwrite in ucpComponent

                    if (this.ucpComponent.add) {
                       return this.ucpComponent.add(component);
                    }
                    */

            // equivalent to super.add(component) where super is a reference on the Container interface
            this._private.interfaces.Container.add(view);

            var thisComponent = this.ucpComponent;
            if (thisComponent.handleSelection)
                component.controller.eventSupport.addEventListener("selected", thisComponent, thisComponent.handleSelection.bind(thisComponent));
            if (thisComponent.onAddUseView) {
                view = thisComponent.onAddUseView(component);
            }

            if (!view)
                view = component.itemView;

            if (this) {
                var container = this.container;
                if (container && container.element)
                    container = container.element;

                view.append(container);

                view.parentUcpView = this;
                this.children.push(view);
                return view;

                // @todo meke updateView consistent
                //if (view.updateView)
                //view.updateView(view);

            }
        }

        select() {
            console.info("select", this.ucpModel._private.model);
            UcpComponentSupport.getUcpComponent4Object(this).then(ucpComponent => {
                if (ucpComponent instanceof JavaScriptObject)
                    ucpComponent = ucpComponent.model;

                if (ucpComponent.handleSelection instanceof Function) {
                    ucpComponent.handleSelection(event, ucpComponent);
                } else {
                    Action.do(Workflow.ACTION_SELECT, ucpComponent);
                    if (Array.isArray(ucpComponent)) {
                        Action.do(OverView.ACTION_SHOW, ucpComponent);
                        Action.do(ActionsPanel.ACTION_SET_PRIMARY, DetailsView.ACTION_SHOW);
                        Action.do(ActionsPanel.ACTION_SET_BUTTONS, [What.ACTION_PUSH, What.ACTION_POP, OverView.ACTION_SHOW]);
                    } else {
                        Action.do(DetailsView.ACTION_SHOW, ucpComponent);
                        Action.do(ActionsPanel.ACTION_SET_PRIMARY, OverView.ACTION_SHOW);
                        Action.do(ActionsPanel.ACTION_SET_BUTTONS, [What.ACTION_PUSH, What.ACTION_POP, DetailsView.ACTION_SHOW]);
                    }
                }
            })
        }

        maintainJsonData(data) {
            this.json = this.json || {};
            data = this.model;
            const ignoredProperties = ["json", "weBean", "model", "ondata", "renderer", "controller", "ucpModel", "version", "type", "namespace", "Store", "parent"];
            for (const i in data) {
                if (!i.startsWith("_") && ignoredProperties.every(p => p !== i)) {
                    const value = data[i];
                    let ignoredTypes = ["String", "Function", "StructrObject", "UcpComponent", "UcpModel", "UcpView", "Namespace", "Map", "IndexedObjects", "IndexedObjectsMap", "JavaScriptObjectDescriptor"]
                    if (
                        value != null &&
                        typeof value === "object" &&
                        ignoredTypes.indexOf(value.constructor.name) == -1 &&
                        !Array.isArray(value)
                    ) {
                        if (
                            value.type &&
                            value.type.extends &&
                            ignoredTypes.indexOf(value.type.extends.name) > -1
                        ) continue;

                        try {
                            this.json[i] = JSON.stringify(value);
                        } catch (error) {
                            console.debug("could not maintain JSON", i);
                        }
                    }
                }
            }
            return data;
        }

        render() {
            //var data = this.ucpModel.properties;
            //var data = this.ucpModel._private.model;

            //if (!data.id)
            //    data.id = this.ucpModel.id;

            //data.viewId = this.viewId;
            this.parentViewId = this.parent ? this.parent.viewId : "document.body";
            if (!this.content) {
                this.content = "<div class='fullHeight' webean-role='" + this.viewId + ":container'></div>";
            }
            //data.content = data.content.replace(/"/g, "'")

            //this.ucpModel.properties = data;
            this.maintainJsonData();

            // @todo hi igor, got the idea. but we have to do that in this.updateModelFromCustomTag() or in maintainDomElement....
            // since model can also be a primitive at any time... model.class throws an error on a brimitive value for model
            // therefore i uncommented it

            let template = this.weBean.textTemplate;
            template = template.replace(/"/g, "'");
            template = template.replace(/\$-\{/g, '${');

            const textWeBean = new Function("return `" + template + "`;").call(this);
            return textWeBean;
        }

        maintainDomElement(element) {
            if (!element) {
                element = document.body;
            }
            if (element.element) {
                element = element.element;
            }

            let ucpComponent = this.ucpComponent;
            if (ucpComponent) {
                if (!ucpComponent._private.isInitialized)
                    ucpComponent.init();
            }

            this.maintainContainment(UcpComponentSupport.getUcpView4Element(element), this);

            const textWeBean = this.render();
            const newWeBean = UcpComponentSupport.weBeanRegistry.textTemplate2TemplateContent(textWeBean);
            this._private.element = newWeBean;

            newWeBean.ucpView = this;
            const id = newWeBean.getAttribute("id");
            if (!id) {
                newWeBean.setAttribute("viewId".toDash(), this.id);
            }
            const viewId = newWeBean.getAttribute("viewId".toDash());
            if (!viewId) {
                newWeBean.setAttribute("viewId".toDash(), this.viewId);
            }


            let found = this.updateChildren(newWeBean); //declarative way

            if (found === false && this.children.length > 0) {
                this.children.forEach(
                    c => {
                        let isContained = newWeBean.querySelector("[view-id='" + c.viewId + "']");
                        if (isContained == undefined) {
                            if (this.container &&
                                c.container &&
                                this.container.element !== c.container.element)
                                this.container.element.append(c.documentElement);
                            else {
                                let container = newWeBean.querySelector("[webean-role='" + this.viewId + ":container']");
                                if (container) {
                                    console.log("updating children of ", this.id);
                                    container.replaceWith(c.documentElement);
                                }
                            }
                        }
                    }
                );
            }



            if (ucpComponent) {
                if (typeof ucpComponent.updateView === "function") {
                    //console.warn("maintainDomElement: update View from Component", this.viewId, ucpComponent.id);
                    ucpComponent.updateView(this);
                }


                if (typeof this.updateView === "function") {
                    this.updateView(this);
                    //console.warn("maintainDomElement: custom update View", this.viewId, this.ucpComponent.id);
                }
            }

            //if (!element)
            //element = this.container;
            return element;
        }

        maintainContainment(parentUcpView, currentUcpView) {
            if (!parentUcpView)
                return;
            if (parentUcpView == currentUcpView) {
                //console.error("child == parent", currentUcpView, parentUcpView)
                return;
            }
            if (!currentUcpView.parent) {
                currentUcpView.parent = parentUcpView;
            }
            //parentUcpView.ucpModel.ucpComponent._private.interfaces.Container.add(currentUcpView.ucpModel.ucpComponent);
            parentUcpView._private.interfaces.Container.add(currentUcpView);
            if (parentUcpView.ucpComponent != currentUcpView.ucpComponent)
                parentUcpView.ucpComponent._private.interfaces.Container.add(currentUcpView.ucpComponent);
        }

        prepend(element) {

            element = this.maintainDomElement(element);
            var newWeBean = this._private.element;

            element.prepend(this._private.element);
            this.setDomReady(newWeBean);

            return newWeBean;
        }

        append(element) {

            element = this.maintainDomElement(element);
            var newWeBean = this._private.element;

            element.append(newWeBean);
            this.setDomReady(newWeBean);
            return newWeBean;
        }

        replace(element) {
            if (!element) {
                return;
            }

            element = this.maintainDomElement(element);
            const newWeBean = this._private.element;

            if (element.children.length > 0) {
                let container = newWeBean.querySelector("[webean-role='" + this.viewId + ":container']");

                if (this.ucpModel.ucpComponent.container) {
                    container = this.ucpModel.ucpComponent.container;
                }

                if (container.element) {
                    container = container.element;
                }

                while (container && element.firstChild) {
                    container.appendChild(element.firstChild);
                }
            }
            //element.parentNode.replaceChild(newWeBean, element);
            element.replaceWith(newWeBean);
            this.setDomReady(newWeBean);

            return newWeBean;
        }

        update(element) {
            if (!element) {
                element = this.documentElement;
            }
            if (!element) {
                return;
            }

            element = this.maintainDomElement(element);

            const currentContainer = this.container;
            const newWeBean = this._private.element;

            if (currentContainer && currentContainer.tag.children.length > 0) {
                const newContainer = this.container.element;
                //newWeBean.querySelector("[webean-role='" + this.viewId + ":container']");

                while (newContainer && currentContainer.tag.firstChild) {
                    newContainer.appendChild(currentContainer.tag.firstChild);
                }
            }

            element.replaceWith(newWeBean);
            this.setDomReady(newWeBean);

            // @todo remove till comment and comment. test. should be fine already. consolidatet everyThing in maintainDomElement 
            if (!this.ucpModel.ucpComponent._private.isInitialized) {
                this.ucpModel.ucpComponent.init();
            }
            /*
            if (this.ucpComponent) {
                if (!this.ucpComponent._private.isInitialized)
                    this.ucpComponent.init();


                if (this.ucpComponent.updateView)
                    this.ucpComponent.updateView(this);

             //@todo meke updateView consistent
             if (typeof this.updateView === "function")
                this.updateView(this);
            }
            */
            return newWeBean;
        }

        setDomReady(element) {
            element.ucpView.domReady = true;
            if (Thinglish.lookupInObject(element, "ucpView.ucpComponent.model.onDomReady") instanceof Function) {
                element.ucpView.ucpComponent.model.onDomReady(element.ucpView);
            }
        }

        clear() {
            let container = this.container;
            if (container.element) {
                container = container.element;
            }
            while (container && container.firstChild) {
                container.firstChild.remove();
            }
            this._private.interfaces.Container.clear();
        }

        get parentUcpComponent() {
            return (this.parent) ? this.parent.ucpComponent : this.ucpComponent.parent;
        }

        updateChildren(parentElement) {
            let found = false;
            const viewList = UcpComponentSupport.getAllViews(parentElement);
            viewList.forEach(view => {
                const tag = view.tag;
                if (!tag.parentElement) {
                    return;
                }
                let weBean = UcpComponentSupport.weBeanRegistry.registry.getValue(view.is);
                if (weBean) {
                    [weBean] = weBean;
                } else {
                    console.warn("WeBean not found: ", view.is);
                    return;
                }
                const ucpComponent = new weBean.controller.ucpComponentClass();
                if (ucpComponent && !ucpComponent._private.isInitialized) {
                    ucpComponent.init();
                }
                const newView = ucpComponent.defaultView;

                newView.updateModelFromCustomTag(ucpComponent.ucpModel, tag);

                // equivalent to super.add(component) where super is a reference on the Container interface
                this._private.interfaces.Container.add(newView);
                //this.ucpModel.ucpComponent.add(newView.ucpModel.ucpComponent);

                if (this.ucpModel.ucpComponent.onNewChild) {
                    this.ucpModel.ucpComponent.onNewChild(newView);
                }


                //newView.update(tag);
                newView.replace(tag);
                found = true;
            });
            return found;
        }

        updateModelFromCustomTag(ucpModel, customTag) {
            ucpModel.ucpComponent.classes = ucpModel.ucpComponent.classes || '';

            Array.from(customTag.attributes).forEach(attribute => {
                let name = attribute.name;
                if (name === "id") {
                    return;
                }
                if (name === 'class') { // defined in component tag like <my-new-component class...>
                    ucpModel.ucpComponent.classes = `${ucpModel.ucpComponent.classes} ${attribute.value}`;
                    return;
                }

                if (name.startsWith("data-")) {
                    name = name.substr(5);
                }
                name = name.toCamelCase();

                let value = attribute.value;
                //if (value.startsWith("{") || value.startsWith("["))
                if (value.startsWith("[")) {
                    value = JSON.parse(value);
                }

                if (name === "properties" && value !== "undefined") {
                    value = JSON.parse(value);
                    for (const p in value) {
                        this.model[p] = value[p];
                    }
                    this.ucpComponent.model = this.model;
                } else {
                    if (name.startsWith("view")) {
                        this[name] = value;
                    } else {
                        Thinglish.defineAccessors(
                            this,
                            name,
                            () => this.model.properties[name],
                            newValue => this.model.properties[name] = newValue
                        );
                    }
                    this.model[name] = value;

                    if (typeof(value) === 'string' && value.startsWith("{")) {
                        this.json = this.json || {};
                        this.json[name] = value;
                    }
                    if (Array.isArray(value)) {
                        this.collection = value;
                    }
                }
            });
            this.ucpComponent.model = this.model;
            //ucpModel.properties = data;
            return ucpModel;
        }

        get documentElement() {
            /*
                    if (!this._private.delegate) return null;
                    var element = this._private.delegate.element;
                    var de = document.getElementById(this.viewId);
                    if (de && de != element) {
                        this._private.delegate.element = de;
                        element = de;
                    }
                    */

            let element = this._private.element;
            if (!element) {
                element = this._private.element = document.getElementById(this.viewId);
            }

            if (element && !element.ucpView) {
                element.ucpView = this;
            }
            return element;
        }

        get ucpComponent() {
            if (!this._private) this._private = {};
            return (this._private.ucpModel) ? this._private.ucpModel.ucpComponent : null;
        }

        get ucpModel() {
            if (!this._private) this._private = {};
            return this._private.ucpModel;
        }

        get model() {
            return Thinglish.lookupInObject(this, "_private.ucpModel._private.model");
        }

        set model(newValue) {
            if (newValue.ucpComponent) {
                newValue.ucpComponent.Store.register(this.constructor, this);
            } else {
                newValue.ucpComponent = newValue.ucpComponent;
            }

            if (newValue.ucpModel) {
                newValue = newValue.ucpModel;
            }

            this._private.ucpModel = newValue;
            this.update();
        }

        get properties() {
            return this.ucpModel.properties
        }

        set properties(data) {
            return this.ucpModel.properties = data;
        }

        get modelId() {
            return this._private.ucpModel.id;
        }

        get typeName() {
            let typeName = "Array";
            if (!Array.isArray(this.model)) {
                typeName = Thinglish.lookupInObject(this, "model.type.descriptor.typeName");
            }
            return typeName;
        }

        get id() {
            return this.viewId;
        }

        get weBean() {
            this._private.weBean = this._private.weBean || this.loadWeBean();
            return this._private.weBean;
        }

        set weBean(newValue) {
            this._private.weBean = newValue;
        }

        remove() {
            //this.controller.model2ViewMap.remove()
            //this.documentElement.outerHTML = "";
            if (this.documentElement) {
                this.documentElement.remove();
            }
            if (this.parent) {
                this.parent.children.remove(this);
            }
        }

        get container() {
            if (!this.documentElement) {
                return null;
            }
            return Array.from(this.documentElement.parentElement.querySelectorAll("[webean-role*='" + this.viewId + ":container']")).map(e => {
                return {
                    id: e.getAttribute("id"),
                    name: e.getAttribute("webean-role"),
                    tag: e,
                    element: e
                };
            })[0];
        }

        get parent() {
            return this._private.parent;
            //if (!this.ucpModel.ucpComponent.parent) return null;
            //return this.ucpModel.ucpComponent.parent.defaultView;
        }

        set parent(newParentUcpView) {
            //this.ucpModel.ucpComponent.parent = newParentUcpView.ucpModel.ucpComponent;
            this._private.parent = newParentUcpView;
        }

        toggleCssClass(cssClassName, element, state, withNewCssClassName) {
            // e.g. this.toggleCssClass("col-sm-3",null,false,"col-sm-12") will replace to "col-sm-12"
            if (element == undefined)
                element = this.documentElement;
            if (!element)
                return;
            console.debug(element.classList);

            if (state == undefined)
                state = !element.classList.contains(cssClassName);

            if (state) {
                if (!element.classList.contains(cssClassName))
                    if (withNewCssClassName)
                        element.classList.replace(withNewCssClassName, cssClassName);
                    else
                        element.classList.add(cssClassName);
            } else {
                if (withNewCssClassName)
                    element.classList.replace(cssClassName, withNewCssClassName);
                else
                    element.classList.remove(cssClassName);
            }
            console.debug(element.classList);

            return state;
        }

    });

var DefaultView = Namespace.declare("tla.EAM.layer5",
    class DefaultView extends UcpView {
        get name() {
            if (this.model)
                return this.model.name;
            return this._private.name;
        }

        set name(newValue) {
            this._private.name = newValue;
        }

        get container() {
            if (!this.documentElement) {
                if (!this.weBean) {
                    return null;
                }
                //this.maintainDomElement();
                return null;
            }
            return Array.from(this.documentElement.parentElement.querySelectorAll("[webean-role*='" + this.viewId + ":container']")).map(e => {
                return {
                    id: e.getAttribute("id"),
                    name: e.getAttribute("webean-role"),
                    tag: e,
                    element: e
                };
            })[0];
        }

    });

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

        init() {
            return this;
        }
    });

var Model = Namespace.declare("tla.EAM.layer3",
    class Model extends Interface {
        get eamLayer() {
            return 2;
        }
        get isLoaded() {
            console.error("Not yet Implemented in Model ", this.constructor.name);
        }

        load() {
            console.error("Not yet Implemented in Model ", this.constructor.name);
        }

        save() {
            console.error("Not yet Implemented in Model ", this.constructor.name);
        }

        get properties() {
            return this;
        }

        set properties(data) {
            console.error("Not yet Implemented in Model ", this.constructor.name);
        }
        get id() {
            return (this._private) ? this._private.id : null;
        }

        set id(newValue) {
            this._private.id = newValue;
        }

        modelWillChange() {}
    });

var UcpModel = Namespace.declare("tla.EAMD",
    class UcpModel extends Thing {
        static get implements() { return [Model, EventSupport]; };
        constructor() {
            super();
            //Thinglish.initType(this);
            //Thinglish.initType(this, Namespaces.lookupInObject(this, "constructor.type.package"));
            //Thinglish.implement(this, Model);

            //Thinglish.implement(this, EventSupport);
            this.eventSupport.addEventListener("onModelWillChange", this, this.modelWillChange.bind(this));

            this._private.isInitialized = false;
            this.collection = null;
        }

        init(ucpComponent) {
            this.ucpComponent = ucpComponent;
            // @todo change into getter
            this.controller = ucpComponent.controller;
            // @todo change into getter
            this.id = this.ucpComponent.id;
            return this;
        }

        async create( /* dynamic arguments */ ) {
            let newUcpCompoent = this.ucpComponent.getInstance();
            newUcpCompoent.init(arguments);
            return newUcpCompoent;
        }

        get model() {
            return this.valueOf();
        }

        get value() {
            return this.valueOf();
        }

        valueOf() {
            if (!this._private)
                return null;
            return this._private.model;
        }

        get properties() {
            if (!this._private)
                return null;
            return this._private.properties;
        }

        set properties(data) {
            // @todo workaround bug
            if (!(data instanceof ManagedProperties)) {
                const original = data;
                this._private.model = original;
            } else {
                console.error("Detected BUG: model.properties is of Type ManagedProperties");
            }
            if (data == null)
                return;

            var newProperties = new ManagedProperties();

            /*

            if (data instanceof UcpComponent && !data.type.descriptor) {
               console.warn("Model is a UcpComponent. Consider to migrate the component:", data);
            }
            let descriptor = JavaScriptObjectDescriptor.describe(data,false,false);
            if (!descriptor)
                console.warn("Failed to describe: ",data, "in", this);
            
            */

            // @todo workaround bug: prevent the recursion on get and set
            //var dataClone = {};
            //Thinglish.cloneValue(data, dataClone);
            //Object.assign(dataClone, data);

            var typeName = data.type;
            if (!Thinglish.isPrimitive(typeName))
                typeName = typeName.name;
            //            if (typeName == null)
            //                return;

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

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

            //var ownPropertyDescriptors = Object.getOwnPropertyDescriptors(data);
            var array = Thinglish.getOwnPropertyDescriptorArray(data);

            array.forEach(ownPropertyDescriptor => {
                var prop = ownPropertyDescriptor.propertyName;
                //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];
                    const propertyName = prop;
                    let getter = new Function(`
                        var value = this._private.model['${propertyName}']; 
                        return value;
                        `);

                    //			console.log("  getter: "+getter);
                    getter = getter.bind(this);

                    let setter = new Function("newValue", `
                        if (this._private.model["${propertyName}"] == newValue) return;
                        if (!this._private.changeLog)
                            this._private.changeLog = {};
                        this._private.changeLog["${propertyName}"] = {from: this._private.model["${propertyName}"], to:newValue, state:'will'};

                        this._private.eventSupport.fire("onModelWillChange", this, {
                                "oldVlaue":this._private.model["${propertyName}"],
                                "newValue":newValue,"property":"${propertyName}"
                        });
                        this._private.changeLog["${propertyName}"].state = 'done';

                        this._private.model["${propertyName}"] = newValue;
                        this._private.eventSupport.fire("onModelChanged", this, {
                                "newValue":newValue,"property":"${propertyName}"
                        });
                    `);

                    setter = setter.bind(this);

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

            this._private.isInitialized = true;
            return this;
        }

        get local() {
            if (this.localObjects)
                return this.localObjects.getValues()[0];
            return this;
        }

        get isLoaded() {
            return this._private.isLoaded;
        }
        set isLoaded(newValue) {
            this._private.isLoaded = newValue;
        }

        get isLoading() {
            return this._private.isLoading;
        }
        set isLoading(newValue) {
            this._private.isLoading = newValue;
        }

        load() {
            this.isLoaded = true;
            return Promise.resolve(this);
        }

        save() {
            this.eventSupport.fire("onModelChanged", this, this.updateObject);
            this._private.changeLog = {};
            //console.error("Not yet Implemented in UcpModel ", this.constructor.name);
        }

        modelWillChange(event, item) { //if (this.controller)
            //this.controller.modelChanged(this, item, event);
            //console.error("Not yet Implemented in UcpModel ", this.constructor.name);
        }

        get updateObject() {
                if (!this._private.changeLog) this._private.changeLog = {};
                let c = this._private.changeLog;
                let updateData = Object.keys(c).reduce(
                    (result, current /*, index, array*/ ) => {
                        let from = c[current].from;
                        let to = c[current].to;
                        if (Array.isArray(from))
                            [from] = from;
                        if (Array.isArray(to))
                            [to] = to;

                        if ((to instanceof StructrObject) && from.id == to.id)
                            c[current].state = "ignore";
                        if (c[current].state !== "ignore")
                            result[current] = c[current].to;
                        return result;
                    }, {} /*initial result*/ );
                if (updateData && Object.keys(updateData).length == 0) updateData = null;
                else updateData.id = this.model.id;
                return updateData;
            }
            /*
            checkChangeLog() {
                if (!this._private.changeLog) this._private.changeLog = {};
                Object.keys(this._private.changeLog).forEach(p => {
                    if (this.isDeepEqual(this._private.changeLog[p].to, this.properties[p]))
                        delete this._private.changeLog[p];
                });
                return this._private.changeLog;
            }
            */
        get changeLog() {
            if (!this._private.changeLog) this._private.changeLog = {};
            return this._private.changeLog;
        }

        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 UcpComponentRepository = Namespace.declare("tla.EAM.layer3",
    class UcpComponentRepository extends Interface {
        static get dependencies() {
            return ["/EAMD.ucp/Components/com/ceruleanCircle/EAM/5_ux/StructrES6Client/0.5.0/StructrES6Client.component.xml"];
        }

        constructor() {
            super();
        }

        get eamLayer() {
            return 1
        };
        init() {
            console.debug("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 get implements() { return [UcpComponentRepository]; };
        static get 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.debug("StructrUcpComponentRepository ready");
            this.ucpComponentIORs = this.discover();
            this.loadAndStartAll(this.ucpComponentIORs);
            return this;
        }

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

            // appplies for objects loaded bz the DocumentScriptLoader
            if (!componentClass.IOR) {
                return null;
            }

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

            const ucpComponents = await Structr.find("UcpComponentDescriptor", null, parameter);
            if (!ucpComponents) {
                return null;
            }

            let ucpComponentDescriptor = null;
            if (ucpComponents.length > 0) {
                ucpComponentDescriptor = ucpComponents.shift();
            }

            if (!ucpComponentDescriptor) {
                console.warn("UcpComponentDescriptor not found on UcpComponentRepository: " + componentClass.IOR.loader.descriptorPath);
                return null;
            }
            console.debug(ucpComponentDescriptor);
            ucpComponentDescriptor.class = componentClass;
            ucpComponentDescriptor.local.class = componentClass;
            ucpComponentDescriptor.local.IOR = ucpComponentDescriptor.IOR;
            ucpComponentDescriptor.IOR.referencedObject = ucpComponentDescriptor;
            ucpComponentDescriptor = ucpComponentDescriptor.local;

            componentClass.type.ucpComponentDescriptor = 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.debug("found Descriptor: " + ucpComponentDescriptor.properties.name);
                    ucpComponentDescriptor.class = componentClass;
                    return ucpComponentDescriptor.properties.uses.map((dependency => {
                        console.debug("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];
                            }

                            console.debug(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.debug(aClass.namespace.package);
                else
                    console.debug("Class has no namespcae: ", aClass.name);

                if (aClass.type) {
                    console.debug(aClass.type.ucpComponentDescriptor);
                    if (aClass.type.ucpComponentDescriptor) {
                        aClass.type.ucpComponentDescriptor.updateDependenciesinRepository();
                        updatedClasses.push(aClass.type.ucpComponentDescriptor);
                    }
                } else
                    console.debug("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.debug("Save Descriptor: ", aClass);
                    //aClass.save();
                }
            });
        }

    });

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

        init() {
            return this;
        }

        get eamLayer() {
            return 3
        };

    });

var DefaultUcpComponentDescriptor = Namespace.declare("tla.EAMD",
    class DefaultUcpComponentDescriptor extends UcpModel {
        static get implements() { return [UcpComponentDescriptor]; };
        constructor() {
            super();
            //Thinglish.implement(this, UcpComponentDescriptor);
        }

        get properties() {
            return this._private.properties;
        }

        set properties(ucpComponentStructrObject) {
            //var data = ucpComponentStructrObject._private.model;
            var data = ucpComponentStructrObject._private.model;
            var ior = new IOR().init(ucpComponentStructrObject._private.model.path);
            var descriptor = Namespace.lookupInObject(ior, "class.type.ucpComponentDescriptor");
            if (descriptor) {
                if (!ucpComponentStructrObject.localObjects) {
                    ucpComponentStructrObject.localObjects = new IndexedObjects();
                    ucpComponentStructrObject.localObjects.push(descriptor);
                } else
                    ucpComponentStructrObject.localObjects.push(descriptor);

                //@todo check if objects are deepEqual or if update has happened

                //return descriptor;
            }

            super.properties = data;
            this.name = this._private.model.name;
            this.id = this._private.model.id;
            this.type.name = this._private.model.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) {}
                    }
                    */

            if (this.class && this.class.type.ucpComponentDescriptor)
                return this.class.type.ucpComponentDescriptor;

            this.save = ucpComponentStructrObject.save;
            this.load = ucpComponentStructrObject.load;

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

            return this;
        }

        get isLoaded() {
            return this.properties.type != null;
        }

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

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

        getDependencies() {
            var p = Promise.all(this.properties.uses.map(d => {
                return d.load();
            }));
            // @todo not required any more...
            p.then(array => {
                var enhancedDependencies = array.map(d => {
                    d.usedBy = this.class;
                    return d;
                });
                this.class.type.dependencies = new IndexedObjects().init("properties.path");
                this.class.type.dependencies.addArray(enhancedDependencies);

                return enhancedDependencies;
            });
            // check ussage ad remove
            return p;
        }

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

        getAllDependencies() {
            if (!this.class.type.dependencies)
                return this.getDependencies().then(d => {
                    return this.getAllDependencies()
                });
            return this.class.type.dependencies.getValues().map(d => {
                return d.IOR;
            });
        }

        getDependencyDiff() {
            if (!this.class.type.dependencies)
                return this.getDependencies().then(d => {
                    return this.getDependencyDiff()
                });
            var mergedDependencies = this.class.type.dependencies;

            var diff = [];
            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;
                    }
                    if (mergedDependencies.add({
                            properties: {
                                path: d.url
                            },
                            IOR: d
                        }))
                        diff.push({
                            properties: {
                                path: d.url
                            },
                            IOR: d
                        });
                    return d;
                });

                var codeDependencies = new IndexedObjects().init("properties.path");
                codeDependencies.addArray(this.class.dependencies.map(d => {
                    return {
                        properties: {
                            path: d.url
                        },
                        IOR: d
                    }
                }));
                mergedDependencies.getValues().forEach(d => {
                    if (codeDependencies.add(d)) {
                        diff.push(d);
                        mergedDependencies.add({
                            properties: {
                                path: d.properties.path
                            },
                            IOR: new IOR().init(d.properties.path)
                        });
                    }
                });
            }
            return diff;

        }

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

            var loadedDependencies = this.properties.uses;
            loadedDependencies.forEach(d => {
                mergedDependencies.add(d);
                // console.debug(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.debug(d);
            });
        }

        initializeUnits() {
            var units = this.properties.units;
            // @todo load only runtime relevant units with Structr.find("Units",null,{ucpComponentDescriptor: this.id, lifecycle:"runtime"})
            return Promise.all(units.map(unit => {
                // load unit details from Structr
                if (unit.isLoaded === false) {
                    return unit.load();
                }

                return unit;

            })).then(units => {
                if (this.class)
                    console.debug("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 => {
                unit.template = weBean;
                return weBean;
            });
        }
    });


var Action = Namespace.declare("tla.EAM.layer2",
    class Action extends Interface {
        static get SEVERITY_DANGER() { return "danger" };
        static get SEVERITY_WARNING() { return "warning" };
        static get SEVERITY_DEFAULT() { return "default" };

        static publish(actionId, actionDisplayName, methodDescriptor, users, roles) {
            if (!Action.theWorkflow)
                return;
            if (methodDescriptor == undefined) {
                console.warn("Action.publish: method for '", actionId, "'not found");
                return;
            }


            let action = new DefaultAction().init(actionId, actionDisplayName, methodDescriptor, users, roles);
            Action.theWorkflow.registerAction(action);
            return action;
        }

        static create(ucpComponent, actionId, actionDisplayName, methodDescriptor, users, roles) {
            if (methodDescriptor == undefined) {
                console.warn("Action.create: method for '", actionId, "'not found");
                return;
            }

            let action = new DefaultAction().init(actionId, actionDisplayName, methodDescriptor, users, roles);

            ucpComponent.actionIndex[action.actionId] = action;
            return action;
        }

        static parse(actionConstant) {
            let tokens = actionConstant.split(":");
            let isActionId = tokens[0];
            if (isActionId !== "actionId") {
                console.warn("Not an actionConstant: ", actionId, actionConstant)
                return null;
            }

            let action = new DefaultAction();
            action.visibility = tokens[1];
            let iOpen = tokens[2].indexOf("[");
            action.actionId = tokens[2].substr(0, iOpen);
            iOpen++;
            action.methodId = action.actionId.substr(tokens[2].lastIndexOf(".") + 1);
            action.displayName = tokens[2].substr(iOpen, tokens[2].lastIndexOf("]") - iOpen);
            if (tokens[3] != undefined)
                action.severity = tokens[3];

            return action;
        }

        static lookup(actionId) {
            if (!Action.theWorkflow)
                return;
            if (actionId instanceof DefaultAction)
                return actionId;

            let action = null;

            if (Action.isActionConstant(actionId)) {
                action = Action.parse(actionId)
                actionId = action.actionId;
            }

            action = Action.theWorkflow.lookup(actionId);
            if (action == undefined && actionId) {
                let aClassName = actionId.substr(0, actionId.lastIndexOf("."));
                let aClass = window[aClassName];
                if (aClass) {
                    action = aClass.type.actionIndex[actionId];
                }
            }

            if (action == undefined) {
                console.warn("no such action:", actionId);
                return false;
            }
            return action;
        }

        static isActionConstant(aString) {
            if (typeof aString !== "string") return false;
            if (aString.startsWith("actionId:")) return true;
            return false;
        }

        static do(actionId) {
            if (!Action.theWorkflow)
                return;

            let action = Action.lookup(actionId);
            if (action === false) return false;


            let args = [];

            for (let i in arguments)
                args[i] = arguments[i];

            args.splice(0, 1);

            return action.do(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);

            //return await action.method.call(action.method.object, args[0],args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9]);

        }


        static get theSelection() {
            return this.theWorkflow.theSelection;
        }

        static get theWorkflow() {
            if (Action._private == undefined)
                Action._private = { _static: { theWorkflow: null } };
            return Action._private._static.theWorkflow;
        }
        static set theWorkflow(newValue) {
            if (Action._private == undefined)
                Action._private = { _static: { theWorkflow: null } };
            if (Action._private._static.theWorkflow !== null)
                console.warn("Workflow will be overwritten", Action._private._static.theWorkflow);
            Action._private._static.theWorkflow = newValue;
        }
    }
);

var DefaultAction = Namespace.declare("com.ceruleanCircle.EAM.5_ux.WODA.ActionsPanel",
    class DefaultAction extends Thing {
        static get implements() { return [Action]; };
        constructor() {
            super();
            //Thinglish.implement(this, Action);
            this.actionId = undefined;
            this.displayName = undefined;
            this.visibility = "protected";
            this.methodId = undefined;
            this.method = undefined;
            // danger, warning, default
            this.severity = Action.SEVERITY_DEFAULT;

            this.users = [];
            this.roles = [];
        }

        init(actionId, displayName, method, users, roles) {
            let o = { actionId, displayName, method, users, roles };
            Object.keys(o).forEach(k => { if (o[k] !== undefined) this[k] = o[k] });
            if (this.users != undefined && !Array.isArray(this.users))
                this.users = [this.users];

            if (this.users.length > 0)
                this.visibility = "user private";
            return this;
        }

        setObject(newValue) {
            this.object = newValue;
            if (this.methodId) {
                this.method = newValue.typeDescriptor.methodIndex[this.methodId];
                if (this.method == undefined) {
                    let feature = newValue.typeDescriptor.featureIndex[this.methodId];
                    if (feature) {
                        let r = newValue.typeDescriptor[feature + "Index"][this.methodId];
                        if (r != undefined)
                            this.method = new MethodDescriptor().init(r.object, r.name, r.feature, r.property);
                    }
                }
            }
        }

        async do() {
            let args = [];

            let length = 0;
            for (let i in arguments) {
                //if (arguments[i] === undefined) continue;
                args.push(arguments[i]);
                length = i;
            }

            for (let i in this.arguments) {
                args[Number(i) + Number(length)] = this.arguments[i];
            }

            if (args[0] instanceof Event) {
                this.event = args[0];
                args.splice(0, 1);
            }

            let action = this;
            Action.theWorkflow.history.push({ action, args })
            console.info("Action will do '", action.actionId, "' with Arguments: ", args);

            if (!action.method)
                action.method = action.object[action.methodId].bind(action.object);

            return await action.method.call(action.method.object, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);

        }

        get name() {
            return this.actionId;
        }
        set name(newValue) {
            this.actionId = newValue;
        }

    }
);