coreFramework.directive('cfForm', function (Restangular) {
    return {
        restrict: 'EA',
        replace: true,
        transclude: true,
        scope: {
            data: '=ngModel',
            errors: '=errors',
            submitCallback: '&'
        },
        template: function (element, attrs) {
            // qqq = element;
            // console.log($(element));
            // console.log($(element).parents('.cf-form'));
            if (element[0].tagName.toUpperCase() == 'FORM') {
                return '<form ng-form="form" class="cf-form" ng-class="{ \'cf-form-submitted\': submitted }"><div name="form" ng-form="form"></div></form>';
            } else {
                return '<div ng-form="form" class="cf-form" ng-class="{ \'cf-form-submitted\': submitted }"><div name="form" ng-form="form"></div></div>';
            }
        },
        link: function(scope, element, attrs, ctrl) {
            ctrl.transclude(scope, function(clone) {
                var el = element.children();
                el.empty();
                el.append(clone);
            });

            scope.validate2 = function () {
                scope.submitted = true;

                if (scope.$parent[attrs.name].$valid) {
                    scope.submitCallback(scope.data).then(function(e) {
                        if (e && e.config && e.config.url) {
                            //Restangular.oneUrl('reload', e.config.url).get().then(function (e) {
                            
                            //});
                            //skope.$apply(function(){skope.articleForm.$setPristine();});
                        }
                    });
                } else {
                    console.log('- form client-side invalid, no submit');
                }
            };

        },
        controller: ['$scope', '$attrs', '$transclude', '$window', '$q', 'Message', function ($scope, $attrs, $transclude, $window, $q, Message) {

            $scope.submit = function () {

                if ($scope.customCallback) {
                    return $scope.customCallback($scope.data);
                } else {
                    return $scope.submitCallback($scope.data);
                }

            };

            $scope.errorCallback;

            $scope.validate = function (params) {
                $scope.submitted = true;
                $scope.customCallback = null;

                if ($scope.$parent[$attrs.name].$valid) {
                    if (angular.isDefined(params)) {
                        if (params.hasOwnProperty('submit')) {
                            $scope.customCallback = $scope.$parent.$eval(params.submit);
                        }
                    }
                    $scope.submit();
                } else {
                    console.log('- form client-side invalid, no submit');
                    if (angular.isDefined(params)) {
                        if (params.hasOwnProperty('errorCallback')) {
                            if (angular.isFunction(params.errorCallback)) {
                                $scope.errorCallback = params.errorCallback;
                            } else {
                                var errorCallback = $scope.$parent.$eval(params.errorCallback);
                                if (angular.isFunction(errorCallback)) {
                                    $scope.errorCallback = errorCallback;
                                }
                            }
                        }
                    }
                    if ($scope.errorCallback) {
                        $scope.errorCallback();
                    }
                }
            };

            $scope.preview = function (path) {
                var url = path.split('/').map(function (value) {
                    return $scope.$eval(value);
                }).join('/').replace(/\/+/g, '/');
                $window.open(url);
            };

            this.transclude = $transclude;

            $scope.$watch('form', function (newVal, oldVal) {
                setValidity($scope.form, $scope.errors);
                $scope.$parent[$attrs.name] = $scope.form;
            }, true);

            $scope.$watch('errors', function (newVal, oldVal) {
                if (newVal != oldVal) {
                    setValidity($scope.form, $scope.errors);
                    if ($scope.errorCallback) {
                        $scope.errorCallback();
                    }
                }
            }, false);

            function setValidity(content, errors) {
                if (angular.isUndefined(content)) {
                    return;
                }

                angular.forEach(errors, function(value, key) {
                    var elName = !isNaN(parseInt(key, 10)) ? 'item_' + key : key;

                    if (angular.isObject(value)) {
                        setValidity(content[elName], errors[key]);
                    } else {
                        if (content.hasOwnProperty(elName)) {
                            content[elName].$setValidity('server', false);
                            content[elName].$error.server = value;
                        }
                    }
                });
            }
        }]
    };
});



coreFramework.directive('cfFormGroup', function () {
    return {
        restrict: 'EA',
        replace: true,
        transclude: true,
        scope: {
            name: '@',
            data: '=ngModel'
        },
        template: '<div class="cf-form-group" ng-form="form"></div>',
        controller: ['$scope', '$transclude', function ($scope, $transclude) {
            this.transclude = $transclude;
        }],
        link: function (scope, element, attrs, ctrl) {
            ctrl.transclude(scope, function(clone) {
                element.empty();
                element.append(clone);
            });
        }
    };
});



coreFramework.directive('cfFormObjectCollection', function () {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        require: 'form',
        scope: {
            list: '=ngModel',
            contentType: '@',
            min: '@',
            max: '@'
        },
        template:   '<div class="cf-form-object-collection" ng-form="form" ui-sortable="sortableOptions">' +
                        '<a href="" ng-click="register()" ng-hide="max && (list.length >= max)" class="Button">Add object</a>' +
                        '<div class="js-sortable-items"><cf-form-object-collection-item ng-repeat="item in list track by $index" ng-model="item" content-type="{{ contentType }}" data-index="{{ $index }}" sortable="true" class="js-sortable-item"></cf-form-object-collection-item></div>' +
                    '</div>',
        controller: ['$scope', 'AssignmentMediator', function ($scope, AssignmentMediator) {
            $scope.list = $scope.list || [];
            $scope.sortableOptions = {
                axis: 'y',
                items: '> .js-sortable-items > .js-sortable-item',
                handle: '.js-sortable-handle'
            };

            $scope.register = function () {
                AssignmentMediator.register($scope.addItem, $scope.contentType);
            };
        }],
        link: function (scope, element, attrs, ctrl) {
            scope.$watch('list', function (newVal, oldVal) {
                if (typeof scope.min !== 'undefined' && (scope.list.length < scope.min)) {
                    ctrl.$setValidity('min', false);
                } else {
                    ctrl.$setValidity('min', true);
                }

                if (typeof scope.max !== 'undefined' && (scope.list.length > scope.max)) {
                    ctrl.$setValidity('max', false);
                } else {
                    ctrl.$setValidity('max', true);
                }
            }, true);

            scope.addItem = function (obj) {
                scope.list = scope.list || [];
                scope.list.push(angular.copy(obj));
                ctrl.$setDirty();
            };

            scope.removeItem = function (index) {
                scope.list.splice(index, 1);
                ctrl.$setDirty();
            };
        }
    };
});



coreFramework.directive('cfFormObjectCollectionItem', function ($compile) {
    return {
        restrict: 'EA',
        scope: {
            item: '=ngModel',
            sortable: '@'
        },
        template: function (element, attrs) {
            return '<div class="cf-form-object-collection-item" ng-form="form">' +
                '<pre>{{ item|json }}</pre>' +
                '<div class="cf-form-object-collection-item-tools">' +
                    (attrs.sortable ? '<a href class="js-sortable-handle"><i class="fa fa-arrows-v"></i></a>' : '') +
                    '<a ng-click="removeItem()"><i class="fa fa-trash-o"></i></a>' +
                '</div>' +
            '</div>';
        },
        controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
            $scope.removeItem = function () {
                // console.log('removing item from', $scope.$parent);
                $scope.$parent.removeItem($element.data('index'), 1);
            };
        }]
    };
});

coreFramework.directive('cfFormCollection', function ($compile) {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        require: 'form',
        scope: {
            list: '=ngModel',
            errors: '=',
            itemModel: '@',
            min: '@',
            max: '@',
            name: '@',
            buttonLabel: '@'
        },
        template: '<div class="cf-form-collection" ng-form="form">' +
                    '<div ng-model="list" ui-sortable="">' +
                    // '<div>' +
                        '<div cf-form-collection-item class="cf-form-collection-item" ng-repeat="data in list" element-name="item_{{$index}}" ng-form="form">' +
                            // '{{ $id }}' +
                            // '<div ng-transclude></div>' +
                            '<div class="cf-form-collection-item-transclude"></div>' +
                            '<div class="cf-form-collection-actions">' +
                                '<span class="cf-form-collection-actionsLink"><i class="fa fa-ellipsis-v"></i></span>' +
                                '<div class="cf-form-collection-actionsDropdown">' +
                                    '<a href ng-click="removeItem($index)" ng-hide="min && (list.length <= min)">Delete</a>' +
                                    // '<a href ng-click="moveUp($index)" ng-hide="$index == 0">Move up</a>' +
                                    // '<a href ng-click="moveDown($index)" ng-hide="$index == list.length - 1">Move down</a>' +
                                '</div>' +
                            '</div>' +
                        '</div>' +
                    '</div>' +
                    '<div class="cf-form-collection-wrapButton"><a href ng-click="addItem()" ng-hide="max && (list.length >= max)" class="Button Button--medium">Add {{buttonLabel || "item"}}</a></div>' +
                  '</div>',
        templateQ: '<div class="cf-form-collection" ng-form="form">' +
                  '</div>',
        controller: ['$scope', '$element', '$transclude', function ($scope, $element, $transclude) {

        }],
        link: function (scope, element, attrs, ctrl, transclude) {

            scope.addItem = function () {
                // console.log('addItem');
                var itemObject = scope.$eval(scope.itemModel);
                scope.list = scope.list || [];
                scope.list.push(itemObject);
                ctrl.$setDirty();
            };

            scope.removeItem = function (index) {
                scope.list.splice(index, 1);
                ctrl.$setDirty();
            };

            scope.moveUp = function (index) {
                var item = scope.list.splice(index, 1)[0];
                scope.list.splice(index - 1, 0, item);
                ctrl.$setDirty();
            };

            scope.moveDown = function (index) {
                var item = scope.list.splice(index, 1)[0];
                scope.list.splice(index + 1, 0, item);
                ctrl.$setDirty();
            };

            if (angular.isDefined(scope.min)) {
                if (angular.isUndefined(scope.list) || scope.list.length < scope.min) {
                    if (angular.isUndefined(scope.list)) {
                        scope.list = [];
                    }
                    while (scope.list.length < scope.min) {
                        scope.addItem();
                    }
                }
            }

            scope.$watchCollection('list', function (newVal, oldVal) {
                if (typeof scope.min !== 'undefined' && (angular.isUndefined(newVal) || scope.list.length < scope.min)) {
                    ctrl.$setValidity('min', false);
                } else {
                    ctrl.$setValidity('min', true);
                }

                if (typeof scope.max !== 'undefined' && (angular.isDefined(newVal) && scope.list.length > scope.max)) {
                    ctrl.$setValidity('max', false);
                } else {
                    ctrl.$setValidity('max', true);
                }
                $(element).find('.cf-form-collection-item-transclude').each(function () {
                    var el = angular.element(this);
                    if (el.html() == '') {
                        transclude(el.scope(), function (clone) {
                            // el.empty();
                            el.append(clone);
                        });
                    }
                });
            });
        }
    };
});



coreFramework.directive('cfFormCollectionItem', function ($compile) {
    return {
        restrict: 'EA',
        scope: {
            elementName: '@'
        },
        controller: ['$scope', '$attrs', '$element', '$transclude', function($scope, $attrs, $element, $transclude) {
            $scope.$watch('elementName', function(newValue, oldValue) {
                $attrs.$set('name', $scope.elementName);
                if (newValue != oldValue) {
                    //$compile($element.contents())($scope);
                }
            });

            $attrs.$set('name', $scope.elementName);
        }]
    };
});



coreFramework.directive('cfFormElement', function ($timeout) {
    return {
        restrict: 'A',
        require: 'ngModel',
        linkQ: function (scope, element, attrs, ctrl) {
            // attrs.id && attrs.id.indexOf('config_'))
            attrs.$set('id', attrs.id + scope.$id);
            scope.$watch(function() { return ctrl.$modelValue; }, function() {
                ctrl.$setValidity('server', true);
            });
        },
        compile: function (tElement, tAttrs) {
            var preLink = function preLink(scope, element, attrs, ctrl) {
                // attrs.$set('id', attrs.id + scope.$id);
                //attrs.$set('ngTrim', 'false');
            };
            var postLink = function postLink(scope, element, attrs, ctrl) {

                function validate(ctrl, validatorName, validity, value){
                    ctrl.$setValidity(validatorName, validity);
                    return validity ? value : undefined;
                }
                var elementFocused = false;
                var cfEmailValidator = function(value) {
                    if (elementFocused) {
                        return validate(ctrl, 'email', elementFocused, value);
                    } else {
                        return value;
                    }
                };

                if (attrs.type == 'email') {
                    ctrl.$formatters.push(cfEmailValidator);
                    ctrl.$parsers.push(cfEmailValidator);
                    element.bind('focus', function () {
                        $timeout(function () {
                            elementFocused = true;
                        });
                    });
                    element.bind('blur', function () {
                        $timeout(function () {
                            elementFocused = false;
                            if (!ctrl.$isEmpty(ctrl.$viewValue)) {
                                ctrl.$setViewValue(ctrl.$viewValue);
                                ctrl.$render();
                            }
                        });
                    });
                }

                var label = $(element).parents('.FloatLabel');

                scope.$watch(function() { return ctrl.$modelValue; }, function(value) {
                    
                    ctrl.$setValidity('server', true);
                    
                    if (angular.isDefined(value) && value != '' && value != null) {
                        label.addClass('is-labelVisible');
                    } else {
                        label.removeClass('is-labelVisible');
                    }

                });

                element.bind('focus', function(e) {
                    label.addClass('is-focused');
                });
                element.bind('blur', function(e) {
                    label.removeClass('is-focused');
                });

                // var previousValue = element.val() || '';
                // var valueChangeCheckTimeout;
                // var isMultiLanguage = element.parents('[cf-shared-model]').length > 0;
                // var valueChangeCheck = function () {
                //     return $timeout(function () {
                //         var currentValue = element.val() || '';
                //         if (isMultiLanguage) {
                //             if(previousValue == '' && currentValue != '') {
                //                 previousValue = currentValue;
                //                 element.trigger('input');
                //                 element.trigger('change');
                //             } else {
                //                 valueChangeCheckTimeout = valueChangeCheck();
                //             }
                //         } else {
                //             if((previousValue != '' || currentValue != '') && previousValue != currentValue) {
                //                 previousValue = currentValue;
                //                 element.trigger('input');
                //                 element.trigger('change');
                //             }
                //             valueChangeCheckTimeout = valueChangeCheck();
                //         }
                //     }, 1000);
                // };
                // valueChangeCheckTimeout = valueChangeCheck();

                // scope.$on('$destroy', function () {
                //     $timeout.cancel(valueChangeCheckTimeout);
                // });

            };
            return {
                pre: preLink,
                post: postLink
            };
        }
    };
});



coreFramework.directive('cfFormEntity', function ($rootScope, $compile, AssignmentService, AssignmentMediator, ItemTypes) {
    return {
        restrict: 'EA',
        replace: true,
        // transclude: true,
        require: ['ngModel', '^form'],
        scope: {
            data: '=ngModel',
            type: '@contentType',
            apiEndpoint: '@',
            apiParams: '@',
            buttonLabel: '@',
            imageVariation: '@'
        },
        template: function (element, attrs) {
            var itemType = ItemTypes.get(attrs.contentType);
            var itemElement = itemType.directive;
            var template;

            var showEditButton = true;
            if (itemType.buttons) {
                showEditButton = itemType.buttons.edit === true;
            }
            if (!$rootScope.userManager.userHasPrivilege(attrs.apiEndpoint + ':edit')) {
                showEditButton = false;
            }

            if (itemType.params && itemType.params.uploader && $rootScope.userManager.userHasPrivilege(attrs.apiEndpoint + ':create')) {
                template = '<div class="cf-form-entity" ng-class="{ loading: isLoading, \'not-empty\': item, \'file-over\': dropClass }" flow-drag-enter="dropClass=true" flow-drag-leave="dropClass=false">' +
                            '<div class="cf-form-entity-drop" flow-init="flowOptions" flow-drop flow-file-success="flow(\'fileSuccess\', $flow, $file)" flow-file-error="flow(\'fileError\', $flow, $file, $message)" flow-file-progress="flow(\'fileProgress\', $flow, $file)" flow-files-submitted="flow(\'filesSubmitted\', $flow, $files)">' +
                                '<div ng-show="item">' +
                                    '<div class="cf-form-entity-item">' +
                                        '<' + itemElement + ' class="" flow-img="$flow.files[0]"></' + itemElement + '>' +
                                        '<div class="cf-form-entity-item-progress" ng-class="{ complete: $flow.files[0] ? $flow.files[0].progress() == 1 : false }" ng-if="$flow.files[0] && $flow.files[0].progress()" style="width: {{ 100 * ((1 - $flow.files[0].progress()) || 1) }}%;"></div>' +
                                        '<div class="cf-form-entity-item-overlay">' +
                                            '<div class="cf-form-entity-item-overlay-title">{{ item.title }}</div>' +
                                            '<a ng-click="remove()" class="cf-form-entity-item-overlay-button">Remove</a>' +
                                            '<a ng-if="image_url" ng-click="preview()" class="cf-form-entity-item-overlay-button">Preview</a>' +
                                            '<span flow-btn flow-single-file class="cf-form-entity-item-overlay-button">Replace</span>' +
                                            (showEditButton ? '<a href="/admin/{{apiEndpoint}}/edit/{{item.id}}/{{ imageVariation ? \'?variation=\' + imageVariation : \'\' }}" class="cf-form-entity-item-overlay-button">Edit</a>' : '') +
                                        '</div>' +
                                    '</div>' +
                                '</div>' +
                                '<div class="cf-form-entity-item" ng-show="!item">' +
                                    '<span flow-btn flow-single-file class="Button">Add {{buttonLabel || "entity"}}</span>' +
                                    '<span class="description">Drag from computer or <a ng-click="register()">panel on the right</a>!</span>' +
                                '</div>' +
                            '</div>' +
                        '</div>';
            } else {
                template = '<div class="cf-form-entity" ng-class="{ loading: isLoading, \'not-empty\': item }">' +
                            '<div class="cf-form-entity-drop">' +
                                '<div ng-show="item">' +
                                    '<div class="cf-form-entity-item">' +
                                        '<' + itemElement + ' class=""></' + itemElement + '>' +
                                        '<div class="cf-form-entity-item-overlay">' +
                                            '<div class="cf-form-entity-item-overlay-title">{{ item.title }}</div>' +
                                            '<a ng-click="remove()" class="cf-form-entity-item-overlay-button">Remove</a>' +
                                            '<a ng-if="image_url" ng-click="preview()" class="cf-form-entity-item-overlay-button">Preview</a>' +
                                            (showEditButton ? '<a href="/admin/{{apiEndpoint}}/edit/{{item.id}}/{{ imageVariation ? \'?variation=\' + imageVariation : \'\' }}" class="cf-form-entity-item-overlay-button">Edit</a>' : '') +
                                        '</div>' +
                                    '</div>' +
                                '</div>' +
                                '<div class="cf-form-entity-item" ng-show="!item">' +
                                    '<span ng-click="register()" class="Button">Add {{buttonLabel || "entity"}}</span>' +
                                    '<span class="description">from the <a ng-click="register()">panel on the right</a>!</span>' +
                                '</div>' +
                            '</div>' +
                        '</div>';
            }
            return template;
        },
        controller: ['$scope', '$parse', 'Restangular', 'Auth', 'Message', '$attrs', 'ngDialog', function ($scope, $parse, Restangular, Auth, Message, $attrs, ngDialog) {
            $scope.flowOptions = {
                target: '/api/' + $scope.apiEndpoint + '/',
                headers: {
                    'Authorization': 'Bearer ' + Auth.getToken()
                },
                fileParameterName: $scope.type + 's',
                withCredentials: true,
                testChunks: false,
                singleFile: true,
                progressCallbacksInterval: 10,
                chunkSize: 24 * 1024 * 1024,
                permanentErrors: [400, 401, 403, 404, 405, 409, 412, 415, 422, 500, 503, 504]
            };
            
            $scope.flow = function (eventType, flow, flowObj, message) {
                console.log('flow event: ', eventType, flowObj);
                switch (eventType) {
                    case 'fileSuccess':
                        console.log(flowObj.chunks[0].itemLocation);
                        var apiParams = angular.extend({}, $scope.$eval($scope.apiParams));
                        Restangular.oneUrl('item', flowObj.chunks[0].itemLocation).get(apiParams).then(function (response) {
                            $scope.add(response.data.originalElement);
                            flow.cancel();
                        });
                        break;
                    case 'filesSubmitted':
                        console.log(eventType, flowObj);
                        $scope.item = {};
                        flow.resume();
                        break;
                    case 'fileError':
                        console.log(eventType, flowObj, message);
                        var errorMessage = {};
                        try {
                            errorMessage = angular.fromJson(message);
                        } catch (e) {}
                        
                        if (errorMessage.length) {
                            errorMessage = $parse('message')(errorMessage[0]);
                        } else {
                            errorMessage = $parse('message')(errorMessage);
                        }
                        
                        Message.send(errorMessage || 'Error!', 'error', 0);
                        $scope.item = null;
                        flow.cancel();
                        flowObj.abort(true);
                        break;
                }
            };

            $scope.preview = function () {
                ngDialog.open({
                    template: '<img ng-src="{{ image_url }}" />',
                    plain: true,
                    scope: $scope
                });
            };
            
            var isRepresentationLoaded = false;
            $scope.item = null;
            $scope.defaultVariation = 'admin_small';

            function getRepresentation(endpoint, data) {
                if (data && data.id) {
                    $scope.item = data;
                    AssignmentService.add($scope.type, $attrs.name, $scope.item);
                    return;
                } else if ($scope.item && $scope.item.id == data) {
                    if ( angular.isDefined( $scope.item.variations ) ) {
                        if (angular.isDefined($scope.item.variations[$scope.imageVariation])) {
                            $scope.image_url = $scope.item.variations[$scope.imageVariation].url;
                        } else if (angular.isDefined($scope.item.variations[$scope.defaultVariation])) {
                            $scope.image_url = $scope.item.variations[$scope.defaultVariation].url;
                        }
                    }
                    return;
                }

                var apiParams = angular.extend({}, $scope.$eval($scope.apiParams));
                $scope.isLoading = true;

                Restangular.one(endpoint, data).get(apiParams).then(function (response) {
                    $scope.item = response.data.originalElement;
                    $scope.isLoading = false;
                    isRepresentationLoaded = true;
                    AssignmentService.add($scope.type, $attrs.name, $scope.item);
                    if ( angular.isDefined( $scope.item.variations ) ) {
                        if (angular.isDefined($scope.item.variations[$scope.imageVariation])) {
                            $scope.image_url = $scope.item.variations[$scope.imageVariation].url;
                        } else if (angular.isDefined($scope.item.variations[$scope.defaultVariation])) {
                            $scope.image_url = $scope.item.variations[$scope.defaultVariation].url;
                        }
                    }
                }, function (error) {
                    console.error('cf-form-entity error', error);
                    $scope.isLoading = false;
                });
            }
/*
            if ($scope.data) {
                getRepresentation($scope.apiEndpoint, $scope.data);
            } else {
                $scope.$watch('data', function(newValue, oldValue) {
                    if (newValue && !isRepresentationLoaded) {
                        // console.log('entity watch', newValue);
                        getRepresentation($scope.apiEndpoint, $scope.data);
                    }
                });
            }
*/
            $scope.$watch('data', function(newValue, oldValue) {
                if (newValue || (newValue != oldValue && !oldValue)) {
                    getRepresentation($scope.apiEndpoint, $scope.data);
                } else {
                    $scope.item = null;
                }
            });


            // $scope.register = function () {
            //     AssignmentMediator.register($scope.add, $scope.type);
            // };
        }],
        link: function (scope, element, attrs, ctrl) {

            var ngModelCtrl = ctrl[0];
            var ngFormCtrl = ctrl[1];
            // console.log('entity', scope, element);

            scope.register = function () {
                // console.log('register');
                // console.log(scope.add, scope.type, element);
                AssignmentMediator.register(scope.add, scope.type, element);
            };
            scope.$watch(function () {
                return AssignmentMediator.element;
            }, function (value) {
                scope.isFocused = element == AssignmentMediator.element;
            });

            function setDirty() {
                ngModelCtrl.$dirty = true;
                ngModelCtrl.$pristine = false;
                ngFormCtrl.$setDirty();
            }

            scope.add = function (data) {
                if (!data.id) throw new Error('cfFormEntity: Assigned object has no id');
                scope.data = data.id;
                scope.item = data;
                setDirty();
                AssignmentService.add(scope.type, attrs.name, scope.item);
            };
            scope.remove = function () {
                scope.data = null;
                scope.item = null;
                setDirty();
                scope.register();
                AssignmentService.add(scope.type, attrs.name, scope.item);
            };
            scope.$on('$destroy', function() {
                AssignmentService.add(scope.type, attrs.name, null);
            });

            element.on('dragover', function (e) {
                if ($('body.cf-file-over').length) return;
                if (e.originalEvent) e = e.originalEvent;
                if (e.preventDefault) e.preventDefault();
                return false;
            });

            element.on('dragenter', function (e) {
                if ($('body.cf-file-over').length) return;
                if (e.originalEvent) e = e.originalEvent;
                if (e.preventDefault) e.preventDefault();
                return false;
            });

            element.on('drop', function (e) {
                if ($('body.cf-file-over').length) return;
                // console.log('drop');
                // console.log(scope.$id);
                if (e.originalEvent) e = e.originalEvent;
                if (e.preventDefault) e.preventDefault();

                var data = e.dataTransfer.getData(scope.type);
                try {
                    data = JSON.parse(data);
                } catch (e) {
                    data = null;
                }
                
                if (data) {
                    scope.$apply(function() {
                        scope.add(data);
                    });
                }
                
                return false;

            });
// console.log('a', scope.register);

        }
    };
});



coreFramework.directive('cfFormEntityCollection', function ($rootScope, AssignmentService, AssignmentMediator, ItemTypes) {
    return {
        restrict: 'EA',
        replace: true,
        require: ['ngModel', '^form'],
        scope: {
            data: '=ngModel',
            type: '@contentType',
            apiEndpoint: '@',
            apiParams: '@',
            min: '@',
            max: '@',
            imageVariation: '@'
        },
        template: function (element, attrs) {
            var itemType = ItemTypes.get(attrs.contentType);
            var itemElement = itemType.directive;
            var template;

            var showEditButton = true;
            if (itemType.buttons) {
                showEditButton = itemType.buttons.edit === true;
            }
            if (!$rootScope.userManager.userHasPrivilege(attrs.apiEndpoint + ':edit')) {
                showEditButton = false;
            }

            if (itemType.params && itemType.params.uploader && $rootScope.userManager.userHasPrivilege(attrs.apiEndpoint + ':create')) {
                template = '<div class="cf-form-entity-collection" ng-class="{ loading: isLoading, \'not-empty\': items.length, \'file-over\': dropClass }" flow-drag-enter="dropClass=true" flow-drag-leave="dropClass=false">' +
                            '<div class="cf-form-entity-drop" flow-init="flowOptions" flow-drop flow-complete="flow(\'complete\', $flow)" flow-file-success="flow(\'fileSuccess\', $flow, $file)" flow-file-error="flow(\'fileError\', $flow, $file, $message)" flow-file-progress="flow(\'fileProgress\', $flow, $file)" flow-files-submitted="flow(\'filesSubmitted\', $flow, $files)">' +
                                '<div class="cf-form-entity-item" ng-show="(items.length < max) || (!max)">' +
                                    '<span flow-btn class="Button">Add {{buttonLabel || "entities"}}</span>' +
                                    '<span class="description">Drag from computer or <a ng-click="register()">panel on the right</a>!</span>' +
                                '</div>' +
                                '<div ui-sortable ng-model="data">' +
                                    '<div ng-repeat="item in items" class="cf-form-entity-item">' +
                                        '<div flow-init="flowOptions" flow-drop flow-file-success="flow(\'fileSuccess\', $flow, $file, $index)" flow-file-progress="flow(\'fileProgress\', $flow, $file, $index)" flow-files-submitted="flow(\'filesSubmitted\', $flow, $files, $index)">' +
                                            '<' + itemElement + ' class="" flow-img="$flow.files[0]"></' + itemElement + '>' +
                                            '<div class="cf-form-entity-item-progress" ng-class="{ complete: $flow.files[0] ? $flow.files[0].progress() == 1 : false }" ng-if="$flow.files[0] && $flow.files[0].progress()" style="width: {{ 100 * ((1 - $flow.files[0].progress()) || 1) }}%;"></div>' +
                                            '<div class="cf-form-entity-item-overlay">' +
                                                '<div class="cf-form-entity-item-overlay-title">{{ item.title }}</div>' +
                                                '<a ng-click="remove($index)" class="cf-form-entity-item-overlay-button">Remove</a>' +
                                                '<a ng-if="getImage(item)" ng-click="preview(getImage(item))" class="cf-form-entity-item-overlay-button">Preview</a>' +
                                                '<span flow-btn flow-single-file class="cf-form-entity-item-overlay-button">Replace</span>' +
                                                (showEditButton ? '<a href="/admin/{{apiEndpoint}}/edit/{{item.id}}/{{ imageVariation ? \'?variation=\' + imageVariation : \'\' }}" class="cf-form-entity-item-overlay-button">Edit</a>' : '') +
                                            '</div>' +
                                        '</div>' +
                                    '</div>' +
                                '</div>' +
                                '<div>' +
                                    '<div ng-repeat="file in $flow.files" class="cf-form-entity-item">' +
                                        '<div>' +
                                            '<' + itemElement + ' class="" flow-img="file"></' + itemElement + '>' +
                                            '<div class="cf-form-entity-item-progress" ng-class="{ complete: file.progress() == 1 }" style="width: {{ 100 * ((1 - file.progress()) || 1) }}%;"></div>' +
                                            '<div class="cf-form-entity-item-overlay">' +
                                                '<div class="cf-form-entity-item-overlay-title">{{ file.name }}</div>' +
                                                '<a ng-click="file.cancel()" class="cf-form-entity-item-overlay-button">Remove</a>'
                                            '</div>' +
                                        '</div>' +
                                    '</div>' +
                                '</div>' +
                            '</div>' +
                        '</div>';
            } else {
                template = '<div class="cf-form-entity-collection" ng-class="{ loading: isLoading, \'not-empty\': items.length }">' +
                            '<div class="cf-form-entity-drop">' +
                                '<div class="cf-form-entity-item" ng-show="(items.length < max) || (!max)">' +
                                    '<span ng-click="register()" class="Button">Add {{buttonLabel || "entities"}}</span>' +
                                    '<span class="description">from the <a ng-click="register()">panel on the right</a>!</span>' +
                                '</div>' +
                                '<div ui-sortable ng-model="data">' +
                                    '<div ng-repeat="item in items" class="cf-form-entity-item">' +
                                        '<div>' +
                                            '<' + itemElement + ' class=""></' + itemElement + '>' +
                                            '<div class="cf-form-entity-item-overlay">' +
                                                '<div class="cf-form-entity-item-overlay-title">{{ item.title }}</div>' +
                                                '<a ng-click="remove($index)" class="cf-form-entity-item-overlay-button">Remove</a>' +
                                                '<a ng-if="getImage(item)" ng-click="preview(getImage(item))" class="cf-form-entity-item-overlay-button">Preview</a>' +
                                                (showEditButton ? '<a href="/admin/{{apiEndpoint}}/edit/{{item.id}}/{{ imageVariation ? \'?variation=\' + imageVariation : \'\' }}" class="cf-form-entity-item-overlay-button">Edit</a>' : '') +
                                            '</div>' +
                                        '</div>' +
                                    '</div>' +
                                '</div>' +
                            '</div>' +
                        '</div>';
            }
            return template;
        },
        controller: ['$scope', '$parse', 'Restangular', 'Auth', '$attrs', 'ngDialog', 'Message', function ($scope, $parse, Restangular, Auth, $attrs, ngDialog, Message) {
            
            $scope.flowOptions = {
                target: '/api/' + $scope.apiEndpoint + '/',
                headers: {
                    'Authorization': 'Bearer ' + Auth.getToken()
                },
                fileParameterName: $scope.type + 's',
                withCredentials: true,
                testChunks: false,
                progressCallbacksInterval: 10,
                chunkSize: 24 * 1024 * 1024,
                permanentErrors: [400, 401, 403, 404, 405, 409, 412, 415, 422, 500, 503, 504]
            };

            $scope.flowCheck = function (flow) {
                if (flow.files.length == $scope.$eval(flow.files.map(function (file) { return file.chunks[0].itemData ? 1 : 0; }).join('+'))) {
                    angular.forEach(flow.files, function (file) {
                        $scope.add(file.chunks[0].itemData);
                    });
                    flow.cancel();
                }
            };
            
            $scope.flow = function (eventType, flow, flowObj, index, message) {
                console.log('flow event: ', eventType, flow, flowObj, index);
                switch (eventType) {
                    case 'complete':
                            angular.forEach(flow.files, function (file) {
                                if (!file.chunks[0].itemDataCalled) {
                                    file.chunks[0].itemDataCalled = true;
                                    Restangular.oneUrl('item', file.chunks[0].itemLocation).get($scope.$eval($scope.apiParams)).then(function (response) {
                                        file.chunks[0].itemData = response.data.originalElement;
                                        $scope.flowCheck(flow);
                                    });
                                }
                            });
                        break;
                    case 'fileSuccess':
                        if (angular.isDefined(index)) {
                            Restangular.oneUrl('item', flowObj.chunks[0].itemLocation).get($scope.$eval($scope.apiParams)).then(function (response) {
                                $scope.replace(response.data.originalElement, index);
                                flow.cancel();
                            });
                        }
                        break;
                    case 'filesSubmitted':
                        flow.resume();
                        break;
                    case 'fileError':
                        console.log(eventType, flowObj, index, message);
                        var errorMessage = {};
                        try {
                            errorMessage = angular.fromJson(index); // test
                        } catch (e) {}
                        
                        if (errorMessage.length) {
                            errorMessage = $parse('message')(errorMessage[0]);
                        } else {
                            errorMessage = $parse('message')(errorMessage);
                        }
                        
                        Message.send(errorMessage || 'Error!', 'error', 0);
                        flow.resume();
                        flowObj.abort(true);
                        break;
                }
            };

            $scope.preview = function (src) {
                console.log(src);
                ngDialog.open({
                    template: '<img src="' + src + '" />',
                    plain: true,
                    scope: $scope
                });
            };

            $scope.getImage = function (item) {
                if (angular.isDefined(item.variations)) {
                    if (angular.isDefined(item.variations[$scope.imageVariation])) {
                        return item.variations[$scope.imageVariation].url;
                    } else if (angular.isDefined(item.variations[$scope.defaultVariation])) {
                        return item.variations[$scope.defaultVariation].url;
                    }
                }
                return null;
            };

            var isRepresentationLoaded = false;
            $scope.items = [];
            $scope.defaultVariation = 'admin_small';

            function orderBy(order, array) {
                return _.map(order, function (value, index) {
                    return angular.copy(_.findWhere(array, {id: '' + value}) || {id: '' + value});
                });
            }

            function getRepresentation(endpoint, data) {
                if (data.length && data[0].id) {
                    $scope.items = data;
                    AssignmentService.add($scope.type, $attrs.name, $scope.items);
                    data = $scope.items.map(function (item) { return item.id; });
                    return;
                } else if ($scope.items && $scope.items.map(function(item) { return item.id; }).join(',') == data.join(',')) {
                    angular.forEach($scope.items, function (item) {
                        if ( angular.isDefined( item.variations ) ) {
                            if (angular.isDefined(item.variations[$scope.imageVariation])) {
                                item.image_url = item.variations[$scope.imageVariation].url;
                            } else if (angular.isDefined(item.variations[$scope.defaultVariation])) {
                                item.image_url = item.variations[$scope.defaultVariation].url;
                            }
                        }
                    });
                    return;
                }


                var apiParams = angular.extend({'query': 'id:[' + data.toString() + ']'}, $scope.$eval($scope.apiParams));
                $scope.isLoading = true;

                Restangular.one(endpoint).get(apiParams).then(function (response) {
                    $scope.items = orderBy(data, response.data.originalElement);
                    $scope.isLoading = false;
                    isRepresentationLoaded = true;
                    AssignmentService.add($scope.type, $attrs.name, $scope.items);
                    angular.forEach($scope.items, function (item) {
                        if ( angular.isDefined( item.variations ) ) {
                            if (angular.isDefined(item.variations[$scope.imageVariation])) {
                                item.image_url = item.variations[$scope.imageVariation].url;
                            } else if (angular.isDefined(item.variations[$scope.defaultVariation])) {
                                item.image_url = item.variations[$scope.defaultVariation].url;
                            }
                        }
                    });
                }, function (error) {
                    console.error('cf-form-entity-collection error', error);
                    $scope.isLoading = false;
                });
            }

            if ($scope.data) {
                getRepresentation($scope.apiEndpoint, $scope.data);
            } else {
                $scope.$watch('data', function(newValue, oldValue) {
                    if (newValue && !isRepresentationLoaded) {
                        getRepresentation($scope.apiEndpoint, $scope.data);
                    }
                });
            }

            $scope.$watch('data', function () {
                if ($scope.data && $scope.items.length) {
                    if ($scope.data.length && $scope.data[0].id) {
                        $scope.items = $scope.data;
                        $scope.data = $scope.items.map(function (item) { return item.id; });
                    } else {
                        $scope.items = orderBy($scope.data, $scope.items);
                    }

                    angular.forEach($scope.items, function (item) {
                        if ( angular.isDefined( item.variations ) ) {
                            if (angular.isDefined(item.variations[$scope.imageVariation])) {
                                item.image_url = item.variations[$scope.imageVariation].url;
                            } else if (angular.isDefined(item.variations[$scope.defaultVariation])) {
                                item.image_url = item.variations[$scope.defaultVariation].url;
                            }
                        }
                    });
                }
            }, true);

        }],
        link: function (scope, element, attrs, ctrl) {

            var ngModelCtrl = ctrl[0];
            var ngFormCtrl = ctrl[1];

            scope.register = function () {
                AssignmentMediator.register(scope.add, scope.type, element);
            };
            scope.$watch(function () {
                return AssignmentMediator.element;
            }, function (value) {
                scope.isFocused = element == AssignmentMediator.element;
            });

            // console.log('entityCollection', scope);

            function setDirty() {
                ngModelCtrl.$dirty = true;
                ngModelCtrl.$pristine = false;
                ngFormCtrl.$setDirty();
            }

            function validateMin(value) {
                if (scope.min && scope.data && (scope.data.length < scope.min)) {
                    return false;
                } else {
                    return true;
                }
            }

            function validateMax(value) {
                if (scope.max && scope.data && (scope.data.length > scope.max)) {
                    return false;
                } else {
                    return true;
                }
            }

            scope.$watch(function () {
                return ngModelCtrl.$modelValue;
            }, function(modelValue, oldValue) {
                ngModelCtrl.$setValidity('min', validateMin(modelValue));
                ngModelCtrl.$setValidity('max', validateMax(modelValue));
            }, true);

            scope.replace = function (data, index) {
                console.log(data);
                if (!data.id) throw new Error('cfFormEntityCollection: Assigned object has no id');
                if (!angular.isArray(scope.data) || scope.data.length <= index) {
                    return scope.add(data);
                }
                if (~scope.data.indexOf(data.id)) {
                    console.log('cfFormEntityCollection: item already in collection');
                    return;
                }
                scope.data.splice(index, 1, data.id);
                scope.items.splice(index, 1, data);
                setDirty();
                AssignmentService.add(scope.type, attrs.name, scope.items);
                return true;
            };
            scope.add = function (data) {
                console.log(data);
                if (!data.id) throw new Error('cfFormEntityCollection: Assigned object has no id');
                if (!angular.isArray(scope.data)) {
                    scope.data = [];
                    scope.items = [];
                }
                if (scope.data.length == scope.max) {
                    console.log('cfFormEntityCollection: collection maxed out');
                    return;
                }
                if (~scope.data.indexOf(data.id)) {
                    console.log('cfFormEntityCollection: item already in collection');
                    return;
                }
                scope.data.push(data.id);
                scope.items.push(data);
                setDirty();
                AssignmentService.add(scope.type, attrs.name, scope.items);
                return true;
            };
            scope.remove = function (index) {
                scope.data.splice(index, 1);
                scope.items.splice(index, 1);
                setDirty();
                scope.register();
                AssignmentService.add(scope.type, attrs.name, scope.items);
            };
            scope.$on('$destroy', function() {
                AssignmentService.add(scope.type, attrs.name, null);
            });
            

            element.on('dragover', function (e) {
                if ($('body.cf-file-over').length) return;
                if (e.originalEvent) e = e.originalEvent;
                if (e.preventDefault) e.preventDefault();
                return false;
            });

            element.on('dragenter', function (e) {
                if ($('body.cf-file-over').length) return;
                if (e.originalEvent) e = e.originalEvent;
                if (e.preventDefault) e.preventDefault();
                return false;
            });

            element.on('drop', function (e) {
                if ($('body.cf-file-over').length) return;
                if (e.originalEvent) e = e.originalEvent;
                if (e.preventDefault) e.preventDefault();

                var data = e.dataTransfer.getData(scope.type);
                try {
                    data = JSON.parse(data);
                } catch (e) {
                    data = null;
                }
                
                if (data) {
                    scope.$apply(function() {
                        scope.add(data);
                    });
                }
                
                return false;

            });
        }
    };
});



coreFramework.directive('cfSelect', function () {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: {},
        template: '<select></select>',
        controller: ['$scope', '$attrs', '$element', 'Restangular', 'Message', '$timeout', '$transclude', function($scope, $attrs, $element, Restangular, Message, $timeout, $transclude) {

            $transclude($scope, function(clone) {
                $element.empty();
                $element.append(clone);
            });

            var dataVar = 'cfSelectData';

            var options;
            if (angular.isDefined($attrs.ngOptions)) {
                return;
                options = $attrs.ngOptions.split(' in ');
            } else {
                options = $attrs.cfOptions.split(' in ');
            }

            var source = options.splice(options.length - 1, 1)[0];
            var filter = source.split('|')[1];
                source = source.split('|')[0];
            options.push(dataVar + (filter ? '|' + filter : ''));
            $attrs.$set('ngOptions', options.join(' in '));

            //if (angular.isDefined($scope.$parent.$eval(source)) || angular.isUndefined($attrs.cfOptions)) {
            if (angular.isUndefined($attrs.cfOptions)) {

                $scope.$watch(function() { return $scope.$parent.$eval(source); }, function(value) {
                    $scope[dataVar] = value;
                }, true);

            } else {

                $scope[dataVar] = [];

                Restangular.one(source).get($scope.$eval($attrs.cfParams)).then(function (response) {
                    fillList(response.data.originalElement);
                }, function (errors) {
                    Message.send('Error!', 'error');
                });

            }

            function fillList(data) {

                angular.forEach(data, function(item) {
                    $scope[dataVar].push(item);
                    if (item.__children) {
                        //fillList(item.__children);
                    }
                });

            }

        }]
    };
});
coreFramework.directive('cfListToOne', function () {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ctrl) {

            ctrl.$formatters.push(function(value) {
                return value && [value];
            });
            ctrl.$parsers.push(function(value) {
                return value && value[0];
            });

        }
    };
});
coreFramework.directive('cfMultiSelect', function ($timeout, Restangular) {
    return {
        restrict: 'EA',
        replace: true,
        require: ['ngModel', '^form'],
        scope: {
            data: '=ngModel',
            apiEndpoint: '@',
            apiParams: '@',
            placeholder: '@',
            textField: '@',
            valueField: '@',
            minQuery: '@',
            min: '@',
            max: '@',
            addNewItem: '@'
        },
        template: '<div><div kendo-multi-select k-options="selectOptions" ng-model="cfMultiSelectModel"></div></div>',
        controller: ['$scope', '$attrs', '$compile', function ($scope, $attrs, $compile) {
            var selectLoaded = false,
                selectCases = {
                    QUERY: 1,
                    GET_ALL: 2,
                    LOAD_IDS: 3
                };

            $scope.$watch('data', function (value) {
                if (!value) {
                    return;
                }
                if (!$scope.cfMultiSelectModel) {
                    $scope.cfMultiSelectModel = [];
                }
                while($scope.cfMultiSelectModel.length) $scope.cfMultiSelectModel.pop();
                if (angular.isArray(value)) {
                    value.forEach(function (item) {
                        $scope.cfMultiSelectModel.push(item);
                    });
                } else {
                    $scope.cfMultiSelectModel.push(value);
                }
            }, true);
            $scope.$watch('cfMultiSelectModel', function (value) {
                if (value) {
                    $scope.data = angular.copy(value);
                    if ($attrs.cfListToOne && angular.isArray($scope.data)) {
                        $scope.data = $scope.data[0];
                    }
                }
            }, true);

            function addNewElementMaybe (filter,data) {

                if (!$scope.addNewItem) {
                    return;
                }

                var addItem = true;
                var filterValue;

                if (angular.isDefined(filter) &&
                        angular.isDefined(filter.filters) &&
                        filter.filters.length &&
                        filter.filters[0].value.length>1
                    ) {

                    filterValue = filter.filters[0].value;
                    angular.forEach(data,function (item) { //console.log(item,filterValue);
                        if (item.title == filterValue) {
                            addItem = false;
                        }
                    });
                } else {
                    addItem = false;
                }

                if (addItem) {
                    data.unshift({id: '---' + filterValue, title: 'Create new: ' + filterValue});
                }
            }

            function apiResponse (selectedCase, options) {
                return function (response) {

                    var responseData = response.data.originalElement;

                    addNewElementMaybe(options.data.filter, responseData);

                    options.success(response.data.originalElement);

                    if (selectedCase == selectCases.LOAD_IDS) {
                        options.success([]);
                    }

                };
            }

            $scope.selectOptions = {
                placeholder: $scope.placeholder,
                dataTextField: $scope.textField,
                dataValueField: $scope.valueField,
                minLength: $scope.minQuery > 0 ? $scope.minQuery : undefined,
                maxSelectedItems: $scope.max,
                autoBind: false,
                serverFiltering: true,
                serverSorting: true,
                serverPaging: true,
                ignoreCase: false,
                dataSource: {
                    serverFiltering: true,
                    serverSorting: true,
                    serverPaging: true,
                    transport: {
                        read: function(options) {
                            
                            var ids = $scope.$parent.$eval($attrs.ngModel),
                                apiParams = {},
                                selectCase;

                            if (angular.isDefined(options.data.filter)) {
                                if (options.data.filter.filters.length) {
                                    selectCase = selectCases.QUERY;
                                    apiParams.query = options.data.filter.filters[0].value;
                                    apiParams.query = ($attrs.queryField || 'title') + ':' + options.data.filter.filters[0].value;
                                } else {
                                    selectCase = selectCases.GET_ALL;
                                }
                            } else {
                                if (selectLoaded) {
                                    selectCase = selectCases.GET_ALL;
                                } else {
                                    if (angular.isDefined(ids) && ids.length) {
                                        selectCase = selectCases.LOAD_IDS;
                                        apiParams.query = 'id:[' + ids.toString() + ']';
                                    } else {
                                        selectCase = selectCases.GET_ALL;
                                    }
                                    selectLoaded = true;
                                }
                            }

                            angular.extend(apiParams, $scope.$eval($scope.apiParams));

                            Restangular.oneUrl($scope.apiEndpoint).get(apiParams).then(apiResponse(selectCase, options), function (errors) {
                                options.error();
                            });
                        }
                    }
                }
            };
        }],
        link: function (scope, element, attrs, ctrl) {

            var ngModelCtrl = ctrl[0];
            var ngFormCtrl = ctrl[1];

            function setDirty() {
                ngModelCtrl.$dirty = true;
                ngModelCtrl.$pristine = false;
                ngFormCtrl.$setDirty();
            }

            function validateMin(value) {
                if (scope.min && scope.data && angular.isArray(scope.data)) {
                    if (scope.data.length < scope.min) {
                        return false;
                    } else {
                        return true;
                    }
                } else {
                    return true;
                }
            }

            function validateMax(value) {
                if (scope.max && scope.data && angular.isArray(scope.data)) {
                    if (scope.data.length > scope.max) {
                        return false;
                    } else {
                        return true;
                    }
                } else {
                    return true;
                }
            }
            scope.$watch(function () {
                return ngModelCtrl.$modelValue;
            }, function(modelValue, oldValue) {
                if (modelValue == oldValue) {
                    return;
                }

                angular.forEach(modelValue, function (id) {
                    if (id.indexOf('---') == 0) {
                        id = id.replace('---', '+++');
                        Restangular.all(scope.apiEndpoint).post({ title: id.replace('+++', '') }).then(function (response) {
                            Restangular.oneUrl('oneTag',response.headers('location')).get().then(function (response) {
                                var tag = response.data.originalElement;

                                // Make sure we accept [{item}] as well as {item}
                                if (angular.isDefined(tag[0]) && angular.isDefined(tag[0].id)) {
                                    tag = tag[0];
                                }

                                if (angular.isDefined(tag.id) && angular.isDefined(tag.title)) {
                                    var i = 0;
                                    angular.forEach(modelValue, function (val) {
                                        if (modelValue[i].replace('---', '') == tag.title) {
                                            modelValue[i] = tag.id;
                                        }
                                    i++;
                                    });
                                }
                            });
                        });
                        return;
                    }
                });

                if (scope.min) ngModelCtrl.$setValidity('min', validateMin(modelValue));
                if (scope.max) ngModelCtrl.$setValidity('max', validateMax(modelValue));
                setDirty();
                //ctrl.$setValidity('required', modelValue.length >= min);
            }, true);

        }
    };
});

coreFramework.directive('cfMultiSelectOld', function ($timeout) {
    return {
        restrict: 'E',
        replace: true,
        require: 'ngModel',
        scope: {
            data: '=ngModel',
            apiEndpoint: '@',
            apiParams: '@',
            placeholder: '@',
            textField: '@',
            valueField: '@',
            minQuery: '@',
            min: '@',
            max: '@'
        },
        template: '<div kendo-multi-select k-options="selectOptions"></div>',
        controller: ['$scope', '$attrs', 'Restangular', function ($scope, $attrs, Restangular) {
            var selectLoaded = false,
                selectCases = {
                    QUERY: 1,
                    GET_ALL: 2,
                    LOAD_IDS: 3
                };

            function apiResponse (selectedCase, options) {
                return function (response) {
                    options.success(response.data.originalElement);

                    if (selectedCase == selectCases.LOAD_IDS) {
                        options.success([]);
                    }

                };
            }

            $scope.selectOptions = {
                placeholder: $scope.placeholder,
                dataTextField: $scope.textField,
                dataValueField: $scope.valueField,
                minLength: $scope.minQuery > 0 ? $scope.minQuery : undefined,
                maxSelectedItems: $scope.max,
                autoBind: false,
                serverFiltering: true,
                serverSorting: true,
                serverPaging: true,
                dataSource: {
                    serverFiltering: true,
                    serverSorting: true,
                    serverPaging: true,
                    transport: {
                        read: function(options) {
                            
                            var ids = $scope.$parent.$eval($attrs.ngModel),
                                apiParams = {},
                                selectCase;

                            if (angular.isDefined(options.data.filter)) {
                                if (options.data.filter.filters.length) {
                                    selectCase = selectCases.QUERY;
                                    apiParams.query = options.data.filter.filters[0].value;
                                    apiParams.query = 'title:' + options.data.filter.filters[0].value;
                                } else {
                                    selectCase = selectCases.GET_ALL;
                                }
                            } else {
                                if (selectLoaded) {
                                    selectCase = selectCases.GET_ALL;
                                } else {
                                    if (angular.isDefined(ids) && ids.length) {
                                        selectCase = selectCases.LOAD_IDS;
                                        apiParams.query = 'id:[' + ids.toString() + ']';
                                    } else {
                                        selectCase = selectCases.GET_ALL;
                                    }
                                    selectLoaded = true;
                                }
                            }

                            angular.extend(apiParams, $scope.$eval($scope.apiParams));

                            Restangular.oneUrl($scope.apiEndpoint).get(apiParams).then(apiResponse(selectCase, options), function (errors) {
                                options.error();
                            });
                        }
                    }
                }
            };
        }],
        link: function (scope, element, attrs, ctrl) {

            function setDirty() {
                ctrl.$dirty = true;
                ctrl.$pristine = false;
            }

            function validateMin(value) {
                if (scope.min && scope.data && (scope.data.length < scope.min)) {
                    return false;
                } else {
                    return true;
                }
            }

            function validateMax(value) {
                if (scope.max && scope.data && (scope.data.length > scope.max)) {
                    return false;
                } else {
                    return true;
                }
            }

            scope.$watch(function () {
                return ctrl.$modelValue;
            }, function(modelValue, oldValue) {
                if (modelValue == oldValue) {
                    return;
                }
                if (scope.min) ctrl.$setValidity('min', validateMin(modelValue));
                if (scope.max) ctrl.$setValidity('max', validateMax(modelValue));
                setDirty();
                //ctrl.$setValidity('required', modelValue.length >= min);
            }, true);

        }
    };
});
coreFramework.directive('cfClearButton', function ($compile) {
    return {
        restrict: 'AC',
        scope: {
            ngModel: '='
        },
        link: function (scope, element, attrs) {

            var btn = angular.element('<a href="" ng-click="clear()" class="ClearAction">Clear</a>');
                btn.insertAfter(element);
            $compile(btn)(scope);

            scope.clear = function () {
                scope.ngModel = null;
            };

        }
    };
});
coreFramework.directive('cfValueQ', function () {
    return {
        restrict: 'A',
        scope: {
            ngModel: '=',
            value: '@cfValue'
        },
        link: function (scope, element, attrs) {

            scope.ngModel = scope.value;

        }
    };
});
coreFramework.directive('cfValue', function () {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ctrl) {
            if (angular.isUndefined(scope.$eval(attrs.ngModel))) {
                ctrl.$setViewValue(scope.$eval(attrs.cfValue));
            }
        }
    };
});
coreFramework.directive('cfDatePicker', function () {
    return {
        restrict: 'A',
        scope: {
            ngModel: '=',
            format: '@cfDatePickerFormat',
            time: '@cfDatePickerTime'
        },
        link: function (scope, element, attrs) {

            if (scope.time && scope.time == 'false') {
                element.kendoDatePicker({format: scope.format || 'yyyy-MM-dd HH:mm:ss', parseFormats: [scope.format || 'yyyy-MM-dd HH:mm:ss']});
                scope.picker = element.data("kendoDatePicker");
            } else {
                element.kendoDateTimePicker({format: scope.format || 'yyyy-MM-dd HH:mm:ss', parseFormats: [scope.format || 'yyyy-MM-dd HH:mm:ss']});
                scope.picker = element.data("kendoDateTimePicker");
            }
            scope.picker.bind("change", function(e) {
                $(element).trigger('k-change');
            });

        }
    };
});
coreFramework.directive('cfFormFieldLabel', function ($compile) {
    return {
        restrict: 'C',
        link: function (scope, element, attrs, ctrl) {
            var requiredCondition = element.parent().find('.cf-form-field-element > :input').attr('ng-required');
            var requiredLabel = element.find('.cf-form-required');
            if (requiredCondition) {
                requiredLabel.attr('ng-show', requiredCondition)
                $compile(requiredLabel)(scope);
            }
        }
    };
});
coreFramework.directive('cfLabel', function () {
    return {
        restrict: 'EA',
        replace: true,
        transclude: true,
        template: '<label ng-transclude></label>'
    };
});
coreFramework.filter('cfFilterByList', function () {
    return function (list, values, param, exclude) {
        // console.log(list, values, param);
        if (!list) {
            return [];
        }
        if (!values || values.length == 0) {
            if (exclude) {
                return list
            } else {
                return [];
            }
        }
        var filtered = [];
        angular.forEach(list, function (item) {
            var visible = false;
            angular.forEach(values, function (value) {
                visible = visible || (param ? item[param] == value : item == value);
            });
            if (exclude) {
                visible = !visible;
            }
            if (visible) {
                filtered.push(item);
            }
        });
        return filtered;
    };
});
coreFramework.directive('cfMultiCheckbox', function (Restangular) {
    return {
        restrict: 'EA',
        replace: true,
        require: ['ngModel', '^form'],
        scope: {
            data: '=ngModel',
            min: '@',
            max: '@'
        },
        template:   '<div>' +
                        '<div ng-repeat="item in data">' +
                            '<label>' +
                                '<input type="checkbox" ng-model="item.value" ng-change="onChange(item)" />' +
                                '{{ item[labelParam] }}' +
                            '</label>' +
                        '</div>' +
                    '</div>',
        controller: ['$scope', function ($scope) {
            
        }],
        link: function (scope, element, attrs, ctrl) {

            scope.labelParam = attrs.textField || 'label';

            var apiData;

            !scope.data && attrs.apiEndpoint && Restangular.one(attrs.apiEndpoint).get().then(function (response) {
                apiData = response.data.originalElement;
                !scope.data && (scope.data = apiData);
            });

            scope.$watch('data', function (value) {
                if (!value && apiData) {
                    scope.data = apiData;
                }
            });

            var ngModelCtrl = ctrl[0];
            var ngFormCtrl = ctrl[1];

            scope.onChange = function (item) {
                setDirty();
            };

            function setDirty() {
                ngModelCtrl.$dirty = true;
                ngModelCtrl.$pristine = false;
                ngFormCtrl.$setDirty();
            }

            function validateMin(value) {
                // fix
                if (scope.min && scope.data) {
                    var checked = 0;
                    for (var i = 0; i < scope.data.length; i++) {
                        if (scope.data[i].value) {
                            checked++;
                        }
                    }
                    if (checked < scope.min) {
                        return false;
                    } else {
                        return true;
                    }
                } else {
                    return true;
                }
            }

            function validateMax(value) {
                // fix
                if (scope.max && scope.data) {
                    var checked = 0;
                    for (var i = 0; i < scope.data.length; i++) {
                        if (scope.data[i].value) {
                            checked++;
                        }
                    }
                    if (checked > scope.min) {
                        return false;
                    } else {
                        return true;
                    }
                    return false;
                } else {
                    return true;
                }
            }

            scope.$watch(function () {
                return ngModelCtrl.$modelValue;
            }, function(modelValue, oldValue) {
                ngModelCtrl.$setValidity('min', validateMin(modelValue));
                ngModelCtrl.$setValidity('max', validateMax(modelValue));
            }, true);

        }
    };
});

coreFramework.directive('cfMatchModel', function ($timeout) {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ctrl) {
            scope.$watch(attrs.cfMatchModel, function (value) {
                if (value) {
                    if (angular.isUndefined(scope.$eval(attrs.ngModel))) {
                        $timeout(function () {
                            ctrl.$setViewValue(value);
                            ctrl.$render();
                        });
                    }
                }
            });
        }
    };
});