angular.module("umbraco.directives")
    .directive('gridRte', function (tinyMceService, stylesheetResource, angularHelper, assetsService, $q, $timeout) {
        return {
            scope: {
                uniqueId: '=',
                value: '=',
                onClick: '&',
                onFocus: '&',
                onBlur: '&',
                configuration:"=",
                onMediaPickerClick: "=",
                onEmbedClick: "=",
                onMacroPickerClick: "=",
                onLinkPickerClick: "="
            },
            template: "<textarea ng-model=\"value\" rows=\"10\" class=\"mceNoEditor\" style=\"overflow:hidden\" id=\"{{uniqueId}}\"></textarea>",
            replace: true,
            link: function (scope, element, attrs) {

                var initTiny = function () {

                    //we always fetch the default one, and then override parts with our own
                    tinyMceService.configuration().then(function (tinyMceConfig) {



                        //config value from general tinymce.config file
                        var validElements = tinyMceConfig.validElements;
                        var fallbackStyles = [{title: "Page header", block: "h2"}, {title: "Section header", block: "h3"}, {title: "Paragraph header", block: "h4"}, {title: "Normal", block: "p"}, {title: "Quote", block: "blockquote"}, {title: "Code", block: "code"}];

                        //These are absolutely required in order for the macros to render inline
                        //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
                        var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style]";

                        var invalidElements = tinyMceConfig.inValidElements;
                        var plugins = _.map(tinyMceConfig.plugins, function (plugin) {
                            if (plugin.useOnFrontend) {
                                return plugin.name;
                            }
                        }).join(" ") + " autoresize";

                        //config value on the data type
                        var toolbar = ["code", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbembeddialog"].join(" | ");
                        var stylesheets = [];

                        var styleFormats = [];
                        var await = [];

                        //queue file loading
                        if (typeof (tinymce) === "undefined") {
                                await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope));
                        }


                        if(scope.configuration && scope.configuration.toolbar){
                            toolbar = scope.configuration.toolbar.join(' | ');
                        }


                        if(scope.configuration && scope.configuration.stylesheets){
                            angular.forEach(scope.configuration.stylesheets, function(stylesheet, key){

                                    stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + stylesheet + ".css");
                                    await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) {
                                        angular.forEach(rules, function (rule) {
                                          var r = {};
                                          var split = "";
                                          r.title = rule.name;
                                          if (rule.selector[0] === ".") {
                                              r.inline = "span";
                                              r.classes = rule.selector.substring(1);
                                          }else if (rule.selector[0] === "#") {
                                              //Even though this will render in the style drop down, it will not actually be applied
                                              // to the elements, don't think TinyMCE even supports this and it doesn't really make much sense
                                              // since only one element can have one id.
                                              r.inline = "span";
                                              r.attributes = { id: rule.selector.substring(1) };
                                          }else if (rule.selector[0] !== "." && rule.selector.indexOf(".") > -1) {
                                              split = rule.selector.split(".");
                                              r.block = split[0];
                                              r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " ");
                                          }else if (rule.selector[0] !== "#" && rule.selector.indexOf("#") > -1) {
                                              split = rule.selector.split("#");
                                              r.block = split[0];
                                              r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1);
                                          }else {
                                              r.block = rule.selector;
                                          }
                                          styleFormats.push(r);
                                        });
                                    }));
                            });
                        }else{
                            stylesheets.push("views/propertyeditors/grid/config/grid.default.rtestyles.css");
                            styleFormats = fallbackStyles;
                        }

                        //stores a reference to the editor
                        var tinyMceEditor = null;
                        $q.all(await).then(function () {

                            var uniqueId = scope.uniqueId;

                            //create a baseline Config to exten upon
                            var baseLineConfigObj = {
                                mode: "exact",
                                skin: "umbraco",
                                plugins: plugins,
                                valid_elements: validElements,
                                invalid_elements: invalidElements,
                                extended_valid_elements: extendedValidElements,
                                menubar: false,
                                statusbar: false,
                                relative_urls: false,
                                toolbar: toolbar,
                                content_css: stylesheets,
                                style_formats: styleFormats,
                                autoresize_bottom_margin: 0
                            };


                            if (tinyMceConfig.customConfig) {

                                //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to
                                // convert it to json instead of having it as a string since this is what tinymce requires
                                for (var i in tinyMceConfig.customConfig) {
                                    var val = tinyMceConfig.customConfig[i];
                                    if (val) {
                                        val = val.toString().trim();
                                        if (val.detectIsJson()) {
                                            try {
                                                tinyMceConfig.customConfig[i] = JSON.parse(val);
                                                //now we need to check if this custom config key is defined in our baseline, if it is we don't want to
                                                //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise
                                                //if it's an object it will overwrite the baseline
                                                if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) {
                                                    //concat it and below this concat'd array will overwrite the baseline in angular.extend
                                                    tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]);
                                                }
                                            }
                                            catch (e) {
                                                //cannot parse, we'll just leave it
                                            }
                                        }
                                    }
                                }

                                angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
                            }

                            //set all the things that user configs should not be able to override
                            baseLineConfigObj.elements = uniqueId;
                            baseLineConfigObj.setup = function (editor) {

                                //set the reference
                                tinyMceEditor = editor;


                                //enable browser based spell checking
                                editor.on('init', function (e) {

                                    editor.getBody().setAttribute('spellcheck', true);

                                    //force overflow to hidden to prevent no needed scroll
                                    editor.getBody().style.overflow = "hidden";

                                    $timeout(function(){
                                        if(scope.value === null){
                                            editor.focus();
                                        }
                                    }, 400);

                                });

                                // pin toolbar to top of screen if we have focus and it scrolls off the screen
                                var pinToolbar = function () {

                                    var _toolbar = $(editor.editorContainer).find(".mce-toolbar");
                                    var toolbarHeight = _toolbar.height();

                                    var _tinyMce = $(editor.editorContainer);
                                    var tinyMceRect = _tinyMce[0].getBoundingClientRect();
                                    var tinyMceTop = tinyMceRect.top;
                                    var tinyMceBottom = tinyMceRect.bottom;
                                    var tinyMceWidth = tinyMceRect.width;

                                    var _tinyMceEditArea = _tinyMce.find(".mce-edit-area");

                                    // set padding in top of mce so the content does not "jump" up
                                    _tinyMceEditArea.css("padding-top", toolbarHeight);

                                    if (tinyMceTop < 160 && ((160 + toolbarHeight) < tinyMceBottom)) {
                                        _toolbar
                                            .css("visibility", "visible")
                                            .css("position", "fixed")
                                            .css("top", "160px")
                                            .css("margin-top", "0")
                                            .css("width", tinyMceWidth);
                                    } else {
                                        _toolbar
                                            .css("visibility", "visible")
                                            .css("position", "absolute")
                                            .css("top", "auto")
                                            .css("margin-top", "0")
                                            .css("width", tinyMceWidth);
                                    }
                                    
                                };

                                // unpin toolbar to top of screen
                                var unpinToolbar = function() {

                                    var _toolbar = $(editor.editorContainer).find(".mce-toolbar");
                                    var _tinyMce = $(editor.editorContainer);
                                    var _tinyMceEditArea = _tinyMce.find(".mce-edit-area");

                                    // reset padding in top of mce so the content does not "jump" up
                                    _tinyMceEditArea.css("padding-top", "0");
                                        
                                    _toolbar.css("position", "static");

                                };

                                //when we leave the editor (maybe)
                                editor.on('blur', function (e) {
                                    editor.save();
                                    angularHelper.safeApply(scope, function () {
                                        scope.value = editor.getContent();

                                        var _toolbar = $(editor.editorContainer)
                                             .find(".mce-toolbar");

                                        if(scope.onBlur){
                                            scope.onBlur();
                                        }

                                        unpinToolbar();
                                        $('.umb-panel-body').off('scroll', pinToolbar);

                                    });
                                });

                                // Focus on editor
                                editor.on('focus', function (e) {
                                    angularHelper.safeApply(scope, function () {

                                        if(scope.onFocus){
                                            scope.onFocus();
                                        }

                                        pinToolbar();
                                        $('.umb-panel-body').on('scroll', pinToolbar);

                                    });
                                });

                                // Click on editor
                                editor.on('click', function (e) {
                                    angularHelper.safeApply(scope, function () {

                                        if(scope.onClick){
                                            scope.onClick();
                                        }

                                        pinToolbar();
                                        $('.umb-panel-body').on('scroll', pinToolbar);

                                    });
                                });

                                //when buttons modify content
                                editor.on('ExecCommand', function (e) {
                                    editor.save();
                                    angularHelper.safeApply(scope, function () {
                                        scope.value = editor.getContent();
                                    });
                                });

                                // Update model on keypress
                                editor.on('KeyUp', function (e) {
                                    editor.save();
                                    angularHelper.safeApply(scope, function () {
                                        scope.value = editor.getContent();
                                    });
                                });

                                // Update model on change, i.e. copy/pasted text, plugins altering content
                                editor.on('SetContent', function (e) {
                                    if (!e.initial) {
                                        editor.save();
                                        angularHelper.safeApply(scope, function () {
                                            scope.value = editor.getContent();
                                        });
                                    }
                                });

                                editor.on('ObjectResized', function (e) {
                                    var qs = "?width=" + e.width + "&height=" + e.height;
                                    var srcAttr = $(e.target).attr("src");
                                    var path = srcAttr.split("?")[0];
                                    $(e.target).attr("data-mce-src", path + qs);
                                });

                                //Create the insert link plugin
                                tinyMceService.createLinkPicker(editor, scope, function(currentTarget, anchorElement){
                                    if(scope.onLinkPickerClick) {
                                        scope.onLinkPickerClick(editor, currentTarget, anchorElement);
                                    }
                                });

                                //Create the insert media plugin
                                tinyMceService.createMediaPicker(editor, scope, function(currentTarget, userData){
                                    if(scope.onMediaPickerClick) {
                                        scope.onMediaPickerClick(editor, currentTarget, userData);
                                    }
                                });

                                //Create the embedded plugin
                                tinyMceService.createInsertEmbeddedMedia(editor, scope, function(){
                                    if(scope.onEmbedClick) {
                                        scope.onEmbedClick(editor);
                                    }
                                });

                                //Create the insert macro plugin
                                tinyMceService.createInsertMacro(editor, scope, function(dialogData){
                                    if(scope.onMacroPickerClick) {
                                        scope.onMacroPickerClick(editor, dialogData);
                                    }
                                });

                            };

                            /** Loads in the editor */
                            function loadTinyMce() {

                                //we need to add a timeout here, to force a redraw so TinyMCE can find
                                //the elements needed
                                $timeout(function () {
                                    tinymce.DOM.events.domLoaded = true;
                                    tinymce.init(baseLineConfigObj);
                                }, 150, false);
                            }

                            loadTinyMce();

                            //here we declare a special method which will be called whenever the value has changed from the server
                            //this is instead of doing a watch on the model.value = faster
                            //scope.model.onValueChanged = function (newVal, oldVal) {
                            //    //update the display val again if it has changed from the server;
                            //    tinyMceEditor.setContent(newVal, { format: 'raw' });
                            //    //we need to manually fire this event since it is only ever fired based on loading from the DOM, this
                            //    // is required for our plugins listening to this event to execute
                            //    tinyMceEditor.fire('LoadContent', null);
                            //};

                            //listen for formSubmitting event (the result is callback used to remove the event subscription)
                            var unsubscribe = scope.$on("formSubmitting", function () {
                                //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
                                // we do parse it out on the server side but would be nice to do that on the client side before as well.
                                scope.value = tinyMceEditor ? tinyMceEditor.getContent() : null;
                            });

                            //when the element is disposed we need to unsubscribe!
                            // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
                            // element might still be there even after the modal has been hidden.
                            scope.$on('$destroy', function () {
                                unsubscribe();
                            });

                        });

                    });

                };

                initTiny();

            }
        };
    });
