define(['jquery', 'xwiki-meta'], function($, xm) {

  /**
   * Globals
   */
   
  // There could be only one picker at a time
  var xwikiCurrentIconsPicker = 0;
  
  // Map that will contains the icons sorted by icon themes
  var icons = {};
  
  // List of icon themes
  var iconThemes = [];
  
  // Current icon theme used for the preview
  var currentIconTheme = '';
  
  // Current input field (there could be only one picker at a time)
  var currentInput = 0;
  
  // The spinner object to display when the picker is loading some data from the server
  var spinner = 0;
  
  // The HTML section where the icons are displayed
  var iconListSection = 0;
  
  // The HTML input to select the icon theme to use for the preview
  var iconThemeSelector = 0;
  
  // Picker Dimmensions
  var PICKER_WIDTH = 520;
  var PICKER_HEIGHT = 300;

  /**
   * Generate an URL for getting JSON resources about icons
   */
  var getResourceURL = function(action, parameters) {
    var param = 'outputSyntax=plain&action=' + action;
    if (parameters) {
      param += '&' + parameters;
    }
    return (new XWiki.Document('IconPicker', 'IconThemesCode', xm.wiki)).getURL('get', param);
  }
  
  /**
   * Close the picker
   */
  var closePicker = function() {
    xwikiCurrentIconsPicker.remove();
    xwikiCurrentIconsPicker = 0;
  }
  
  /**
   * Display the list of icons
   */
  var displayList = function(iconTheme) {
    // For each icon
    for (var i=0; i < iconTheme.length; ++i) {
      // Display the icon
      var displayer = $(document.createElement('div'));
      iconListSection.append(displayer);
      displayer.addClass('xwikiIconPickerIcon');
      var imageDiv = $(document.createElement('div'));
      imageDiv.addClass('xwikiIconPickerIconImage').html(iconTheme[i].render);
      displayer.append(imageDiv);
      var iconNameDiv = $(document.createElement('div'));
      iconNameDiv.addClass('xwikiIconPickerIconName').text(iconTheme[i].name);
      displayer.append(iconNameDiv);
      // Change the input value when the icon is clicked
      displayer.click(function(event) {
        currentInput.val(currentInput.data('xwikiIconPickerSettings').prefix + $(event.currentTarget).children('.xwikiIconPickerIconName').text() );
        closePicker();
      });
    }
  }
  
  /**
   * Load the icon list (get the JSON from the server) and display it afterwards
   */
  var loadIconList = function(iconTheme) {
    $.getJSON(getResourceURL('data_icons', 'iconTheme='+iconTheme), function(dataIcons) {
      // Put the result in the icons map
      icons[iconTheme] = dataIcons;
      // Display the list
      displayList(icons[iconTheme]);
      // Hide the spinner
      spinner.hide();
    });
  }
  
  /**
   * Fill the icon theme selector with the list of icon themes
   */
  var fillIconThemesSelector = function() {
    // Create the icon theme selector
    for (var i = 0; i < iconThemes.length; ++i) {
      iconThemeSelector.append('<option>'+iconThemes[i]+'</option>');
    }
    // Select the icon theme
    iconThemeSelector.val(currentIconTheme);
  }
  
  /**
   * Load the list of all icon themes and display it in the icon themes selector afterwards
   */
  var loadIconThemes = function() {
    // Get the list of all icon themes
    $.getJSON(getResourceURL('data_iconthemes'), function(data) {
      // Save the result
      iconThemes = data.iconThemes;
      // Re-init the global icons map
      icons = {};
      // Fill the map where icon theme names are the keys
      for (var i = 0; i < iconThemes.length; ++i) {
        icons[iconThemes[i]] = [];
      }
      // The first icon theme is the current one (hack)
      currentIconTheme = data.currentIconTheme;
      // Get the icons of the current icon theme
      loadIconList(currentIconTheme);
      // Fill the selector
      fillIconThemesSelector();
    });
  }
  
  /**
   * Create the icon theme selector
   */
  var createIconThemeSelector = function() {
    iconThemeSelector = $(document.createElement('select'));
    // Change the current icon theme where the selector is used
    iconThemeSelector.change(function() {
      currentIconTheme = iconThemeSelector.val();
      // Remove all the displayed icons
      $('.xwikiIconPickerIcon').remove();
      // Display the new ones
      if (icons[currentIconTheme].length == 0) {
        // if the icon theme has not already been loaded, load it
        spinner.show();
        loadIconList(currentIconTheme)
      } else {
        // just display what have been previously loaded
        displayList(icons[currentIconTheme]);
      }
    });
  }
  
  /**
   * Set the position of the picker and handle the current window scroll status
   */
  var setPickerPosition = function() {
    // If the picker does not exist, nothing to do
    if (xwikiCurrentIconsPicker == 0) {
      return;
    }
  
    var inputOffset = currentInput.offset();
    var inputWidth = currentInput.outerWidth();
    var inputHeight = currentInput.outerHeight();
    
    // Compute the left coordinate
    var left = inputOffset.left - $(window).scrollLeft();
    if (inputOffset.left + PICKER_WIDTH > $('body').outerWidth()) {
      // if there is not enough place to display the picker at the same position than the input,
      // then display it by using the right corner of the input for the right corner of the picker
      left = inputOffset.left + inputWidth - PICKER_WIDTH - $(window).scrollLeft();
      if (left < 0) {
        // if there is not enough place for this neither, center the picker
        left = $('body').outerWidth() / 2 - PICKER_WIDTH / 2;
      }
    }
    xwikiCurrentIconsPicker.css('left', left);
    
    // Compute the top coordinate
    var top = inputOffset.top + inputHeight - $(window).scrollTop();
    if (inputOffset.top + inputHeight + PICKER_HEIGHT > $('body').outerHeight()) {
      // if there is not enough place to display the picker just under the input,
      // then display it on the top
      top = inputOffset.top - PICKER_HEIGHT - $(window).scrollTop();
      if (top < 0) {
        // if there is not enough place for this neither, center the picker
        top = $('body').outerHeight() / 2 - PICKER_HEIGHT / 2;
      }
    }
    xwikiCurrentIconsPicker.css('top', top);
  }
  
  /**
   * Create the piccker
   */
  var createPicker = function (input) {
    // If the picker already exists
    if (xwikiCurrentIconsPicker != 0) {
      if (input != currentInput) {
        // if it has been created for an other input, close the previous one
        closePicker();
      } else {
        // the picker already exists for the current input, we do not go further
        return;
      }
    }
    // Save the input
    currentInput = input;
    // Create the picker
    xwikiCurrentIconsPicker = $(document.createElement('div'));
    xwikiCurrentIconsPicker.addClass('xwikiIconPickerContainer');
    // Insert the picker in the DOM
    $(body).append(xwikiCurrentIconsPicker);
    // Set the position
    setPickerPosition();
    // Add the icon list section
    iconListSection = $(document.createElement('div'));
    iconListSection.addClass('xwikiIconPickerList');
    xwikiCurrentIconsPicker.append(iconListSection);
    // Display the spinner correctly, while the icons are loading
    if (spinner == 0) {
      spinner = $(document.createElement('img'));
      spinner.attr('alt', 'Loading').attr('src', '../../../resources/icons/xwiki/spinner.gif');
    }
    iconListSection.append(spinner);
    // Add the icon theme selector section
    var iconThemeSelectorSection = $(document.createElement('div'));
    xwikiCurrentIconsPicker.append(iconThemeSelectorSection);
    iconThemeSelectorSection.addClass('xwikiIconPickerIconThemeSelector');
    iconThemeSelectorSection.text('Preview with:');
    // Create the icon theme selector
    createIconThemeSelector();
    iconThemeSelectorSection.append(iconThemeSelector);
    // Load the icon themes (if is not already loaded)
    if (iconThemes.length == 0) {
      loadIconThemes();
    } else {
      // Fill the selector
      fillIconThemesSelector();
      // Display icons
      if (icons[currentIconTheme].length > 0) {
        // display the icon theme since it has already be loaded
        displayList(icons[currentIconTheme]);
      } else {
        // load the icon theme
        loadIconList(currentIconTheme);
      }
    }
    // Close the picker when the user press 'escape'.
    currentInput.keyup(function (event) {
      // See: http://stackoverflow.com/questions/1160008/which-keycode-for-escape-key-with-jquery
      if (event.which == 27) {
        closePicker();
      }
    });
  }
  
  /**
   * Create the jQuery plugin
   */
  $.fn.xwikiIconPicker = function(options) {
    // Get the default settings if the options are not fully filled
    var settings = $.extend({
      prefix: 'icon:image:'
    }, options);
  
    // Enable the picker on focus
    this.each(function() {
      var elem = $(this);
      // Verify that the element is a valid input field
      if (!elem.is('input')) {
        return;
      }
      // Attach the settings to the input
      $(this).data('xwikiIconPickerSettings', settings);
      // Create the picker on focus
      $(this).focusin(function(event) {
        createPicker($(event.currentTarget));
      });
    });
    
    // Because the picker uses the 'fixed' position, we must recompute its position everytime the user scrolls the page
    // Otherwise, the picker would stay in the same position *on the screen* without staying close to the input.
    $(window).scroll(function() {
      setPickerPosition();
    });
    
    // Close the picker if the input and the picker itself have lost the focus
    $(window).click(function(event) {
      if (xwikiCurrentIconsPicker && document.activeElement != currentInput[0] &&
          !$.contains(xwikiCurrentIconsPicker[0], document.activeElement)) {
        closePicker();
      }
    });
    
    // Return this object to enable the jQuery chaining
    return this;
  }
});

