
CTFrontendBuilder.directive("ngBuilderWrap", function ($parentScope, $compile, $timeout, $interval) {
    
    return {
        restrict: 'A',
        link: function(scope, element) {

            /**
             * Some components shouldn't be a child of itslef or other components
             * i.e. section can't be a child of a section or any section children
             */
            scope.insertBeforeComponent = function(name, insertData) {

                var parent = insertData.activeComponent,
                    parentId;

                // search for component in parents
                while ( !parent.is(name) && !parent.hasClass('ct-builder') ) {
                    parent = parent.parent();
                }

                parentId = parent[0].getAttribute('ng-attr-component-id');

                // found component in parents
                if (parentId != 0) {

                    insertData.index = parent.index() + 1;

                    // go level up from component
                    parent = jQuery(parent).parent().closest("[is-nestable]"); 
                    insertData.idToInsert = parent[0].getAttribute('ng-attr-component-id');

                    // activate new component
                    scope.activateComponent(insertData.idToInsert);
                    insertData.activeComponent = scope.getActiveComponent();
                }
            };

            // Example tree structure, for testing only
            scope.tree = 
                    [   // 0
                        {
                            'oxy-timeline':[
                                // 0
                                {
                                    'oxy-timeline-entry':[
                                        // 0
                                        'ct_headline', 
                                        // 1
                                        'ct_text_block', 
                                        // 2
                                        'ct_image'
                                    ]
                                },
                                // 1
                                {
                                    'oxy-timeline-entry':[
                                        // 0
                                        //'ct_text_block', 
                                        // 1
                                        {
                                            'ct_div_block':[
                                                // 0
                                                'ct_headline'
                                            ]
                                        }, 
                                        // 2
                                        'ct_text_block', 
                                        // 3
                                        'ct_image'
                                    ]
                                },
                                // 2
                                {
                                    'oxy-timeline-entry':[
                                        'ct_headline', 'ct_text_block', 'ct_image'
                                    ]
                                }
                            ]
                        },
                        // 1
                        'ct_headline'
                    ]
                
            /**
             * Recusrive function to add a pre-defind components tree
             *
             * @since 3.0
             * @author Ilya K.
             */

            scope.addComponentsTree = function(tree, parentID) {

                //console.log(tree);
                
                //for testing only: if (tree===undefined) tree = scope.tree;

                for (key in tree) { 
                    if (tree.hasOwnProperty(key)){
                        var subTree = tree[key];

                        // add sub tree
                        if (typeof(subTree)==='object') {
                            for (subKey in subTree){ // names
                                if (subTree.hasOwnProperty(subKey)){

                                    var id = scope.addComponent(subKey);
                                    //console.log(subKey + ' added', id);

                                    // add sub tree
                                    if (typeof(subTree[subKey])==='object') {
                                        // recursion
                                        scope.addComponentsTree(subTree[subKey], id);
                                    }
                                    
                                    // add leaf
                                    if (typeof(subTree[subKey])==='string') {
                                        scope.addComponent(subTree[subKey],'',true);
                                    }
                                }
                            }

                            // activate parent
                            if (parentID!==undefined) {
                                scope.activateComponent(parentID);
                                //console.log('activate',parentID);
                            }
                        }

                        // add leaf
                        if (typeof(subTree)==='string') {
                            scope.addComponent(subTree,'',true);    
                        }
                    }
                }

                // activate parent
                if (parentID!==undefined) {
                    scope.activateComponent(parentID);
                    //console.log('activate',parentID);
                }
                    
            }



            scope.addComponents = function(first, second) {
                
                scope.addComponent(first);

                // Fix for inserting second after first and not inside of it.
                // Need a timeout to update a controller scope
                if ( first == "ct_columns") {
                   
                    var timeout = $timeout(function() {
                        // add first column and keep parent active
                        scope.addComponent(second, false, true);

                        $interval.cancel(timeout);
                    }, 0, false);

                    var timeout2 = $timeout(function() {
                        // add second column
                        scope.addColumn(scope.component.active.id);
                        scope.columns[scope.component.active.id] = 2;
                        
                        $interval.cancel(timeout2);
                    }, 500, false);
                }
                else {
                    var timeout = $timeout(function() {
                
                        // add second component
                        scope.waitOxygenTree(function(){
                            scope.addComponent(second);
                        })

                        $interval.cancel(timeout);
                    }, 0, false);
                }
            };

            scope.addTab = function() {

                // save active component ID before activate another
                var activeComponentID   = scope.component.active.id,
                    activeComponentName = scope.component.active.name;

                if ( scope.component.active.name == "oxy_tabs" ) {
                    var wrapper             = scope.getOption("tabs_contents_wrapper"),
                        id                  = jQuery("#"+wrapper).attr('ng-attr-component-id'),
                        addedComponentID    = scope.addComponent("oxy_tab", false, true),
                        activeClass         = scope.getOption('active_tab_class', activeComponentID);
                    
                    scope.addClassToComponentSafe(addedComponentID, activeClass.replace('-active',''));
                    scope.addClassToComponentSafe(addedComponentID, activeClass);
                    scope.activeSelectors[addedComponentID] = activeClass.replace('-active','');

                    var timeout = $timeout(function() {
                        
                        scope.activateComponent(addedComponentID, "oxy_tab");
                        var textBlock = scope.addComponent("ct_text_block", false, true);
                        scope.setOptionModel('ct_content', "Another Tab", textBlock, "ct_text_block");

                        $interval.cancel(timeout);
                    }, 0, false);
                    
                    
                    scope.activateComponent(id, "oxy_tabs_contents");
                    addedComponentIDTwo = scope.addComponent("oxy_tab_content", false, true);
                    scope.addClassToComponentSafe(addedComponentIDTwo, activeClass.replace('tabs-','tabs-contents-').replace('-active',''));
                    scope.activeSelectors[addedComponentIDTwo] = activeClass.replace('tabs-','tabs-contents-').replace('-active','');
                    
                    var timeout = $timeout(function() {
                        
                        scope.activateComponent(addedComponentIDTwo, "oxy_tab_content");
                        var textBlockTwo = scope.addComponent("ct_text_block", false, true);
                        scope.setOptionModel('ct_content', "Another Tab Contents", textBlockTwo, "ct_text_block");

                        var timeoutInner = $timeout(function() {

                            jQuery(scope.getComponentById(addedComponentID)).trigger('click');
                            scope.activateComponent(activeComponentID, activeComponentName);
                            scope.adjustResizeBox();

                            $interval.cancel(timeoutInner);
                        }, 100, false);

                        $interval.cancel(timeout);
                    }, 0, false);
                }
                else if ( scope.component.active.name == "oxy_tabs_contents" ) {
                    var wrapper             = scope.getOption("tabs_wrapper"),
                        id                  = jQuery("#"+wrapper).attr('ng-attr-component-id'),
                        addedComponentID    = scope.addComponent("oxy_tab_content", false, true),
                        activeClass         = scope.getOption('active_tab_class', id);

                    scope.addClassToComponentSafe(addedComponentID, activeClass.replace('tabs-','tabs-contents-').replace('-active',''));
                    scope.activeSelectors[addedComponentID] = activeClass.replace('tabs-','tabs-contents-').replace('-active','');

                    var timeout = $timeout(function() {
                        
                        scope.activateComponent(addedComponentID, "oxy_tab_content");
                        var textBlock = scope.addComponent("ct_text_block", false, true);
                        scope.setOptionModel('ct_content', "Another Tab Contents", textBlock, "ct_text_block");

                        $interval.cancel(timeout);
                    }, 0, false);
                    
                    scope.activateComponent(id, "oxy_tabs");
                    addedComponentIDTwo = scope.addComponent("oxy_tab", false, true);

                    scope.addClassToComponentSafe(addedComponentIDTwo, activeClass.replace('-active',''));
                    scope.addClassToComponentSafe(addedComponentIDTwo, activeClass);
                    scope.activeSelectors[addedComponentIDTwo] = activeClass.replace('-active','');
                    
                    var timeout = $timeout(function() {
                        
                        scope.activateComponent(addedComponentIDTwo, "oxy_tab");
                        var textBlockTwo = scope.addComponent("ct_text_block", false, true);
                        scope.setOptionModel('ct_content', "Another Tab", textBlockTwo, "ct_text_block");

                        var timeoutInner = $timeout(function() {

                            jQuery(scope.getComponentById(addedComponentIDTwo)).trigger('click');
                            // activate back the initial component
                            scope.activateComponent(activeComponentID, activeComponentName);
                            scope.adjustResizeBox();

                            $interval.cancel(timeoutInner);
                        }, 100, false);

                        $interval.cancel(timeout);
                    }, 0, false);
                }
            };
            

            scope.addComponent = function(componentName, type, notActivate, params) {

                scope.cancelDeleteUndo();

                if (scope.log) {
                    console.log("addComponent()", componentName, type, notActivate, params);
                }
                
                var innerContent = jQuery('div.ct-component.ct-inner-content:not(.ct-inner-content-workarea)');

                if(componentName === "ct_inner_content" && innerContent.length > 0) {//&& jQuery('div.ct-component.ct-inner-content').length > 0) {
                    alert('You cannot add more than one Inner Content component to a template.');
                    return;
                }

                if (componentName === "oxy-product-tabs" && jQuery(".oxy-product-tabs").length > 0) {
                    scope.showNoticeModal("<div>You cannot add more than one Product Tabs element</div>");
                    return;
                }

                if(componentName === "ct_inner_content")
                    scope.innerContentAdded = true;


                if ( componentName == "oxy_dynamic_list" ) {    
                    iframeScope.dynamicListActions.actions[scope.component.id] = {};
                }

                var parent = false;
                //console.log("addComponent", componentName);

                // set default options first
                scope.applyComponentDefaultOptions(scope.component.id, componentName);

                var parent_id = null;
                if (undefined != params) {
                    parent_id = params.parent_id;
                }

                var insertData = {},
                    componentTemplate   = scope.getComponentTemplate(componentName, scope.component.id, type, null, parent_id);

                insertData.idToInsert          = scope.component.active.id;
                insertData.activeComponent     = scope.getActiveComponent();
                insertData.index               = false;
                
                if (!insertData.activeComponent) {

                    insertData.activeComponent = document.getElementsByClassName("ct-builder"); 
                    // create jqLite element
                    insertData.activeComponent = angular.element(insertData.activeComponent);

                    insertData.idToInsert = 0;
                }

                // don't allow to insert API components inside itslef at any deep
                if ( scope.isAPIComponent(componentName) ) {
                    scope.insertBeforeComponent("."+componentName, insertData);
                }

                // Section can't be a child of a Section or Header Builder
                if ( componentName == "ct_section" ) {
                    scope.insertBeforeComponent(".ct_section, .oxy_header, .ct_slider", insertData);
                }

                // Modal can't be a child of a Section, Header Builder or another Modal
                if ( componentName == "ct_modal" ) {
                    scope.insertBeforeComponent(".ct_section, .oxy_header, .ct_slider, .ct_modal", insertData);
                    // force apply of default width
                    var timeout = $timeout(function() {
                        scope.setOptionModel("width",iframeScope.component.options[scope.component.active.id].model.width,scope.component.active.id,"ct_modal");
                        $timeout.cancel(timeout);
                    }, 0, false);
                }

                // new columns can't be a child of a new columns
                if ( componentName == "ct_new_columns" ) {
                    scope.insertBeforeComponent(".ct_new_columns", insertData);
                }

                // tabs can't be a child of other tabs
                if ( componentName == "oxy_tabs" || componentName == "oxy_tabs_contents" ) {
                    scope.insertBeforeComponent(".oxy_tabs, .oxy_tabs_contents", insertData);
                }

                // link wrapper or text link can't be a child of a link wrapper
                if ( componentName == "ct_link" || componentName == "ct_link_text") {
                    scope.insertBeforeComponent(".ct_link", insertData);
                }

                // Header Builder can't be a child of a Header Builder or Section
                if ( componentName == "oxy_header" ) {
                    scope.insertBeforeComponent(".oxy_header, .ct_section", insertData);
                }

                // Inner Content can't be a child of a Header Builder or Section
                if ( componentName == "ct_inner_content" ) {
                    scope.insertBeforeComponent(".oxy_header, .ct_section", insertData);
                }
                
                // slider can't be a child of a slider
                if ( componentName == "ct_slider" ) {
                    scope.insertBeforeComponent(".ct_slider", insertData);
                }

                // Icon Box can't be a child of a Icon Box
                if ( componentName == "oxy_icon_box" ) {
                    scope.insertBeforeComponent(".oxy_icon_box", insertData);
                }

                // Pricing Box can't be a child of a Pricing Box
                if ( componentName == "oxy_pricing_box" ) {
                    scope.insertBeforeComponent(".oxy_pricing_box", insertData);
                }

                // while editing an outer template, do not nest anything under ct_inner_content
                if ( scope.component.active.name == "ct_inner_content" && !scope.isTemplateInnerContent(scope.component.active)) {
                    scope.insertBeforeComponent(".ct_inner_content", insertData);
                }

                // only add Buttons or Links to Pricnig Box and Icon Box via +Add
                if ( componentName != "ct_link_button" && componentName != "ct_link_text" && type !== "builtin") {
                    scope.insertBeforeComponent(".oxy_pricing_box, .oxy_icon_box", insertData);
                }

                // check if we add a column into a columns
                if ( scope.component.active.name == "ct_columns" && componentName == "ct_column" ) {
                    
                    var innerWrap = scope.getInnerWrap(insertData.activeComponent);
                    innerWrap.append(componentTemplate);
                    
                    scope.cleanInsert(insertData.activeComponent);

                    // insert to Components Tree active element
                    callback = scope.insertComponentToTree;

                    // update model
                    scope.setOptionModel("width", "100", scope.component.id, "ct_column");
                }

                // check if we add a row into a Header Builder
                else if ( scope.component.active.name == "oxy_header" && componentName == "oxy_header_row" ) {
                    
                    var innerWrap   = scope.getInnerWrap(insertData.activeComponent);
                    callback        = scope.insertComponentToTree;
                    
                    // insert to DOM
                    innerWrap.append(componentTemplate);
                    scope.cleanInsert(insertData.activeComponent);
                }

                // check if we add a div into new columns
                else if ( scope.component.active.name == "ct_new_columns" && componentName == "ct_div_block" ) {
                    
                    var innerWrap   = scope.getInnerWrap(insertData.activeComponent);
                    callback        = scope.insertComponentToTree;

                    // insert to DOM
                    innerWrap.append(componentTemplate);
                    scope.cleanInsert(insertData.activeComponent);
                }

                // check if we add anything other than div into new columns and columns not empty
                else if ( scope.component.active.name == "ct_new_columns" && componentName != "ct_div_block" && 
                          insertData.activeComponent.children(".ct_div_block").length > 0) {
                    
                    var firstColumn         = insertData.activeComponent.children(".ct_div_block").first();
                    insertData.idToInsert   = parseInt(firstColumn.attr("ng-attr-component-id"));
                    callback                = scope.insertComponentToTree;
                    
                    // insert to DOM
                    scope.cleanInsert(componentTemplate, firstColumn);
                }

                // check if we add anything other than Div into Tabs or Tabs Contents
                else if ( (scope.component.active.name == "oxy_tabs" || scope.component.active.name == "oxy_tabs_contents") && componentName != "ct_div_block" && 
                          insertData.activeComponent.children(".ct_div_block").length > 0) {
                    
                    var firstChild         = insertData.activeComponent.children(".ct_div_block").first();
                    insertData.idToInsert   = parseInt(firstChild.attr("ng-attr-component-id"));
                    callback                = scope.insertComponentToTree;
                    
                    // insert to DOM
                    scope.cleanInsert(componentTemplate, firstChild);
                }

                // add Header Row after Header Row
                else if ( scope.component.active.name == "oxy_header_row" && componentName == "oxy_header_row" ) {
                    
                    parent              = insertData.activeComponent.parent();
                    insertData.index    = insertData.activeComponent.index() + 1; // save current component index
                    callback            = scope.insertComponentToParent;
                    
                    // insert to DOM
                    scope.cleanInsert(componentTemplate, parent, insertData.index);
                }

                // add to Header Left when trying to add to Header Row
                else if ( scope.component.active.name == "oxy_header_row" && componentName !== "oxy_header_left" && componentName !== "oxy_header_center" && componentName !== "oxy_header_right") {

                    var oxyHeaderLeft       = insertData.activeComponent.find(".oxy-header-left");
                    insertData.idToInsert   = parseInt(oxyHeaderLeft.attr("ng-attr-component-id"));
                    callback                = scope.insertComponentToTree;
                    
                    // insert to DOM
                    scope.cleanInsert(componentTemplate, oxyHeaderLeft);
                }

                // add to Header Left when trying to add to Header
                else if ( scope.component.active.name == "oxy_header" && componentName !== "oxy_header_left" && componentName !== "oxy_header_center" && componentName !== "oxy_header_right") {

                    var oxyHeaderLeft       = insertData.activeComponent.find(".oxy-header-left");
                    insertData.idToInsert   = parseInt(oxyHeaderLeft.attr("ng-attr-component-id"));
                    callback                = scope.insertComponentToTree;
                    
                    // insert to DOM
                    scope.cleanInsert(componentTemplate, oxyHeaderLeft);
                }

                // add Header columns inside Header Row
                else if ( componentName == "oxy_header_left" || componentName == "oxy_header_center" || componentName == "oxy_header_right" ) {
                    
                    var innerWrap       = scope.getInnerWrap(insertData.activeComponent);
                    callback            = scope.insertComponentToTree;
                    
                    // insert to DOM
                    innerWrap.append(componentTemplate);
                    scope.cleanInsert(insertData.activeComponent);
                }

                // Ul
                else if ( scope.component.active.name == "ct_ul" && componentName != "ct_li" ){
                    
                    parent              = insertData.activeComponent.parent();
                    insertData.index    = insertData.activeComponent.index() + 1;
                    callback            = scope.insertComponentToParent;

                    // insert to DON
                    scope.cleanInsert(componentTemplate, parent, insertData.index);
                } 

                else if ( scope.component.active.name == "ct_if_else_wrap" && componentName != "ct_if_wrap" && componentName != "ct_else_wrap" ){
                    
                    parent              = insertData.activeComponent.parent();
                    insertData.index    = insertData.activeComponent.index() + 1;
                    callback            = scope.insertComponentToParent;

                    // insert to DON
                    scope.cleanInsert(componentTemplate, parent, insertData.index);
                }  
                else if ( scope.component.active.name == "oxy_dynamic_list" && (componentName != "ct_div_block" || insertData.activeComponent.children('.ct_div_block').length > 0)){

                    var innerWrap = insertData.activeComponent.children('div:first-child');
                    
                    if(innerWrap.length > 0) {
                        scope.cleanInsert(componentTemplate, innerWrap, insertData.index);

                        // insert to Components Tree active element
                        callback = scope.insertComponentToChild;
                    }
                    else {
                        parent              = insertData.activeComponent.parent();
                        insertData.index    = insertData.activeComponent.index() + 1;
                        callback            = scope.insertComponentToParent;

                        // insert to DON
                        scope.cleanInsert(componentTemplate, parent, insertData.index);
                    }
                } 
                // Li
                else if ( scope.component.active.name == "ct_li" && componentName != "ct_li" ) {
                    
                    parent              = insertData.activeComponent.parent().parent();                    
                    insertData.index    = insertData.activeComponent.parent().index() + 1;
                    callback            = scope.insertComponentToGrandParent;

                    // insert to DOM
                    scope.cleanInsert(componentTemplate, parent, insertData.index);
                } 
                
                // insert Slide after Slide
                else if ( scope.component.active.name == "ct_slide" && componentName == "ct_slide" ) {
                    
                    parent              = insertData.activeComponent.parent().parent();
                    insertData.index    = insertData.activeComponent.parent().index() + 1; // save current component index
                    callback            = scope.insertComponentToParent;

                    // insert to DOM
                    scope.cleanInsert(componentTemplate, parent, insertData.index);
                }

                // insert API component after itself
                else if ( scope.component.active.name == componentName && scope.isAPIComponent(componentName) ) {
                    
                    parent              = insertData.activeComponent.parent().closest('[is-nestable]');
                    insertData.index    = insertData.activeComponent.parent().closest('ct-component').index() + 1; // save current component index
                    callback            = scope.insertComponentToParent;

                    // insert to DOM
                    scope.cleanInsert(componentTemplate, parent, insertData.index);
                } 

                // check if we add anything other than Slide into Slider and Slider doesn't empty
                else if ( scope.component.active.name == "ct_slider" && componentName != "ct_slide" && 
                          insertData.activeComponent.find(".unslider-active").length > 0) {
                    
                    var activeSlide         = insertData.activeComponent.find(".unslider-active").children(".ct_slide");
                    insertData.idToInsert   = parseInt(activeSlide.attr("ng-attr-component-id"));
                    callback                = scope.insertComponentToTree;

                    // insert to DOM
                    scope.cleanInsert(componentTemplate, activeSlide);
                }

                // check if we add anything other than Slide into Slider and Slider is empty
                else if ( scope.component.active.name == "ct_slider" && componentName != "ct_slide" && 
                          insertData.activeComponent.find(".unslider-active").length == 0) {
                    
                    parent              = insertData.activeComponent.parent();
                    insertData.index    = insertData.activeComponent.index() + 1;
                    callback            = scope.insertComponentToParent;

                    // insert to DOM
                    scope.cleanInsert(componentTemplate, parent, insertData.index);
                }
                
                // handle nestable components
                else if ( insertData.activeComponent[0].attributes['is-nestable'] && scope.canBeChild(scope.component.active.name, componentName)) {

                    // append to element
                    if((jQuery('body').hasClass('ct_inner') || jQuery('body', window.parent.document).hasClass('ct_inner')) && (parseInt(insertData.idToInsert) === 0 || parseInt(insertData.idToInsert) > 100000)) {
                        //reassign the component to the ct_inner_content
                        if(jQuery('.ct-inner-content.ct-component').length > 0) {

                            var innerContentID = parseInt(jQuery('.ct-inner-content.ct-component').attr('ng-attr-component-id'));
                            if (insertData.idToInsert!=innerContentID) {
                                insertData.idToInsert=innerContentID;
                                insertData.index = false;
                                scope.activateComponent(insertData.idToInsert);
                                insertData.activeComponent = scope.getActiveComponent();
                                //parent = insertData.activeComponent;
                                callback = scope.insertComponentToTree;
                            }
                        }
                    }

                    var innerWrap = scope.getInnerWrap(insertData.activeComponent);
                    if ( type == "builtin" ) {
                        innerWrap = scope.getBuiltInWrap(insertData.activeComponent);
                    }
                    scope.cleanInsert(componentTemplate, innerWrap, insertData.index);

                    // insert to Components Tree active element
                    callback = scope.insertComponentToTree;

                }

                // allow to insert builtin components to non nestable components
                else if ( type == "builtin" ) {
                    
                    innerWrap = scope.getBuiltInWrap(insertData.activeComponent);
                    scope.cleanInsert(componentTemplate, innerWrap, insertData.index);

                    // insert to Components Tree active element
                    callback = scope.insertComponentToTree;
                }

                // not nestable elements
                else {
                    
                    parent = insertData.activeComponent.parent();
                    // insert to Components Tree active element parent
                    callback = scope.insertComponentToParent;

                    // find first nestable parent
                    if ( ! parent[0].attributes['is-nestable'] && ! parent.hasClass('ct-inner-wrap') ) {
                        while ( ! parent[0].attributes['is-nestable'] && ! parent.hasClass('ct-inner-wrap') ) {
                            parent = parent.parent();
                        }
                        // insert to Components Tree active element grand parent
                        callback = scope.insertComponentToGrandParent;
                    }

                    if (scope.component.options[scope.component.active.id]['original']['oxy_builtin']=='true') {
                        parent = parent.parent();
                    }

                    // get index to insert after current component
                    insertData.index = insertData.activeComponent.index() + 1;

                    // append to parent
                    scope.cleanInsert(componentTemplate, parent, insertData.index);
                }

                // TODO: This is stupid, we need to redo it to be just "type"
                if ( type == "shortcode" ) {
                    var isShortcode = true;
                }

                if ( type == "widget" ) {
                    var isWidget = true;
                }

				if ( type == "data" ) {
					var isData = true;
				}

                if ( type == "sidebar" ) {
                    var isSidebar = true;
                }

                if ( type == "nav_menu" ) {
                    var isNavMenu = true;
                }

                if ( type == "builtin" ) {
                    var isBuiltIn = true;
                    scope.builtinComponents[scope.component.id] = true;
                }
                
                var component = {
                    id : scope.component.id,
                    name : componentName,
                    isShortcode : isShortcode,
                    isWidget: isWidget,
					isData: isData,
                    isSidebar: isSidebar,
                    isNavMenu: isNavMenu,
                    isBuiltIn: isBuiltIn,
                    index: insertData.index,
                };
                
                // insert to Components Tree
                scope.findComponentItem(scope.componentsTree.children, insertData.idToInsert, callback, component);

                // activate component
                if (!notActivate) {
                    scope.activateComponent(scope.component.id, componentName);
                }

                // Lets keep the newly added component in memory, this will help to distinguish these from the ones loaded from db
                scope.justaddedcomponents = scope.justaddedcomponents || [];
                scope.justaddedcomponents.push(scope.component.id);

                // update sortable
                //jQuery('.ct-sortable').sortable('update');

                // update options
                scope.setOption(scope.component.id, componentName);
                scope.setFirstTimeOptions(scope.component.id, componentName);

                // rebuild Slider each time new slide is added
                if ( componentName == "ct_slide") {
                    var timeout = $timeout(function() {
                        
                        scope.rebuildDOM(insertData.idToInsert);

                        $interval.cancel(timeout);
                    }, 0, false);
                }
                // rebuild Parent component each time this one is added
                if ( typeof(params) !== 'undefined' && params['rebuild_parent']) {
                    var timeout = $timeout(function() {
                        console.log('rebuild', insertData.idToInsert)
                        scope.rebuildDOM(insertData.idToInsert);

                        $interval.cancel(timeout);
                    }, 0, false);
                }

                // if it is inserted inside an oxy dynamic list component, then update the list
                if(componentName !== 'ct_span') {
                    var oxyList = scope.getComponentById(component.id).parent().closest('.oxy-dynamic-list');
                    if(oxyList.length > 0 && !oxyList.hasClass('oxy-dynamic-list-edit')) {
                        
                        if(scope.afterComponentsAddedSignal) {
                            clearTimeout(scope.afterComponentsAddedSignal);
                        }

                        scope.afterComponentsAddedSignal = setTimeout(function() {
                            scope.updateRepeaterQuery(parseInt(oxyList.attr('ng-attr-component-id')));
                        }, 500);
                    }
                }

                // look for any parent that should be rebuilt and rebuild it
                scope.rebuildDOMChangeParent(insertData.idToInsert);

                // check if it was the first child and parent is AJAX element
                var parent = scope.findComponentItem(scope.componentsTree.children, insertData.idToInsert, scope.getComponentItem);
                if ( parent.children && parent.children.length === 1 && scope.isAJAXElement(parent.name)) {
                    scope.rebuildDOM(insertData.idToInsert);
                }

                // increment id
                scope.component.id++;

                $parentScope.closeAllTabs();
                scope.unsavedChanges();

                // scroll to the component after selector is parsed
                var interval = 0;
                if ( componentName == "ct_slide" || componentName == "oxy_social_icons" ) {
                    interval = 1000;
                }
                var timeout = $timeout(function() {
                    var component = scope.getActiveComponent();
                    if (component) {
                        $parentScope.scrollToComponent(component.attr('id'));
                    }
                    $interval.cancel(timeout);
                }, interval, false);

                // Adding 3 children of a Header Builder Row
                if ( componentName == "oxy_header_row" ) {                    
                    var timeout = $timeout(function() {
                        scope.waitOxygenTree(function(){
                            scope.addComponent("oxy_header_left", false, true);
                            scope.addComponent("oxy_header_center", false, true);
                            scope.addComponent("oxy_header_right", false, true);
                        })
                        $interval.cancel(timeout);
                    }, 0, false);
                }

                // Adding children of an if_else_wrap
                if ( componentName == "ct_if_else_wrap" ) {                    
                    var timeout = $timeout(function() {
                        scope.addComponent("ct_if_wrap", false, true, {parent_id: component.id});
                        scope.addComponent("ct_else_wrap", false, true, {parent_id: component.id});

                        $interval.cancel(timeout);
                    }, 0, false);
                }

                // Adding a child div to a repeater
                if ( componentName == "oxy_dynamic_list" ) {    
                    var timeout = $timeout(function() {
                        scope.waitOxygenTree(function(){
                            scope.addComponent("ct_div_block", false, true, {parent_id: component.id});
                        })
                        $interval.cancel(timeout);
                    }, 0, false);
                }

                // Adding builtin Icon to Icon Box
                if ( componentName == "oxy_icon_box" ) {                    
                    var timeout = $timeout(function() {
                        scope.waitOxygenTree(function(){
                            var iconID = scope.addComponent("ct_fancy_icon", "builtin", true);
                            scope.setOptionModel('icon-id', "Lineariconsicon-rocket", iconID, "ct_fancy_icon");
                        })
                        $interval.cancel(timeout);
                    }, 0, false);
                }

                // Adding builtin Divs to Superbox
                if ( componentName == "oxy_superbox" ) {                    
                    var timeout = $timeout(function() {
                        scope.waitOxygenTree(function(){
                            var primary     = scope.addComponent("ct_div_block", "builtin", true),
                                secondary   = scope.addComponent("ct_div_block", "builtin", true);

                            scope.component.options[primary]['nicename']    = "Primary";
                            scope.component.options[secondary]['nicename']  = "Secondary";

                            scope.updateFriendlyName(primary);
                            scope.updateFriendlyName(secondary);

                            scope.addClassToComponentSafe(primary,'oxy-superbox-primary');
                            scope.addClassToComponentSafe(secondary,'oxy-superbox-secondary');

                            scope.setOptionModel('align-items', "center", secondary, "ct_div_block");
                            scope.setOptionModel('justify-content', "center", secondary, "ct_div_block");
                            scope.setOptionModel('background-color', "rgba(0,0,0,0.3)", secondary, "ct_div_block");
                            scope.setOptionModel('text-align', "center", secondary, "ct_div_block");


                            scope.activateComponent(primary);
                            var imageId = scope.addComponent("ct_image", false, true);
                            scope.setOptionModel('src', "https://source.unsplash.com/random/480x270", imageId, "ct_image");
                            scope.setOptionModel('width', "400", imageId, "ct_image");

                            scope.activateComponent(secondary);
                            var headlineID = scope.addComponent("ct_headline", false, true);
                            scope.setOptionModel('ct_content', "Superbox", headlineID, "ct_headline");
                            scope.setOptionModel('font-size', "36", headlineID, "ct_headline");
                            scope.setOptionModel('color', "#ffffff", headlineID, "ct_headline");
                            scope.setOptionModel('tag', "h2", headlineID, "ct_headline");

                            // activate Superbox at the end
                            scope.activateComponent(component.id);
                        })
                        $interval.cancel(timeout);
                    }, 0, false);
                }

                // Adding components to Pricing Box
                if ( componentName == "oxy_pricing_box" ) {                    
                    var timeout = $timeout(function() {
                        scope.waitOxygenTree(function(){
                            var imageId = scope.addComponent("ct_image", "builtin", true);
                            scope.setOptionModel('src', "http://placehold.it/300x150", imageId, "ct_image");

                            scope.addComponent("ct_link_button", false, true);
                        })
                        $interval.cancel(timeout);
                    }, 0, false);
                }

                // Adding tabs
                if ( componentName == "oxy_tabs" ) {

                    var UID = oxygenMD5(Date.now().toString()).match(/\d+/)[0].substring(0,4),
                        i = 0;
                    while (UID.length<4&&i<100000) {
                        UID = oxygenMD5(Date.now().toString()).match(/\d+/)[0].substring(0,4);
                        i++;
                    }

                    var tabOneID;
                    
                    scope.waitOxygenTree(function(){
                        var timeout = $timeout(function() { 

                            tabOneID    = scope.addComponent("oxy_tab", false, true); 
                            var tabTwoID    = scope.addComponent("oxy_tab", false, true);
                            var tabThreeID  = scope.addComponent("oxy_tab", false, true);

                            scope.addClassToComponentSafe(tabOneID,'tabs-'+UID+'-tab');
                            scope.addClassToComponentSafe(tabTwoID,'tabs-'+UID+'-tab');
                            scope.addClassToComponentSafe(tabThreeID,'tabs-'+UID+'-tab');

                            scope.addClassToComponentSafe(tabOneID,'tabs-'+UID+'-tab-active');
                            scope.addClassToComponentSafe(tabTwoID,'tabs-'+UID+'-tab-active');
                            scope.addClassToComponentSafe(tabThreeID,'tabs-'+UID+'-tab-active');

                            scope.activeSelectors = scope.activeSelectors || {};
                            scope.activeSelectors[tabOneID] = 'tabs-'+UID+'-tab';
                            scope.activeSelectors[tabTwoID] = 'tabs-'+UID+'-tab';
                            scope.activeSelectors[tabThreeID] = 'tabs-'+UID+'-tab';
                            
                            scope.activateComponent(tabOneID, "oxy_tab");
                            var tabOneTextID = scope.addComponent("ct_text_block", false, true);
                            scope.setOptionModel('ct_content', "Tab #1", tabOneTextID, "ct_text_block");

                            scope.activateComponent(tabTwoID, "oxy_tab");
                            var tabTwoTextID = scope.addComponent("ct_text_block", false, true);
                            scope.setOptionModel('ct_content', "Tab #2", tabTwoTextID, "ct_text_block");

                            scope.activateComponent(tabThreeID, "oxy_tab");
                            var tabThreeTextID = scope.addComponent("ct_text_block", false, true);
                            scope.setOptionModel('ct_content', "Tab #3", tabThreeTextID, "ct_text_block");
                        
                            $interval.cancel(timeout);
                        }, 0, false);
                    })
                    
                    scope.waitOxygenTree(function(){
                        var timeout = $timeout(function() {

                            scope.activateComponent(component.id, "oxy_tabs");
                            var tabsContentsID = scope.addComponent("oxy_tabs_contents", false, true);  
                            scope.activateComponent(tabsContentsID, "oxy_tabs_contents");

                            var tabContentOneID    = scope.addComponent("oxy_tab_content", false, true); 
                            var tabContentTwoID    = scope.addComponent("oxy_tab_content", false, true);
                            var tabContentThreeID  = scope.addComponent("oxy_tab_content", false, true);

                            scope.addClassToComponentSafe(tabContentOneID,'tabs-contents-'+UID+'-tab');
                            scope.addClassToComponentSafe(tabContentTwoID,'tabs-contents-'+UID+'-tab');
                            scope.addClassToComponentSafe(tabContentThreeID,'tabs-contents-'+UID+'-tab');

                            scope.activeSelectors = scope.activeSelectors || {};
                            scope.activeSelectors[tabContentOneID] = 'tabs-contents-'+UID+'-tab';
                            scope.activeSelectors[tabContentTwoID] = 'tabs-contents-'+UID+'-tab';
                            scope.activeSelectors[tabContentThreeID] = 'tabs-contents-'+UID+'-tab';
                            
                            scope.activateComponent(tabContentOneID, "oxy_tab_content");
                            var tabOneTextID = scope.addComponent("ct_text_block", false, true);
                            scope.setOptionModel('ct_content', "Tab Content #1", tabOneTextID, "ct_text_block");

                            scope.activateComponent(tabContentTwoID, "oxy_tab_content");
                            var tabTwoTextID = scope.addComponent("ct_text_block", false, true);
                            scope.setOptionModel('ct_content', "Tab Content #2", tabTwoTextID, "ct_text_block");

                            scope.activateComponent(tabContentThreeID, "oxy_tab_content");
                            var tabThreeTextID = scope.addComponent("ct_text_block", false, true);
                            scope.setOptionModel('ct_content', "Tab Content #3", tabThreeTextID, "ct_text_block");

                            // Set data
                            scope.activateComponent(component.id, "oxy_tabs");
                            scope.setOptionModel('tabs_wrapper', scope.component.options[component.id]['selector'], tabsContentsID, "oxy_tabs_contents");
                            scope.setOptionModel('tabs_contents_wrapper', scope.component.options[tabsContentsID]['selector'], component.id, "oxy_tabs");
                            scope.setOptionModel('active_tab_class', 'tabs-'+UID+'-tab-active', component.id, "oxy_tabs");

                            var timeoutInner = $timeout(function() {
                                jQuery(scope.getComponentById(component.id)).children('.oxy-tabs-wrapper > div').eq(0).trigger('click');
                                scope.activateComponent(tabOneID, "oxy_tab");
                                $interval.cancel(timeoutInner);
                            }, 100, false);

                            var timeoutInner = $timeout(function() {
                                scope.waitOxygenTree(function(){
                                    scope.adjustResizeBox();
                                })
                                $interval.cancel(timeoutInner);
                            }, 200, false);

                        
                            $interval.cancel(timeout);
                        }, 100, false);
                    })
                }


                // Adding tabs
                if ( componentName == "oxy_toggle" ) {

                    var UID = oxygenMD5(Date.now().toString()).match(/\d+/)[0].substring(0,4),
                        i = 0;
                    while (UID.length<4&&i<100000) {
                        UID = oxygenMD5(Date.now().toString()).match(/\d+/)[0].substring(0,4);
                        i++;
                    }

                    var parentID = scope.component.active.parent.id,
                        parentName = scope.component.active.parent.name;

                    scope.addClassToComponentSafe(component.id,'toggle-'+UID);
                    scope.addClassToComponentSafe(component.id,'toggle-'+UID+'-expanded');

                    scope.activeSelectors[component.id] = 'toggle-'+UID;

                    scope.setOptionModel('toggle_active_class', 'toggle-'+UID+'-expanded', component.id, "oxy_toggle");

                    var timeout = $timeout(function() {
                        scope.waitOxygenTree(function(){
                            scope.addComponent("ct_headline", false, true);
                            scope.activateComponent(parentID, parentName);
                            scope.addComponent("ct_text_block", false, true); 
                        })
                        $interval.cancel(timeout);
                    }, 10, false);
                }

                // return inserted component id
                return component.id;
            };
        }
    };
});


/**
 * Fix for Webkit to force CSS apply on input change
 * 
 */

CTFrontendBuilder.directive("ngModel",function($timeout, $interval){
    return {
        restrict: 'A',
        priority: -1, // give it lower priority than built-in ng-model
        link: function(scope, element, attr) {
            scope.$watch(attr.ngModel,function(value){
                if (element.is(':radio')){
                    element.next().addClass('ct-update-css');
                    element.next().removeClass('ct-update-css');
                }
            });
        }
      }
});


/**
 * Float editor panel. Use jQuery Draggable for user custom drag
 *
 */

CTFrontendBuilder.directive('ctFloatEditor', function() {

    return {
        link:function(scope,parentScope,el,attrs) {

            var toolBar                 = angular.element("#oxygen-topbar"),
                toolBarHeight           = toolBar.outerHeight(),
                
                activeComponent         = scope.getActiveComponent(),
                activeComponentHeight   = activeComponent.outerHeight(),
                
                windowPosition          = activeComponent[0].getBoundingClientRect(),
                documentPosition        = activeComponent.offset();


            // init jQuery UI draggable
            el.draggable({ 
                handle: ".ct-draggable-handle",
                containment: "document"
            })

            var draggableHeight = el.outerHeight(),
                draggableWidth  = el.outerWidth(),
                yOffset = xOffset = 0;

            if (el.is(".ct-choose-selector")) {
                yOffset = 110;
            }
            else 
            // calcualte Y offset
            if ( toolBarHeight + draggableHeight > windowPosition.top ) {
                yOffset = activeComponentHeight + 8;
            }
            else {
                yOffset = -(draggableHeight + 8)
            }

            var domTreeWidth = (parentScope.showSidePanel) ? 330 : 0;

            // calcualte X offset
            if ( windowPosition.left + draggableWidth > document.documentElement.clientWidth - 20 - domTreeWidth) {
                xOffset = (document.documentElement.clientWidth - 20 - domTreeWidth) - (windowPosition.left + draggableWidth);
            }

            // place draggable
            el.css({
                'top' : documentPosition.top + yOffset, 
                'left' : documentPosition.left + xOffset,
            })
        }
    }
})


/**
 * Resize Box directive for paddings and margins
 * Used insted of outlines since 2.0
 * 
 * taken from https://github.com/Reklino/angular-resizable
 */

CTFrontendBuilder.directive('oxygenResizeBox', function($timeout,$interval) {

        return {
            restrict: 'AE',
            scope: {
                rOptions: '=',
                rCenteredX: '=',
                rCenteredY: '=',
                rWidth: '=',
                rHeight: '=',
                rFlex: '=',
                rGrabber: '@',
                rDisabled: '@',
            },
            link: function(scope, element, attr) {

                var flexBasis = 'flexBasis' in document.documentElement.style ? 'flexBasis' :
                    'webkitFlexBasis' in document.documentElement.style ? 'webkitFlexBasis' :
                    'msFlexPreferredSize' in document.documentElement.style ? 'msFlexPreferredSize' : 'flexBasis';

                var style = window.getComputedStyle(element[0], null),
                    w,
                    h,
                    dir = scope.rOptions || ['top','right','bottom','left','padding-top','padding-right', 'padding-bottom','padding-left',
                                             'margin-top','margin-right', 'margin-bottom','margin-left'],
                    vx = scope.rCenteredX ? 2 : 1, // if centered double velocity
                    vy = scope.rCenteredY ? 2 : 1, // if centered double velocity
                    inner = scope.rGrabber ? scope.rGrabber : '<span></span>',
                    start,
                    dragDir,
                    axis,
                    info = {},
                    activeComponent,
                    dragHandle,
                    newValue;

                var getClientX = function(e) {
                    return e.touches ? e.touches[0].clientX : e.clientX;
                };

                var getClientY = function(e) {
                    return e.touches ? e.touches[0].clientY : e.clientY;
                };

                var dragging = function(e) {
                    var offset = axis === 'x' ? start - getClientX(e) : start - getClientY(e);

                    switch(dragDir) {

                        case 'margin-top':
                            newValue = ( marginTop - (offset * vy) > 0 ) ? marginTop - (offset * vy) : 0;
                            scope.$parent.parentScope.showStatusBar("margin-top:"+newValue+marginTopUnit);
                            if (scope.$parent.isEditing('id')) {
                                jQuery(activeComponent).css('margin-top',newValue.toString()+marginTopUnit);
                            }
                            else if (scope.$parent.isEditing('class')) {
                                var style = "." + scope.$parent.currentClass + "{margin-top:"+newValue.toString()+marginTopUnit+"}";
                                scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                            }
                            break;

                        case 'margin-bottom':
                            newValue = ( marginBottom - (offset * vy) > 0 ) ? marginBottom - (offset * vy) : 0;
                            scope.$parent.parentScope.showStatusBar("margin-bottom:"+newValue+marginBottomUnit);
                            if (scope.$parent.isEditing('id')) {
                                jQuery(activeComponent).css('margin-bottom',newValue.toString()+marginBottomUnit);
                            }
                            else if (scope.$parent.isEditing('class')) {
                                var style = "." + scope.$parent.currentClass + "{margin-bottom:"+newValue.toString()+marginBottomUnit+"}";
                                scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                            }
                            break;

                        case 'margin-right':
                            newValue = ( marginRight - (offset * vx) > 0 ) ? marginRight - (offset * vx) : 0;
                            scope.$parent.parentScope.showStatusBar("margin-right:"+newValue+marginRightUnit);
                            if (scope.$parent.isEditing('id')) {
                                jQuery(activeComponent).css('margin-right',newValue.toString()+marginRightUnit);
                            }
                            else if (scope.$parent.isEditing('class')) {
                                var style = "." + scope.$parent.currentClass + "{margin-right:"+newValue.toString()+marginRightUnit+"}";
                                scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                            }
                            break;

                        case 'margin-left':
                            newValue = ( marginLeft - (offset * vx) > 0 ) ? marginLeft - (offset * vx) : 0;
                            scope.$parent.parentScope.showStatusBar("margin-left:"+newValue+marginLeftUnit);
                            if (scope.$parent.isEditing('id')) {
                                jQuery(activeComponent).css('margin-left',newValue.toString()+marginLeftUnit);
                            }
                            else if (scope.$parent.isEditing('class')) {
                                var style = "." + scope.$parent.currentClass + "{margin-left:"+newValue.toString()+marginLeftUnit+"}";
                                scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                            }
                            break;

                        case 'padding-top':
                            if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                newValue = ( containerPaddingTop - (offset * vy) > 0 ) ? containerPaddingTop - (offset * vy) : 0;
                                if (scope.$parent.isEditing('id')){
                                    jQuery('.ct-section-inner-wrap', activeComponent).css('padding-top',newValue.toString()+paddingTopUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    var style = "." + scope.$parent.currentClass + " .ct-section-inner-wrap{padding-top:"+newValue.toString()+paddingTopUnit+"}";
                                }
                            }
                            else {
                                newValue = ( paddingTop - (offset * vy) > 0 ) ? paddingTop - (offset * vy) : 0;
                                if (scope.$parent.isEditing('id')){
                                    jQuery(activeComponent).css('padding-top',newValue.toString()+paddingTopUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    var style = "." + scope.$parent.currentClass + "{padding-top:"+newValue.toString()+paddingTopUnit+"}";
                                    scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                                }
                            }
                            scope.$parent.parentScope.showStatusBar("padding-top:"+newValue+paddingTopUnit);
                            if (scope.$parent.parentScope.isActiveName('ct_link_button')) {
                                if (scope.$parent.isEditing('id')){
                                    jQuery(activeComponent).css('padding-bottom',newValue.toString()+paddingTopUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    style += "." + scope.$parent.currentClass + "{padding-bottom:"+newValue.toString()+paddingTopUnit+"}";
                                }
                            }
                            scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                            break;

                        case 'padding-bottom':
                            if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                newValue = ( containerPaddingBottom - (offset * vy) > 0 ) ? containerPaddingBottom - (offset * vy) : 0;
                                if (scope.$parent.isEditing('id')) {
                                    jQuery('.ct-section-inner-wrap', activeComponent).css('padding-bottom',newValue.toString()+paddingBottomUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    var style = "." + scope.$parent.currentClass + " .ct-section-inner-wrap{padding-bottom:"+newValue.toString()+paddingTopUnit+"}";
                                }
                            }
                            else {
                                newValue = ( paddingBottom - (offset * vy) > 0 ) ? paddingBottom - (offset * vy) : 0;
                                if ( scope.$parent.isEditing('id') ) {    
                                    jQuery(activeComponent).css('padding-bottom',newValue.toString()+paddingBottomUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    var style = "." + scope.$parent.currentClass + "{padding-bottom:"+newValue.toString()+paddingBottomUnit+"}";
                                    scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                                }
                            }
                            scope.$parent.parentScope.showStatusBar("padding-bottom:"+newValue+paddingBottomUnit);
                            if (scope.$parent.parentScope.isActiveName('ct_link_button')) {
                                if ( scope.$parent.isEditing('id') ) {    
                                    jQuery(activeComponent).css('padding-top',newValue.toString()+paddingBottomUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    style += "." + scope.$parent.currentClass + "{padding-top:"+newValue.toString()+paddingBottomUnit+"}";
                                }
                            }
                            scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                            break;

                        case 'padding-right':
                            if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                newValue = ( containerPaddingRight - (offset * vy) > 0 ) ? containerPaddingRight - (offset * vy) : 0;
                                if (scope.$parent.isEditing('id')) {
                                    jQuery('.ct-section-inner-wrap', activeComponent).css('padding-right',newValue.toString()+paddingRightUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    var style = "." + scope.$parent.currentClass + " .ct-section-inner-wrap{padding-right:"+newValue.toString()+paddingTopUnit+"}";
                                }
                            }
                            else {
                                newValue = ( paddingRight - (offset * vx) > 0 ) ? paddingRight - (offset * vx) : 0;
                                if ( scope.$parent.isEditing('id') ) {    
                                    jQuery(activeComponent).css('padding-right',newValue.toString()+paddingRightUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    var style = "." + scope.$parent.currentClass + "{padding-right:"+newValue.toString()+paddingRightUnit+"}";
                                    scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                                }
                            }
                            scope.$parent.parentScope.showStatusBar("padding-right:"+newValue+paddingRightUnit);
                            if (scope.$parent.parentScope.isActiveName('ct_link_button')) {
                                if ( scope.$parent.isEditing('id') ) {    
                                    jQuery(activeComponent).css('padding-left',newValue.toString()+paddingRightUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    style += "." + scope.$parent.currentClass + "{padding-left:"+newValue.toString()+paddingRightUnit+"}";
                                }
                            }
                            scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                            break;

                        case 'padding-left':
                            if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                newValue = ( containerPaddingLeft - (offset * vy) > 0 ) ? containerPaddingLeft - (offset * vy) : 0;
                                if (scope.$parent.isEditing('id')) {
                                    jQuery('.ct-section-inner-wrap', activeComponent).css('padding-left',newValue.toString()+paddingLeftUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    var style = "." + scope.$parent.currentClass + " .ct-section-inner-wrap{padding-left:"+newValue.toString()+paddingTopUnit+"}";
                                }
                            }
                            else {
                                newValue = ( paddingLeft - (offset * vx) > 0 ) ? paddingLeft - (offset * vx) : 0;
                                if ( scope.$parent.isEditing('id') ) {    
                                    jQuery(activeComponent).css('padding-left',newValue.toString()+paddingLeftUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    var style = "." + scope.$parent.currentClass + "{padding-left:"+newValue.toString()+paddingLeftUnit+"}";
                                    scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                                }
                            }
                            scope.$parent.parentScope.showStatusBar("padding-left:"+newValue+paddingLeftUnit);
                            if (scope.$parent.parentScope.isActiveName('ct_link_button')) {
                                if ( scope.$parent.isEditing('id') ) {    
                                    jQuery(activeComponent).css('padding-right',newValue.toString()+paddingLeftUnit);
                                }
                                else if (scope.$parent.isEditing('class')) {
                                    style += "." + scope.$parent.currentClass + "{padding-right:"+newValue.toString()+paddingLeftUnit+"}";
                                }
                            }
                            scope.$parent.outputCSSStyles("oxy-dragging-styles", style);
                            break;
                    }

                    if ( newValue > 0 ) {
                        jQuery("#rb-"+dragDir+"-button").css("display","none");
                        jQuery("#rb-"+dragDir).addClass("rb-currently-editing");
                    }

                    if ( newValue == 0 ) {
                        jQuery("#rb-"+dragDir).removeClass("rb-currently-editing");
                    }

                    scope.$parent.adjustResizeBox();

                    scope.$apply();
                };
                var dragEnd = function(e) {

                    scope.$parent.outputCSSStyles("oxy-dragging-styles", "");

                    scope.$apply();
                    document.removeEventListener('mouseup', dragEnd, false);
                    document.removeEventListener('mousemove', dragging, false);
                    document.removeEventListener('touchend', dragEnd, false);
                    document.removeEventListener('touchmove', dragging, false);

                    // properly handle events when pointer is outside of iframe
                    window.parent.document.removeEventListener('mouseup', dragEnd, false);
                    window.parent.document.removeEventListener('touchend', dragEnd, false);

                    if (newValue!==undefined){

                        switch(dragDir) {
                            case 'margin-top':
                                scope.$parent.setOptionModel('margin-top',newValue.toString());
                                jQuery(activeComponent).css('margin-top',"");
                                break;

                            case 'margin-bottom':
                                scope.$parent.setOptionModel('margin-bottom',newValue.toString());
                                jQuery(activeComponent).css('margin-bottom',"");

                                break;

                            case 'margin-right':
                                scope.$parent.setOptionModel('margin-right',newValue.toString());
                                jQuery(activeComponent).css('margin-right',"");

                                break;

                            case 'margin-left':
                                scope.$parent.setOptionModel('margin-left',newValue.toString());
                                jQuery(activeComponent).css('margin-left',"");

                                break;

                            case 'padding-top':
                                if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                    scope.$parent.setOptionModel('container-padding-top',newValue.toString());
                                    jQuery('.ct-section-inner-wrap', activeComponent).css('padding-top','');
                                }
                                else {
                                    scope.$parent.setOptionModel('padding-top',newValue.toString());
                                    jQuery(activeComponent).css('padding-top',"");
                                }
                                if (scope.$parent.parentScope.isActiveName('ct_link_button')) {
                                    scope.$parent.setOptionModel('padding-bottom',newValue.toString());
                                    jQuery(activeComponent).css('padding-bottom',"");
                                }
                                break;

                            case 'padding-bottom':
                                if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                    scope.$parent.setOptionModel('container-padding-bottom',newValue.toString());
                                    jQuery('.ct-section-inner-wrap', activeComponent).css('padding-bottom','');
                                }
                                else {
                                    scope.$parent.setOptionModel('padding-bottom',newValue.toString());
                                    jQuery(activeComponent).css('padding-bottom',"");
                                }
                                if (scope.$parent.parentScope.isActiveName('ct_link_button')) {
                                    scope.$parent.setOptionModel('padding-top',newValue.toString());
                                    jQuery(activeComponent).css('padding-top',"");
                                }
                                break;

                            case 'padding-right':
                                if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                    scope.$parent.setOptionModel('container-padding-right',newValue.toString());
                                    jQuery('.ct-section-inner-wrap', activeComponent).css('padding-right','');
                                }
                                else {
                                    scope.$parent.setOptionModel('padding-right',newValue.toString());
                                    jQuery(activeComponent).css('padding-right',"");
                                }
                                if (scope.$parent.parentScope.isActiveName('ct_link_button')) {
                                    scope.$parent.setOptionModel('padding-left',newValue.toString());
                                    jQuery(activeComponent).css('padding-left',"");
                                }
                                break;

                            case 'padding-left':
                                if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                    scope.$parent.setOptionModel('container-padding-left',newValue.toString());
                                    jQuery('.ct-section-inner-wrap', activeComponent).css('padding-left','');
                                }
                                else {
                                    scope.$parent.setOptionModel('padding-left',newValue.toString());
                                    jQuery(activeComponent).css('padding-left',"");
                                }
                                if (scope.$parent.parentScope.isActiveName('ct_link_button')) {
                                    scope.$parent.setOptionModel('padding-right',newValue.toString());
                                    jQuery(activeComponent).css('padding-right',"");
                                }
                                break;
                        }
                    }

                    element.removeClass('oxygen-no-transition');
                    jQuery(".rb-currently-editing").removeClass("rb-currently-editing");
                    scope.$parent.parentScope.hideStatusBar();
                    scope.$parent.parentScope.$apply();

                    var timeout = $timeout(function() {
                        jQuery('body').removeClass('oxy-dragging-resize-box');
                        $timeout.cancel(timeout);
                    }, 0, false);
                };
                var dragStart = function(e, direction) {

                    jQuery('body').addClass('oxy-dragging-resize-box');
                    dragDir = direction;
                    dragHandle = jQuery("#rb-"+dragDir);
                    axis = ( dragDir.indexOf('left') >= 0 || dragDir.indexOf('right') >= 0 ) ? 'x' : 'y';
                    start = axis === 'x' ? getClientX(e) : getClientY(e);
                    newValue = undefined;
                    //w = parseInt(style.getPropertyValue('width'));
                    //h = parseInt(style.getPropertyValue('height'));

                    activeComponent = scope.$parent.getActiveComponent();

                    containerPaddingBottom      = parseInt(scope.$parent.getOption('container-padding-bottom'));
                    containerPaddingBottomUnit  = scope.$parent.getOptionUnit('container-padding-bottom');
                    containerPaddingTop         = parseInt(scope.$parent.getOption('container-padding-top'));
                    containerPaddingTopUnit     = scope.$parent.getOptionUnit('container-padding-top');
                    containerPaddingLeft        = parseInt(scope.$parent.getOption('container-padding-left'));
                    containerPaddingLeftUnit    = scope.$parent.getOptionUnit('container-padding-left');
                    containerPaddingRight       = parseInt(scope.$parent.getOption('container-padding-right'));
                    containerPaddingRightUnit   = scope.$parent.getOptionUnit('container-padding-right');

                    paddingTop          = parseInt(scope.$parent.getOption('padding-top')) || 0;
                    paddingTopUnit      = scope.$parent.getOptionUnit('padding-top');
                    paddingRight        = parseInt(scope.$parent.getOption('padding-right')) || 0;
                    paddingRightUnit    = scope.$parent.getOptionUnit('padding-right');
                    paddingBottom       = parseInt(scope.$parent.getOption('padding-bottom')) || 0;
                    paddingBottomUnit   = scope.$parent.getOptionUnit('padding-bottom');
                    paddingLeft         = parseInt(scope.$parent.getOption('padding-left')) || 0;
                    paddingLeftUnit     = scope.$parent.getOptionUnit('padding-left');

                    marginTop           = parseInt(scope.$parent.getOption('margin-top')) || 0;
                    marginTopUnit       = scope.$parent.getOptionUnit('margin-top');
                    marginRight         = parseInt(scope.$parent.getOption('margin-right')) || 0;
                    marginRightUnit     = scope.$parent.getOptionUnit('margin-right');
                    marginBottom        = parseInt(scope.$parent.getOption('margin-bottom')) || 0;
                    marginBottomUnit    = scope.$parent.getOptionUnit('margin-bottom');
                    marginLeft          = parseInt(scope.$parent.getOption('margin-left')) || 0;
                    marginLeftUnit      = scope.$parent.getOptionUnit('margin-left');

                    //prevent transition while dragging
                    element.addClass('oxygen-no-transition');

                    document.addEventListener('mouseup', dragEnd, false);
                    document.addEventListener('mousemove', dragging, false);
                    document.addEventListener('touchend', dragEnd, false);
                    document.addEventListener('touchmove', dragging, false);

                    // properly end dragging when pointer is outside of iframe
                    window.parent.document.addEventListener('mouseup', dragEnd, false);
                    window.parent.document.addEventListener('touchend', dragEnd, false);

                    // Disable highlighting while dragging
                    if(e.stopPropagation) e.stopPropagation();
                    if(e.preventDefault) e.preventDefault();
                    e.cancelBubble = true;
                    e.returnValue = false;

                    scope.$apply();
                };

                dir.forEach(function (direction) {
                    var grabber = document.createElement('div');

                    grabber.setAttribute('class', 'rb');
                    grabber.setAttribute('id', 'rb-' + direction);
                    grabber.innerHTML = inner;
                    element.append(grabber);
                    grabber.ondragstart = function() { return false; };

                    var down = function(e) {
                        var disabled = (scope.rDisabled === 'true');
                        if (!disabled && (e.which === 1 || e.touches)) {
                            // left mouse click or touch screen
                            dragStart(e, direction);
                        }
                    };
                    grabber.addEventListener('mousedown', down, false);
                    grabber.addEventListener('touchstart', down, false);

                    // Overlays
                    if (["top","right","left","bottom"].indexOf(direction) == -1) {

                        // add buttons
                        /*var button = document.createElement('div');

                        button.setAttribute('class', 'rb-button');
                        button.setAttribute('id', 'rb-' + direction + '-button');
                        button.innerHTML = inner;
                        var exactDir = direction.replace("margin-","").replace("padding-","");
                        jQuery("#rb-"+exactDir).append(button);
                        button.ondragstart = function() { return false; };

                        var down = function(e) {
                            var disabled = (scope.rDisabled === 'true');
                            if (!disabled && (e.which === 1 || e.touches)) {
                                // left mouse click or touch screen
                                dragStart(e, direction);
                            }
                        };
                        button.addEventListener('mousedown', down, false);
                        button.addEventListener('touchstart', down, false);*/

                        // fix Chrome hover when mousedown bug
                        jQuery(grabber).mouseenter(function () {
                            jQuery(this).addClass('rb-currently-editing');
                        }).mouseleave(function () {
                            jQuery(this).removeClass('rb-currently-editing');
                        });

                        // unset option on double click
                        jQuery(grabber).dblclick(function (){
                            activeComponent = scope.$parent.getActiveComponent();
                            if (scope.$parent.parentScope.isActiveName('ct_section')) {
                                scope.$parent.setOptionModel('container-'+direction,"");
                                jQuery('.ct-section-inner-wrap', activeComponent).css(direction,'');
                            }
                            else {
                                scope.$parent.setOptionModel(direction,"");
                                jQuery(activeComponent).css(direction,"");
                            }
                            scope.$parent.outputCSSStyles("oxy-dragging-styles", "");
                            scope.$apply();
                        })

                        jQuery(grabber).addClass("rb-overlay");
                    }
                    // Borders
                    else {
                        grabber.addEventListener("mouseover", function(event) {
                            
                            var padding;

                            if ( scope.$parent.parentScope.isActiveName("ct_section") ) {
                                jQuery("#rb-margin-"+direction+"-button").css("display", "none");
                                padding = parseInt(scope.$parent.getOption('container-padding-'+direction));
                            }
                            else {
                                // check if direction has margin or padding = 0
                                var margin  = parseInt(scope.$parent.getOption('margin-'+direction));
                                    padding = parseInt(scope.$parent.getOption('padding-'+direction));
                                if (margin > 0) {
                                    jQuery("#rb-margin-"+direction+"-button").css("display", "none");
                                }
                                else {
                                    jQuery("#rb-margin-"+direction+"-button").css("display", "");
                                }
                            }

                            if (padding > 0) {
                                jQuery("#rb-padding-"+direction+"-button").css("display", "none");
                            }
                            else {
                                jQuery("#rb-padding-"+direction+"-button").css("display", "");
                            }

                        }, false);
                        grabber.addEventListener("mouseout", function(event) {
                            jQuery("#rb-margin-"+direction+"-button").css("display", "");
                            jQuery("#rb-padding-"+direction+"-button").css("display", "");
                        }, false)
                        jQuery(grabber).addClass("rb-border");
                    }
                });

                scope.$parent.rbMarginTop = jQuery("#rb-margin-top");
                scope.$parent.rbMarginLeft = jQuery("#rb-margin-left");
                scope.$parent.rbMarginRight = jQuery("#rb-margin-right");
                scope.$parent.rbMarginBottom = jQuery("#rb-margin-bottom");

                scope.$parent.rbPaddingTop = jQuery("#rb-padding-top");
                scope.$parent.rbPaddingLeft = jQuery("#rb-padding-left");
                scope.$parent.rbPaddingRight = jQuery("#rb-padding-right");
                scope.$parent.rbPaddingBottom = jQuery("#rb-padding-bottom");

                scope.$parent.rbTop = jQuery("#rb-top");
                scope.$parent.rbLeft = jQuery("#rb-left");
                scope.$parent.rbRight = jQuery("#rb-right");
                scope.$parent.rbBottom = jQuery("#rb-bottom");

                scope.$parent.rbTitleBar = jQuery("#oxygen-resize-box-titlebar");
                scope.$parent.rbParentTitleBar = jQuery("#oxygen-resize-box-parent-titlebar");
            }
        };
    });


/**
 * Testing drag to resize flex children
 * 
 */
CTFrontendBuilder.directive('oxygenResizeFlex', function($timeout,$interval) {

        return {
            restrict: 'AE',
            scope: {
                rOptions: '=',
                rCenteredX: '=',
                rCenteredY: '=',
                rWidth: '=',
                rHeight: '=',
                rFlex: '=',
                rId: '=',
                rGrabber: '@',
                rDisabled: '@',
            },
            link: function(scope, element, attr) {

                var flexBasis = 'flexBasis' in document.documentElement.style ? 'flexBasis' :
                    'webkitFlexBasis' in document.documentElement.style ? 'webkitFlexBasis' :
                    'msFlexPreferredSize' in document.documentElement.style ? 'msFlexPreferredSize' : 'flexBasis';

                var timeout = $timeout(function() {
                    var position = element.css('position')

                    if ( position == "" || position == "static" ){
                        element.addClass('oxygen-resizable-relative');
                    }
                    
                    $interval.cancel(timeout);
                }, 0, false);

                var style = window.getComputedStyle(element[0], null),
                    w,
                    h,
                    dir = scope.rOptions || ['right'],
                    vx = scope.rCenteredX ? 2 : 1, // if centered double velocity
                    vy = scope.rCenteredY ? 2 : 1, // if centered double velocity
                    inner = scope.rGrabber ? scope.rGrabber : '<span></span>',
                    start,
                    dragDir,
                    axis,
                    info = {};

                var getClientX = function(e) {
                    return e.touches ? e.touches[0].clientX : e.clientX;
                };

                var getClientY = function(e) {
                    return e.touches ? e.touches[0].clientY : e.clientY;
                };

                var dragging = function(e) {
                    var offset = axis === 'x' ? start - getClientX(e) : start - getClientY(e);

                    switch(dragDir) {

                        case 'right':
                            var newWidth = ( width - (offset * vx) > 0 ) ? width - (offset * vx) : 0;
                            scope.$parent.setOptionModel('flex-basis',newWidth+'px',scope.rId);
                            console.log(width, newWidth);
                            break;

                    }
                };
                var dragEnd = function(e) {
                    scope.$apply();
                    document.removeEventListener('mouseup', dragEnd, false);
                    document.removeEventListener('mousemove', dragging, false);
                    document.removeEventListener('touchend', dragEnd, false);
                    document.removeEventListener('touchmove', dragging, false);
                    element.removeClass('oxygen-no-transition');
                };
                var dragStart = function(e, direction) {
                    dragDir = direction;
                    axis = ( dragDir.indexOf('left') >= 0 || dragDir.indexOf('right') >= 0 ) ? 'x' : 'y';
                    start = axis === 'x' ? getClientX(e) : getClientY(e);
                    //w = parseInt(style.getPropertyValue('width'));
                    //h = parseInt(style.getPropertyValue('height'));

                    width = parseInt(scope.$parent.getOption('width')) || parseInt(style.getPropertyValue('width'));

                    //prevent transition while dragging
                    element.addClass('oxygen-no-transition');

                    document.addEventListener('mouseup', dragEnd, false);
                    document.addEventListener('mousemove', dragging, false);
                    document.addEventListener('touchend', dragEnd, false);
                    document.addEventListener('touchmove', dragging, false);

                    // Disable highlighting while dragging
                    if(e.stopPropagation) e.stopPropagation();
                    if(e.preventDefault) e.preventDefault();
                    e.cancelBubble = true;
                    e.returnValue = false;

                    scope.$apply();
                };

                dir.forEach(function (direction) {
                    var grabber = document.createElement('div');

                    grabber.setAttribute('class', 'rg rg-' + direction);
                    grabber.innerHTML = inner;
                    element.append(grabber);
                    grabber.ondragstart = function() { return false; };

                    var down = function(e) {
                        var disabled = (scope.rDisabled === 'true');
                        if (!disabled && (e.which === 1 || e.touches)) {
                            // left mouse click or touch screen
                            dragStart(e, direction);
                        }
                    };
                    grabber.addEventListener('mousedown', down, false);
                    grabber.addEventListener('touchstart', down, false);
                });
            }
        };
    });

CTFrontendBuilder.directive('oxyimageonload', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            element.bind('load', function() {
                scope.adjustResizeBox();
            });
        }
    };
})

/**
 * Taken from http://www.myersdaily.org/joseph/javascript/md5-text.html
 *
 */

function oxygenMD5(str) {

    function md5cycle(x, k) {
        var a = x[0],
            b = x[1],
            c = x[2],
            d = x[3];

        a = ff(a, b, c, d, k[0], 7, -680876936);
        d = ff(d, a, b, c, k[1], 12, -389564586);
        c = ff(c, d, a, b, k[2], 17, 606105819);
        b = ff(b, c, d, a, k[3], 22, -1044525330);
        a = ff(a, b, c, d, k[4], 7, -176418897);
        d = ff(d, a, b, c, k[5], 12, 1200080426);
        c = ff(c, d, a, b, k[6], 17, -1473231341);
        b = ff(b, c, d, a, k[7], 22, -45705983);
        a = ff(a, b, c, d, k[8], 7, 1770035416);
        d = ff(d, a, b, c, k[9], 12, -1958414417);
        c = ff(c, d, a, b, k[10], 17, -42063);
        b = ff(b, c, d, a, k[11], 22, -1990404162);
        a = ff(a, b, c, d, k[12], 7, 1804603682);
        d = ff(d, a, b, c, k[13], 12, -40341101);
        c = ff(c, d, a, b, k[14], 17, -1502002290);
        b = ff(b, c, d, a, k[15], 22, 1236535329);

        a = gg(a, b, c, d, k[1], 5, -165796510);
        d = gg(d, a, b, c, k[6], 9, -1069501632);
        c = gg(c, d, a, b, k[11], 14, 643717713);
        b = gg(b, c, d, a, k[0], 20, -373897302);
        a = gg(a, b, c, d, k[5], 5, -701558691);
        d = gg(d, a, b, c, k[10], 9, 38016083);
        c = gg(c, d, a, b, k[15], 14, -660478335);
        b = gg(b, c, d, a, k[4], 20, -405537848);
        a = gg(a, b, c, d, k[9], 5, 568446438);
        d = gg(d, a, b, c, k[14], 9, -1019803690);
        c = gg(c, d, a, b, k[3], 14, -187363961);
        b = gg(b, c, d, a, k[8], 20, 1163531501);
        a = gg(a, b, c, d, k[13], 5, -1444681467);
        d = gg(d, a, b, c, k[2], 9, -51403784);
        c = gg(c, d, a, b, k[7], 14, 1735328473);
        b = gg(b, c, d, a, k[12], 20, -1926607734);

        a = hh(a, b, c, d, k[5], 4, -378558);
        d = hh(d, a, b, c, k[8], 11, -2022574463);
        c = hh(c, d, a, b, k[11], 16, 1839030562);
        b = hh(b, c, d, a, k[14], 23, -35309556);
        a = hh(a, b, c, d, k[1], 4, -1530992060);
        d = hh(d, a, b, c, k[4], 11, 1272893353);
        c = hh(c, d, a, b, k[7], 16, -155497632);
        b = hh(b, c, d, a, k[10], 23, -1094730640);
        a = hh(a, b, c, d, k[13], 4, 681279174);
        d = hh(d, a, b, c, k[0], 11, -358537222);
        c = hh(c, d, a, b, k[3], 16, -722521979);
        b = hh(b, c, d, a, k[6], 23, 76029189);
        a = hh(a, b, c, d, k[9], 4, -640364487);
        d = hh(d, a, b, c, k[12], 11, -421815835);
        c = hh(c, d, a, b, k[15], 16, 530742520);
        b = hh(b, c, d, a, k[2], 23, -995338651);

        a = ii(a, b, c, d, k[0], 6, -198630844);
        d = ii(d, a, b, c, k[7], 10, 1126891415);
        c = ii(c, d, a, b, k[14], 15, -1416354905);
        b = ii(b, c, d, a, k[5], 21, -57434055);
        a = ii(a, b, c, d, k[12], 6, 1700485571);
        d = ii(d, a, b, c, k[3], 10, -1894986606);
        c = ii(c, d, a, b, k[10], 15, -1051523);
        b = ii(b, c, d, a, k[1], 21, -2054922799);
        a = ii(a, b, c, d, k[8], 6, 1873313359);
        d = ii(d, a, b, c, k[15], 10, -30611744);
        c = ii(c, d, a, b, k[6], 15, -1560198380);
        b = ii(b, c, d, a, k[13], 21, 1309151649);
        a = ii(a, b, c, d, k[4], 6, -145523070);
        d = ii(d, a, b, c, k[11], 10, -1120210379);
        c = ii(c, d, a, b, k[2], 15, 718787259);
        b = ii(b, c, d, a, k[9], 21, -343485551);

        x[0] = add32(a, x[0]);
        x[1] = add32(b, x[1]);
        x[2] = add32(c, x[2]);
        x[3] = add32(d, x[3]);

    }

    function cmn(q, a, b, x, s, t) {
        a = add32(add32(a, q), add32(x, t));
        return add32((a << s) | (a >>> (32 - s)), b);
    }

    function ff(a, b, c, d, x, s, t) {
        return cmn((b & c) | ((~b) & d), a, b, x, s, t);
    }

    function gg(a, b, c, d, x, s, t) {
        return cmn((b & d) | (c & (~d)), a, b, x, s, t);
    }

    function hh(a, b, c, d, x, s, t) {
        return cmn(b ^ c ^ d, a, b, x, s, t);
    }

    function ii(a, b, c, d, x, s, t) {
        return cmn(c ^ (b | (~d)), a, b, x, s, t);
    }

    function md51(s) {
        txt = '';
        var n = s.length,
            state = [1732584193, -271733879, -1732584194, 271733878],
            i;
        for (i = 64; i <= s.length; i += 64) {
            md5cycle(state, md5blk(s.substring(i - 64, i)));
        }
        s = s.substring(i - 64);
        var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        for (i = 0; i < s.length; i++)
            tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
        tail[i >> 2] |= 0x80 << ((i % 4) << 3);
        if (i > 55) {
            md5cycle(state, tail);
            for (i = 0; i < 16; i++) tail[i] = 0;
        }
        tail[14] = n * 8;
        md5cycle(state, tail);
        return state;
    }

    /* there needs to be support for Unicode here,
     * unless we pretend that we can redefine the MD-5
     * algorithm for multi-byte characters (perhaps
     * by adding every four 16-bit characters and
     * shortening the sum to 32 bits). Otherwise
     * I suggest performing MD-5 as if every character
     * was two bytes--e.g., 0040 0025 = @%--but then
     * how will an ordinary MD-5 sum be matched?
     * There is no way to standardize text to something
     * like UTF-8 before transformation; speed cost is
     * utterly prohibitive. The JavaScript standard
     * itself needs to look at this: it should start
     * providing access to strings as preformed UTF-8
     * 8-bit unsigned value arrays.
     */
    function md5blk(s) { /* I figured global was faster.   */
        var md5blks = [],
            i; /* Andy King said do it this way. */
        for (i = 0; i < 64; i += 4) {
            md5blks[i >> 2] = s.charCodeAt(i) +
                (s.charCodeAt(i + 1) << 8) +
                (s.charCodeAt(i + 2) << 16) +
                (s.charCodeAt(i + 3) << 24);
        }
        return md5blks;
    }

    var hex_chr = '0123456789abcdef'.split('');

    function rhex(n) {
        var s = '',
            j = 0;
        for (; j < 4; j++)
            s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] +
            hex_chr[(n >> (j * 8)) & 0x0F];
        return s;
    }

    function hex(x) {
        for (var i = 0; i < x.length; i++)
            x[i] = rhex(x[i]);
        return x.join('');
    }

    function md5(s) {
        return hex(md51(s));
    }

    /* this function is much faster,
    so if possible we use it. Some IEs
    are the only ones I know of that
    need the idiotic second function,
    generated by an if clause.  */

    function add32(a, b) {
        return (a + b) & 0xFFFFFFFF;
    }

    if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') {
        function add32(x, y) {
            var lsw = (x & 0xFFFF) + (y & 0xFFFF),
                msw = (x >> 16) + (y >> 16) + (lsw >> 16);
            return (msw << 16) | (lsw & 0xFFFF);
        }
    }

    return md5(str);
}