window.Plum = (function() {
  var _config = {}, _storage = {}, _errorMessages = {
    'G2284': "A server error was encountered, we can't show you the items you've requested. Please wait a moment and try again - if the problem persists please let us know via the contact form below.",
    'G2290': "A server error was encountered, we can't show you the items you've requested. Please wait a moment and try again - if the problem persists please let us know via the contact form below.",
    'G2310': "A server error was encountered, we can't show you the outfits you've requested. Please wait a moment and try again - if the problem persists please let us know via the contact form below.",
    'G2319': "A server error was encountered, we can't show you the outfits you've requested. Please wait a moment and try again - if the problem persists please let us know via the contact form below.",
    'G2344': "A server error was encountered, we can't show you the items and outfits you've requested. Please wait a moment and try again - if the problem persists please let us know via the contact form below.",
    'G2353': "A server error was encountered, we can't show you the items and outfits you've requested. Please wait a moment and try again - if the problem persists please let us know via the contact form below."
  };

  function resolve(object, path, value) {
    var parts = path.split('.');
    var obj = object || {};
    for (var i = 0; i < parts.length; i++) {
      var p = parts[i], next = (i + 1) == parts.length;
      if (obj[parts[i]] !== undefined) {
        if (next) {
          if (arguments.length == 2) {
            return obj[parts[i]];
          }
          obj[parts[i]] = value;
          return object;
        } else if (typeof obj[parts[i]] != 'object' && arguments.length == 3) {
          // Re-initialize our chain if we're not an object and we're a setter
          // and we have an existing key
          obj[parts[i]] = {};
        }
        obj = obj[parts[i]];
      } else if (arguments.length == 3) {
        // Initial our chain if it doesn't already exist
        obj[parts[i]] = {};
        if (next) {
          obj[parts[i]] = value;
          return object;
        }
        obj = obj[parts[i]];
      }
    }
    return undefined;
  }

  var _publishNesting = 0,
      _publishTracking = 0;

  return {
    config: function(k, v) {
      if (arguments.length == 2) {
        _config[k] = v;
        this.publish('config.set', { key: k, value: v });
      } else {
        return _config[k];
      }
    },
    debugMode: false,
    alert: function(message) {
      if (Plum.debugMode) {
        Plum.message('Debug message', message);
      }
    },
    htmlentities: function(str) {
      return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
    },
    storage: {
      set: function(scope, k, v) {
        if (arguments.length == 2) {
          v = k,
          k = scope;
          scope = 'global';
        }
        if (!_storage[scope]) {
          _storage[scope] = {};
        }
        _storage[scope] = resolve(_storage[scope], k, v);
      },
      get: function(scope, k) {
        if (arguments.length == 1) {
          k = scope;
          scope = 'global';
        }
        return resolve(_storage[scope], k);
      }
    },
    ajax: function(url, data, fn) {
      var _this = this;
      $.ajax({
        url: url,
        type: 'POST',
        data: data,
        dataType: 'json',
        success: function(json) {
          // A valid formatted response was returned from the server
          if (json && json.status == 'success') {
            fn.call({}, json.data);
          } else if (json.status == 'error') {
            //    MessageBus.publish('', {  });
          }
        },
        error: function(xhr, status, error) {
          // A network/connection/server level error occurred
          _this.publish('connection.failure', { message: 'Server connection failure' });
        }
      });
    },
    handler: function() {
      return MessageBus.handler.apply(MessageBus, arguments);
    },
    request: function() {
      return MessageBus.request.apply(MessageBus, arguments);
    },
    publish: function() {
      if (!_publishNesting++ && _publishTracking) {
        var obj = arguments[1];
        var saveObj;

        //Workaround for ui.* messages having html elements
        if (typeof obj == 'object' && typeof obj.element !== 'undefined') {
          saveObj = $.extend({}, obj);

          //Try and find a unique instance of the referenced item
          var elt = saveObj.element;
          var ref = '';
          if ($(elt).attr('id')) {
            ref = '#' + $(elt).attr('id');
          } else {
            //cannot find a unique element
            saveObj = false;
          }

          if (ref) {
            saveObj = "{ element: jQuery('" + ref + "')[0] }";
          }
        } else {
          saveObj = JSON.stringify(obj);
        }

        if (saveObj) {
          logClicktale("Plum.publish('" + arguments[0] + "', " + saveObj + ')');
        }
      }

      var result = MessageBus.publish.apply(MessageBus, arguments);

      _publishNesting--;

      return result;
    },
    subscribe: function() {
      return MessageBus.subscribe.apply(MessageBus, arguments);
    },
    publishSetTracking: function(track) {
      _publishTracking = track;
    },
    imageTypes: {
      i: 'item', o: 'outfit', p: 'picture'
    },
    imageSize: 250,
    realImageSize: 180, // we reduce the image size in HTML
    rowsOfResults: 4,
    fullsizeNav: true,
    item: {
      cache: {},
      get: function(id, fn) {
        // TODO: Ajax request for data
      },
      image: function(id, size) {
        var s = {
          orig: 2000,
          large: 550,
                    medium: 250,
          thumb: 100
          // not used: medium: 250, micro: 50
                };
        var idParts = id.split('-');
        return Plum.config(Plum.imageTypes[idParts[0]] + '_base_url') + id + '-' + (s[size] ? s[size] : '50');
      },
      /**
       * @deprecate? use getId 
       */
      idFromUrl: function(url) {
        var match = url.match(/(\w(-\w+)+)-\d+/);
        return match[1] || '';
      },
      /**
       * @deprecate? use getId 
       */
      idFromClass: function(className) {
        var match;

          if (!className) {
              return '';
          }
          match = className.match(/data-[a-z]+id-([^\s]+)/);
          if (!match) {
              return '';
          }
          return match[1] || '';
      },
      idToClass: function(id) {
        var classType = id[0] == 'i' ? 'item' : 'pic';
        return 'data-' + classType + 'id-' + id;
      }
    },
    closetURL: function(id, name) {
      if (name instanceof Array) {
        name = name[0];
      }
      if (!name) {
        return '/closet/' + id;
      }
      name = $.trim(name);
      var nameEncoded = encodeURIComponent(name.replace('/[^\w\p{L}\d]+/gu', '-').replace(/[ \t\r\n]/g, '-').replace(/-+/, '-'));
      return '/' + (nameEncoded ? nameEncoded : (id[0] == 'o' ? 'outfit' : 'item')) + '/closet-' + id;
    },
    ui: {
      /**
         * Don't add new calls to this method - it's not helpful
         *
         * @deprecated
         */
      alert: function(title, message, fn, options) {
        logClicktale('Plum.ui.alert("' + title + '","' + message + '")');
        var modal = $('<div id="canvasErrorContainer"></div>')
                    .html(message)
                    .appendTo('body')
                    .dialog({
                        title: title,
                        resizable: false,
                        width: 300,
                        modal: false,
                        buttons: {
                            'ok' : function() {
                                $(this).dialog('close');
                                if ($.isFunction(fn)) {
                                    fn();
                                }
                            }
                        },
                        close: function() {
                            logClicktale("jQuery('#canvasErrorContainer').dialog('close')");
                            $(this).remove();
                        }
                    });

        if (typeof options === 'object' && options['class']) {
            modal.parent().addClass(options['class']);
        }

        modal.parent().attr('id', 'canvasErrorDialog');
        return modal;
      },
      /**
         * Don't add new calls to this method - it's not helpful
         *
         * @deprecated
         */
      confirm: function(title, message, ok, confirmid) {
        if (!confirmid) {
          confirmid = (isFirstTimeUser()) ? 'canvasFirstTimeDialog' : 'canvasErrorDialog';
        }
        var exists = $('#' + confirmid);
        if (exists.length) {
          exists.show();
          return;
        }

        logClicktale('Plum.ui.confirm("' + title + '","' + message + '")');
        var buttons = {
          'cancel' : function() {
            $(this).dialog('close');
          },
          'sign in' : function() {
            $(this).dialog('close');
            if ($.isFunction(ok)) {
                            ok();
            }
          },
          'sign up' : function() {
            $(this).dialog('close');
            if ($.isFunction(ok)) {
                            ok();
            }
          }
        };

        $('<div></div>')
                    .html(message)
                    .appendTo('body')
                    .dialog({
                        title: title,
                        resizable: false,
                        width: 300,
                        modal: false,
                        buttons: buttons,
                        zIndex: 10000,
                        close: function() {
                $(this).remove();
                        }
                    }).parent().attr('id', confirmid);
        if (confirmid && confirmid == 'canvasFirstTimeDialog') {
          var winH = $(window).height();
          var winW = $(window).width();
          var pop = $('#' + confirmid);
          var oW = pop.width();
          var oH = pop.height();
          pop.css({
            'top': ((winH / 2) - (oH / 2)),
            'left': ((winW / 2) - (oW / 2))
          });
        }
      }
    },
    logError: function(data) {
      $.ajax({
        url: '/error',
        type: 'POST',
        dataType: 'json',
        data: data
      });
    },
    searchTimeout: function(el) {
      if (el.hasClass('preloader')) {
        el.removeClass('preloader');
      }
    },
    /**
         * Give the user a message.
         *
         * Lazy loads jquery gritter, could use another implementation
         */
    message: function(title, text, options) {
      var modal, parent, defaults;

      if (typeof title === 'object') {
        options = title;
      } else if (typeof text === 'object' && (typeof text[0] !== 'object' || !text[0].attributes)) {
        options = text;
        if (!options.text) {
          options.title = 'System Message';
          options.text = title;
        } else {
          options.title = title;
        }
      } else {
        options = options || {};
        if (!text) {
          text = title;
          title = 'System Message';
        }
        options.title = title;
        options.text = text;
      }

      if (options.notice) {
        if (!$.gritter) {
          this.loadCss('/css/jquery.gritter.css');
          this.loadJs('/js/lib/jquery.gritter.js', function() {
            if (title) {
              $.gritter.add(options);
            }
          });
          return;
        }
        return $.gritter.add(options);
      }

      if (options.block) {
        defaults = {
          title: 'A message',
          resizable: false,
          modal: true,
          closeOnEscape: false,
          dialogbeforeclose: false,
          buttons: {
            cancel: function() {
              document.location = '/';
            }
          }
        };
      } else if (options.modal) {
        defaults = {
          title: 'A message',
          resizable: false,
          modal: true
        };
      } else {
        defaults = {title: 'A message'};
      }

      options = $.extend(defaults, options);
      modal = $('<div></div>')
                         .html(options.text)
                         .dialog(options);

      parent = modal.parent();

      if (options.id) {
        parent.attr('id', options.id);
      }

      if (options['class']) {
        parent.addClass(options['class']);
      }

      if (options.block) {
        parent.find('a.ui-dialog-titlebar-close').remove();
      }
      return modal;
    },
    /**
     * Give the user a message which cycles.
     *
     * Generates a dialog using the first message and the options array, and loops on the messages
     * until there are no more messages defined. If there is a function defined it is called before
     * the next message, if any.
     *
     * Example:
     *  Plum.multiMessage([
     *    [10000, "original title", "display for 10s"],
     *    [1000, "new title", "blink and you miss me, will auto close in 1s", function() { this.dialog('close'); }]
     *  ], {id: "example"});
     *
     */
    multiMessage: function(messages, options) {
      var message = messages.shift(),
          dialog = Plum.message(message[1], message[2], options),
                      parent = dialog.parent(),
                      updateMessage;

                  updateMessage = function(messages) {
                    message = messages.shift();
                    parent.find('.ui-dialog-title').text(message[1]);
                    parent.find('.ui-dialog-content').text(message[2]);

                    if (message[0]) {
                        if (message[3]) {
                            setTimeout(function() { message[3].call(dialog); }, message[0]);
                        }
                        if (messages.length) {
                            setTimeout(function() { updateMessage(messages); }, message[0]);
                        }
                    } else if (message[3]) {
                        message[3].call(dialog);
        }
                  }
                    setTimeout(function() {updateMessage(messages);}, message[0]);
                    return dialog;
                  },
    error: function(ref) {
      message = _errorMessages[ref] || 'Unknown Error!';
      Plum.message('Ooops!', _errorMessages[ref] + ' The reference for this error is ' + ref + '.');
    },
    url: function(url) {
      if (PW.minify && url.indexOf('.min.') === -1) {
        if (url.charAt(0) === '/') {
          url = '/' + PW.version + url;
        }
        return url
            .replace(/\.css$/, '.min.css')
            .replace(/\.js$/, '.min.js');
      }
      if (url.indexOf('?')) {
          return url;
      }
      return url + '?' + (new Date().getTime());
    },
    loadCss: function(url) {
      var port = '';

      url = this.url(url);

      if (location.port && location.port != 80) {
        port = ':' + location.port;
      }
      if (url.charAt(0) === '/') {
        url = 'http://' + location.hostname + port + url;
      }
      $('head')
                .append('<link rel="stylesheet" type="text/css" href= "' + url + '">');
    },
    /**
     * Add a script to the page, no dependencies.
     */
    addJs: function(url) {
        var a, s, i;

        if (typeof url === 'object') {
            for (i in url) {
                this.addJs(url[i]);
            }
            return;
        }

        a = document.createElement('script');
        a.type = 'text/javascript';
        a.async = true;
        a.src = url;

        s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(a, s);
    },
    /**
     * Load a script with an optional callback when the script has loaded. Uses/Requires jquery
     */
    loadJs: function(url, cb) {
      $.ajax({
        url: this.url(url),
        dataType: 'script',
        cache: true,
        success: cb
      });
    },
    /**
     * For the given html element, what is the item/outfit id
     */
    getId: (function(input) {
      var urlPattern = /([ip]-\d+)/,
          classPattern = new RegExp('data-[a-z]+id-([ip]-\\d+)');

      return function(input) {
        var match;
        if (typeof input === 'object' && input[0]) {
          input = input[0];
        }

        if (input.src) {
          match = input.src.match(urlPattern);
          if (match) {
            return match[1];
          }
        }

        if (input.className) {
          match = input.className.match(classPattern);
          if (match) {
            return match[1];
          }
        }
      };
    }())
  };
})();


