/*
*/
require.config({
  paths: {
    ckeditor: '../../webjars/application-ckeditor-webjar/1.63.1/ckeditor',
    resourcePickerBundle: '../../webjars/application-ckeditor-webjar/1.63.1/plugins/xwiki-resource/resourcePicker.bundle.min',
    modal: '../../webjars/application-ckeditor-webjar/1.63.1/plugins/xwiki-dialog/modal.min',
    l10n: '../../webjars/application-ckeditor-webjar/1.63.1/plugins/xwiki-localization/l10n.min',
    macroWizard: '../../webjars/application-ckeditor-webjar/1.63.1/plugins/xwiki-macro/macroWizard.bundle.min',
    imageWizard: '../../webjars/application-ckeditor-webjar/1.63.1/plugins/xwiki-image/imageWizard.bundle.min',
    // This is used by the resource suggest picker on the link modal.
    'bootstrap3-typeahead': "../../webjars/bootstrap-3-typeahead/4.0.2/bootstrap3-typeahead.min",
    // This is used to preserve the selection when switching between WYSIWYG and Source modes.
    'fast-diff': "../../webjars/fast-diff/1.2.0/diff"
  },
  bundles: {
    'resourcePickerBundle': ['resource', 'resourcePicker', 'entityResourcePicker', 'entityResourceSuggester',
      'entityResourceDisplayer']
  },
  shim: {
    ckeditor: {
      exports: 'CKEDITOR',
      // This includes dependencies of the plugins bundled with the CKEditor code.
      deps: ['jquery', 'resource', 'resourcePicker', 'macroWizard', 'imageWizard']
    }
  },
  config: {
    l10n: {
      // We need to specify the language because this URL can be used after the page is loaded and thus after the
      // context language changes on the server session (e.g. if the user opens another page in another browser tab with
      // a different language specified). We take the current language from the page HTML, rather than using Velocity,
      // in order to avoid having the current language cached.
      url: new XWiki.Document('Translator', 'CKEditor').getURL('get', 'outputSyntax=plain&language=' +
        encodeURIComponent(document.documentElement.getAttribute('lang') || ''))
    }
  }
});

window.CKEDITOR_BASEPATH = "../../webjars/application-ckeditor-webjar/1.63.1/";

define('xwiki-ckeditor', [
  'jquery',
  'ckeditor',
  // Used to access the form token required by the upload URL.
  'xwiki-meta',
  // Used to catch form action events fired from Prototype.js code (actionButtons.js).
  'xwiki-events-bridge',
  // Configures the path to the tree widget module and its dependencies which are used by the page and attachment
  // pickers on the link, image and macro dialogs to select pages and attachments.
  "../../webjars/xwiki-platform-tree-webjar/14.4/require-config.min.js"
], function($, ckeditor, xwikiMeta) {
  var deferred = $.Deferred();
  var currentLocale = $('html').attr('lang') || '';

  // We have to pass the plugin that makes the request (the initiator) because the expected response can be different
  // (e.g. between the filebrowser and filetools plugins).
  var getUploadURL = function(document, initiator) {
    return document.getURL('get', $.param({
      sheet: 'CKEditor.FileUploader',
      outputSyntax: 'plain',
      // The syntax and language are important especially when the upload request creates a new document.
      syntax: document.syntax,
      language: currentLocale,
      form_token: xwikiMeta.form_token,
      initiator: initiator
    }));
  };

  // Extend the default CKEditor configuration with settings that depend on the source document.
  var getConfig = function(element) {
    var sourceDocument = XWiki.currentDocument;
    var sourceDocumentReference = $(element).attr('data-sourceDocumentReference');
    if (sourceDocumentReference) {
      sourceDocument = new XWiki.Document(XWiki.Model.resolve(sourceDocumentReference, XWiki.EntityType.DOCUMENT));
    }
    sourceDocument.syntax = $(element).attr('data-sourceDocumentSyntax');

    var config = {
      filebrowserUploadUrl: getUploadURL(sourceDocument, 'filebrowser'),
      height: $(element).height(),
      mentions: [
        {
          // We use the source document to compute the feed URL because we want the suggested link references to be
          // relative to the edited document (we want the editor to output relative references as much as possible).
          feed: sourceDocument.getURL('get', $.param({
            sheet: 'CKEditor.LinkSuggestions',
            outputSyntax: 'plain',
            language: currentLocale
          // Prevent the curly brackets from being URL encoded because they mark a placeholder that will be replaced
          // with the text typed by the user (and CKEditor takes care of URL encoding it).
          }) + '&input={encodedQuery}'),
          itemTemplate: [
            '<li data-id="{id}" class="ckeditor-autocomplete-item">',
              '<div>',
                '<span class="ckeditor-autocomplete-item-icon-wrapper">',
                  // We have to output both icon types but normally only one is defined and the other is hidden.
                  '<img src="{iconURL}"/>',
                  '<span class="{iconClass}"></span>',
                '</span>',
                '<span class="ckeditor-autocomplete-item-label">{label}</span>',
              '</div>',
              '<div class="ckeditor-autocomplete-item-hint">{hint}</div>',
            '</li>'].join(''),
          outputTemplate: '<a href="{url}" data-reference="{typed}|-|{type}|-|{reference}">{label}</a><span>&nbsp;</span>',
          marker: '[',
          minChars: 0,
          itemsLimit: 6
        }
      ],
      sourceDocument: sourceDocument,
      uploadUrl: getUploadURL(sourceDocument, 'filetools'),
      'xwiki-link': {
        // We use the source document to compute the link label generator URL because we want the link references to be
        // resolved relative to the edited document (as they were inserted).
        labelGenerator: sourceDocument.getURL('get', $.param({
          sheet: 'CKEditor.LinkLabelGenerator',
          outputSyntax: 'plain',
          language: currentLocale
        }))
      }
    };
    return config;
  };

  var oldReplace = ckeditor.replace;
  ckeditor.replace = function(element, config) {
    // Take into account the configuration options specified on the target element.
    return oldReplace.call(this, element, ckeditor.tools.extend(getConfig(element), config, true));
  };

  var oldInline = ckeditor.inline;
  ckeditor.inline = function(element, config) {
    // Take into account the configuration options specified on the target element.
    return oldInline.call(this, element, ckeditor.tools.extend(getConfig(element), config, true));
  };

  //
  // Overwrite in order to add support for configuration namespaces.
  //
  ckeditor.tools.extend = function(target) {
    var argsLength = arguments.length, overwrite, propertiesList;
    if (typeof (overwrite = arguments[argsLength - 1]) === 'boolean') {
      argsLength--;
    } else if (typeof (overwrite = arguments[argsLength - 2]) === 'boolean') {
      propertiesList = arguments[argsLength - 1];
      argsLength -= 2;
    }
    for (var i = 1; i < argsLength; i++) {
      var source = arguments[i] || {};
      ckeditor.tools.array.forEach(ckeditor.tools.object.keys(source), function(propertyName) {
        // Only copy existing fields if in overwrite mode.
        if (overwrite === true || target[propertyName] == null) {
          // Only copy specified fields if list is provided.
          if (!propertiesList || (propertyName in propertiesList)) {
            // NOTE: This is the only part we overwrite.
            setObjectProperty(target, propertyName, source[propertyName]);
          }
        }
      });
    }
    return target;
  };

  var setObjectProperty = function(object, key, value) {
    var oldValue = object[key];
    var newValue = value;
    // Merge the old value with the new value if both are objects and the old value is a configuration namespace.
    if ($.isPlainObject(oldValue) && oldValue.__namespace === true && $.isPlainObject(newValue)) {
      // We don't modify directly the old value because it may be inherited (e.g. global configuration).
      newValue = ckeditor.tools.extend({}, oldValue, newValue, true);
    }
    object[key] = newValue;
  };

  require([
    // We cannot add these modules to the list of dependencies because they need the tree module to be configured first.
    'entityResourcePicker', 'entityResourceSuggester', 'entityResourceDisplayer',
    // Load the translations for our custom CKEditor plugins. We didn't add this as a dependency to the xwiki-ckeditor
    // module because some of our plugins load their dependencies with RequireJS and thus they are loaded with a small
    // delay. The plugin needs to be defined before calling CKEDITOR.plugins.setLang().
    new XWiki.Document('Translations', 'CKEditor').getURL('jsx', $.param({
      language: currentLocale
    }))
  ], function() {
    deferred.resolve(ckeditor);
  });

  return deferred.promise();
});
// Fix CKEDITOR.tools.escapeCss() for browsers that don't support CSS.escape()
// See https://github.com/ckeditor/ckeditor4/issues/681
require(['jquery', 'ckeditor'], function($, ckeditor) {
  // Test if CKEDITOR.tools.escapeCss() works as expected.
  if (ckeditor.tools.escapeCss('.') === '.') {
    // Special CSS characters were not escaped so we need to fix this.
    // Use jQuery's escapeSelector() if available (only since jQuery 3.0), otherwise use some naive implementation.
    ckeditor.tools.escapeCss = $.escapeSelector || function(selector) {
      if (typeof selector === 'string') {
        // Simple implementation.
        // See https://learn.jquery.com/using-jquery-core/faq/how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation/
        return selector.replace(/(:|\.|\[|\]|,|=|@)/g, '\\$1');
      } else {
        return selector;
      }
    };
  }
});

// Fix the path to the arrow icon that indicates the presence of a sub-menu in the context menu.
if (CKEDITOR_BASEPATH.indexOf('?') >= 0) {
  // The WebJar resource path was specified in the query string before 7.1M1 which prevented the browser from resolving
  // relative paths. See XWIKI-10880 (A CSS file inside a webjar cannot use a resource from that webjar).
  require(['ckeditor'], function(ckeditor) {
    ckeditor.on('instanceReady', function(event) {
      if (event.editor.contextMenu) {
        event.editor.contextMenu._.panelDefinition.css.push(
          '.cke_menuarrow {' +
            'background-image: url("' + CKEDITOR_BASEPATH + 'skins/moono-lisa/images/arrow.png") !important;' +
          '}'
        );
      }
    });
  });
}

// Polyfill for the xwiki:actions:beforeSave and xwiki:actions:beforePreview events (available since 7.4.1)
// We have to use Prototype.js because it is loaded before jQuery and we need to register our save and preview listeners
// before the actionButtons.js does it, as otherwise the CKEditor doesn't get the chance to update the text area before
// the form is submitted.
(function() {
  var submitActions = ['save', 'preview'];
  var beforeSubmitWasTriggered = false;
  var onBeforeSubmit = function(event) {
    beforeSubmitWasTriggered = true;
    if (!(event.memo || {}).polyfill) {
      removeListeners();
    }
  };
  var onSubmit = function(event) {
    if (!beforeSubmitWasTriggered && jQuery) {
      // Our CKEditor plugins use jQuery to listen to events.
      var parts = event.eventName.split(':');
      var data = event.memo || {};
      data.polyfill = true;
      jQuery(document).triggerHandler(getBeforeEventName(parts[parts.length - 1]), data);
    }
    beforeSubmitWasTriggered = false;
  };
  var getBeforeEventName = function(action) {
    return 'xwiki:actions:before' + action.substr(0, 1).toUpperCase() + action.substr(1);
  };
  var removeListeners = function() {
    submitActions.forEach(function(action) {
      document.stopObserving(getBeforeEventName(action), onBeforeSubmit);
      document.stopObserving('xwiki:actions:' + action, onSubmit);
    });
  };
  if (typeof document.observe == 'function') {
    submitActions.forEach(function(action) {
      document.observe(getBeforeEventName(action), onBeforeSubmit);
      document.observe('xwiki:actions:' + action, onSubmit);
    });
  }
})();

// Make sure the "Back To Edit" button from the Preview mode works as expected on XWiki versions older than 8.2 (where
// CKEditor is not the default editor).
require(['jquery'], function($) {
  if (/&sheet=CKEditor.EditSheet\b/.test(window.location.href)) {
    // Make sure the CKEditor.EditSheet is preserved when coming back from Preview mode.
    $('form#inline').find('input[name="xcontinue"]').val(function(index, oldValue) {
      return oldValue + '&sheet=CKEditor.EditSheet';
    });
  }
});

// Polyfill for entityReference.js
require(['jquery'], function($) {
  if (typeof XWiki.EntityType.byName !== 'function') {
    // Before 6.4.1
    var entityTypeByName = {
      wiki: XWiki.EntityType.WIKI,
      space: XWiki.EntityType.SPACE,
      document: XWiki.EntityType.DOCUMENT,
      attachment: XWiki.EntityType.ATTACHMENT
    };
    XWiki.EntityType.byName = function(name) {
      return entityTypeByName[name];
    };
  }

  if (typeof XWiki.EntityType.getName !== 'function') {
    // Before 6.4.1
    var entityTypes = ['wiki', 'space', 'document', 'attachment'];
    XWiki.EntityType.getName = function(entityType) {
      return entityTypes[entityType];
    }
  }

  if (typeof XWiki.EntityReference.prototype.getReversedReferenceChain !== 'function') {
    // Before 7.2M2
    XWiki.EntityReference.prototype.getReversedReferenceChain = function() {
      return this._extractComponents().reverse();
    };
  }

  if (typeof XWiki.currentDocument.getDocumentReference !== 'function') {
    // Before 7.2M3
    // Take the current document full name.
    var currentDocumentReference = $('meta[name="document"]').attr('content');
    // Resolve the local reference.
    currentDocumentReference = XWiki.Model.resolve(currentDocumentReference, XWiki.EntityType.DOCUMENT);
    // Add the wiki component.
    currentDocumentReference.getReversedReferenceChain()[0].parent = new XWiki.WikiReference(XWiki.currentWiki);
    XWiki.currentDocument.getDocumentReference = function() {
      return currentDocumentReference;
    };
  }

  var reference = XWiki.Model.resolve('S', XWiki.EntityType.SPACE, new XWiki.WikiReference('W'));
  if (!reference.parent) {
    // The resolve method did not support a default value provider before 7.2M1.
    var oldResolve = XWiki.Model.resolve;
    XWiki.Model.resolve = function(representation, entityType, defaultValueProvider) {
      var reference = oldResolve.apply(this, arguments);
      if (reference && defaultValueProvider && typeof defaultValueProvider.extractReference === 'function') {
        // The given default value provider is an entity reference (normally a document reference).
        var root = reference.getReversedReferenceChain()[0];
        // We cover the document and attachment references mainly,
        var defaultRoot = defaultValueProvider.extractReference(root.type) || {parent: defaultValueProvider};
        // Nested spaces were introduced in 7.2M1 so we can safely assume that the given default value provider has only
        // one space reference component.
        root.parent = defaultRoot.parent;
      }
      return reference;
    };
  }
});

// Fix the layout to be consistent with the Wiki edit mode (tested with the Flamingo Skin).
require(['jquery'], function($) {
  var ckeEditMeta = $('.cke-editMeta');
  if (ckeEditMeta.length != 1) {
    // Fix the layout only when the CKEditor.EditSheet is applied.
    return;
  }
  // Hide the page title because the edit form has an input field to edit the title. This is consistent with the Wiki
  // edit mode. Remove the 'editMeta' id because the edit sheet adds an element with the same id.
  // NOTE: In order to rely on Chrome's Back-Forward cache we must not remove or move form elements. We can only hide.
  $('#document-title').parent('#editMeta').removeAttr('id').parent('.row').hide();
  // Move the content menu before the editMeta element so that they are displayed on the same row.
  ckeEditMeta.attr('id', 'editMeta').addClass('col-md-pull-5')
    .before($('#contentmenu').parent('.col-md-5').addClass('col-md-push-7'));
});
require(['jquery', 'xwiki-ckeditor', 'xwiki-events-bridge'], function($, ckeditorPromise) {
  // Make sure we don't create the editors twice because this file can be loaded twice (by RequireJS, for in-place
  // editing, and as a JSX resource, for standalone editing).
  if (ckeditorPromise.__editSheetLoaded) {
    return;
  }
  ckeditorPromise.__editSheetLoaded = true;

  var createEditors = function(ckeditor, container) {
    container.find('.ckeditor-textarea').each(function() {
      // Wrap in try/catch so that a failure to load one editor doesn't affect the other editors.
      try {
        createEditor(ckeditor, this);
      } catch(e) {
        console.log(e);
      }
    });
  };

  var createEditor = function(ckeditor, textArea, instanceConfig) {
    var deferred = $.Deferred();
    var editor = ckeditor.replace(textArea, instanceConfig);
    editor.once('instanceReady', deferred.resolve.bind(deferred, editor));
    editor.once('reload', function(event) {
      event.data.promise = event.data.promise.then(createEditor.bind(null, ckeditor, textArea));
    });
    return deferred.promise();
  };

  ckeditorPromise.done(function(ckeditor) {
    createEditors(ckeditor, $(body));
    // Make sure we don't register the event listener multiple times (in case this code is loaded multiple times).
    $(document).off('xwiki:dom:updated.ckeditor').on('xwiki:dom:updated.ckeditor', function(event, data) {
      createEditors(ckeditor, $(data.elements));
    });
  });
});
