(function($){

$.extend({
  autocompletes: [],
  Autocomplete: {
    position: function($input, $container){
      var offset = $input.offset();
      $container.css({
        top: offset.top + $input.outerHeight(),
        left: offset.left
      });
    },

    positionAll: function(){
      $.each($.autocompletes, function(index){
        var data = $.autocompletes[index];
        $.Autocomplete.position(data.input, data.container);
      });
    },

    SOURCES: {
      sites: PATH_PREFIX + '/sites?prefix='
    },

    source: function(type){
      if($.Autocomplete.SOURCES[type]){
        return $.Autocomplete.SOURCES[type];
      } else {
        return PATH_PREFIX + '/' + type + '?prefix=';
      }
    }
  }
});

$.fn.extend({
  autocomplete: function (options){
    /**
     * options:
     *   type: A string that refers to a preset source in $.Autocomplete.SOURCES.
     *   source: The path to query for completions.
     *   params: A string of GET parameters to append to each query.
     *   delay: The duration that should pass between pressing a key and fetching
     *      completions.
     *   action: The callback function for when the user selects an item.
     *   actionLabel: The label to display as a link next to each item.
     *   id, class: Attributes of the completions container.
     *
     * Either type or source must be given.
     */
    if(!options){ return; }

    if(!options.source){
      if(options.type){
        options.source = $.Autocomplete.source(options.type);
      } else {
        return;
      }
    }

    options.delay = (typeof(options.delay) != 'undefined') ? options.delay : 400;
    options.params = options.params ? '&' + options.params : '';

    this.each(function(){
      var $input = $(this);
      var focused = false;
      $input.focus(function(){ focused = true; });
      $input.blur(function(){ focused = false; hideContainer(); });

      var $container = $('<div class="autocompletions"/>');
      $(document.body).append($container);

      if(options.id){ $container.attr('id', options.id); }
      if(options.klass){ $container.addClass(options.klass); }

      $.autocompletes.push({
        input: $input,
        container: $container
      });

      var lastTyped = '';
      var cache = {'': []};

      var labelTmpl = {
        list: '<table/>',
        row: '<tr/>',
        cell: '<td/>'
      };

      var noLabelTmpl = {
        list: '<ul/>',
        row: '<li/>'
      };

      var tmpl = options.actionLabel ? labelTmpl : noLabelTmpl;

      if(options.replace){
        var $to_replace = options.replace === true ? $input : $(options.replace);
      }

      if($to_replace && $to_replace.parent().find('.tile').length > 0){
        $(document).ready(function(){ $to_replace.hide(); });
        $to_replace.parent().find('.tile a.remove').click(unsetField);
      }

      function setField(evnt){
        $to_replace.hide();
        var $tile = $('<div>' + evnt.data.tile + '</div>').addClass('tile');
        $tile.find('a.remove').click(unsetField);
        $tile.insertAfter($to_replace);
        return false;
      }

      function unsetField(evnt){
        $(evnt.target).closest('.tile').remove();
        $to_replace.show();
        return false;
      }

      function action(evnt){
        $input.val('').keyup().focus();
        if(options.action){
          options.action(evnt);
        }
        if(options.replace){
          setField(evnt);
        }
        return false;
      }

      function showContainer(evnt){
        $container.fadeIn('slow');
        $(document).one('click', hideContainer);
        return false;
      }

      function hideContainer(evnt){
        $container.fadeOut('slow');
        $(document).unbind('click', hideContainer);
        return false;
      }

      function updater(query){
        return function(data){
          cache[query] = data;
          
          if(!data.length){
            hideContainer();
          } else if(focused) {
            showContainer();
          }

          var results_list = $(tmpl.list);

          var odd = true;
          $.map(data, function(item){
            var result = $(tmpl.row);
            result.addClass(odd ? 'odd' : 'even');
            odd = !odd;

            if(tmpl.cell){
              result.append($(tmpl.cell).addClass('name').html(item.html));

              if(options.actionLabel){
                action_link = $('<a href="#">' + options.actionLabel + '</a>');
                result.append($(tmpl.cell).append(action_link));
              }
            } else {
              result.addClass('name').html(item.html);
            }

            result.bind('click', item, action);
            results_list.append(result);
          });

          $.Autocomplete.position($input, $container);

          if(focused){
            $container.empty().append(results_list);
            $container.find('.name').css('min-width', $input.width());
          }
        };
      }

      $input.keyup(function(evnt){
        if(evnt.keyCode == 13 || evnt.charCode == 13){
          // ENTER
          hideContainer();
          return true;
        }

        var query = String($(evnt.target).val());
        lastTyped = String(query);

        if(query === ''){
          // Remove the completions immediately when the field is cleared.
          updater('')(cache['']);
        }

        window.setTimeout(function(){
          // If the text in the field has changed while we were waiting, don't try
          // to get completios.
          if(query != $(evnt.target).val()){ return; }

          if(cache[query]){
            updater(query)(cache[query]);
          } else {
            $.getJSON(options.source + $(evnt.target).val() + options.params,
              updater(query));
          }
        }, options.delay);
      });
    }); // this.each
  
  }
});

})(jQuery);


var Sites = {
  addSite: function(evnt){
    var site = evnt.data.site;
    var params = {
      'site[id]': site.id,
      'site[name]': site.name,
      'site[url]': site.url,
      'authenticity_token': AUTH_TOKEN
    };

    $.post('/sites', params, Sites.processResponse, 'json');

    return false;
  },

  processResponse: function(data, textStatus){
    if(textStatus == 'success'){
      if(data.status == 'added'){
        $.each(data.sites, function(i){
          var $tile = $(data.sites[i].html);
          $tile.prependTo('#subscribed-sites')
            .find('a.remove')
              .bind('click', {text: 'undo', tileClass: 'deleted'},
                Sites.toggleTileSubscription());
          Users.attachModals(
            $tile.closest('.site-tile').find('div.foreign-login'));
        });
        Sites.showQueryField();
      } else if(data.status == 'query' || data.status == 'error'){
        Sites.showNameEditor(data);
      }
    }
  },

  showNameEditor: function(data, textStatus, $form){
    $parent = $('#add-single-site').closest('td, div');
    $parent.width($parent.width());
    $parent.height($parent.height());
    $('#add-single-site, #edit-site').fadeOut('slow', function(){
      $('#edit-site').remove();
      $(this).find('input[type=text][name=site[url]]').val('').keyup();
      $(data.html)
        .insertAfter($('#add-single-site'))
        .hide()
        .find('form').ajaxForm({
          beforeSubmit: function(){
            $('#edit-site')
              .find('input[type=submit], input[type=button]').hide().end()
              .append('<span class="loading">Loading...</span>');
          },
          success: Sites.processResponse,
          dataType: 'json'
        }).end()
        .find('input[type=button][class=cancel]')
          .click(Sites.showQueryField).end()
        .fadeIn('slow');
      $parent.width('auto');
      $parent.height('auto');
    });
  },

  showQueryField: function(){
    $('#add-single-site')
      .find('input[type=submit], input[type=button]').show().end()
      .find('.loading').remove();
    $parent = $('#add-single-site').closest('td, div');
    $parent.width($parent.width());
    $parent.height($parent.height());
    $('#add-single-site, #edit-site').fadeOut('slow', function(){
      $('#edit-site').remove();
      $('#add-single-site').fadeIn('slow');
      $parent.width('auto');
      $parent.height('auto');
    });
  },

  querySubmit: function(){
    $(this).find('input[type=submit], input[type=button]').hide();
    $(this).append('<span class="loading">Loading...</span>');
    $(this).ajaxSubmit({
      success: Sites.showNameEditor,
      dataType: 'json'
    });
    return false;
  },

  toggleTileSubscription: function(){
    var originalLinkText = null;
    return function(evnt){
      $target = $(evnt.target);
      originalLinkText = originalLinkText ? originalLinkText : $target.text();
      $tile = $target.closest('.site.tile');
      
      $fLogin = $tile.find('.participates-as:not(.unset) .foreign-login');
      if($fLogin.length > 0){
        var params = '?username=' + /^\s*(.+?)\s*$/.exec($fLogin.text())[1];
      } else {
        var params = '';
      }

      var postData = {authenticity_token: AUTH_TOKEN};

      $.post($target.attr('href') + params, postData, function(data){
        if(data.status == 'success'){
          $target.attr('href', data.undo)
          $tile.toggleClass(evnt.data.tileClass);
          $target.text($tile.hasClass(evnt.data.tileClass) ?
            evnt.data.text : originalLinkText);
        }
      }, 'json');
      return false;
    };
  }
};

var Users = {
  hideEdit: function(evnt){
    $('#user-edit').remove();
    $('#user-show')
      .show()
      .find('.user-details').hide().fadeIn('slow');
    return false;
  },

  showEdit: function(evnt){
    $('#user-show').hide();
    $('#user-edit-template')
      .clone()
      .attr('id', 'user-edit')
      .find('.user-details').hide().end()
      .insertAfter('#user-show')
      .find('.user-details').fadeIn('slow');

    $('#user-edit form').submit(function(evnt){
      if(!$(this).find('input[type=file]').val()){
        $(this).ajaxSubmit({
          url: $('#user-edit form').attr('action') + '.json',
          success: Users.updatePage,
          dataType: 'json'
        });
        return false;
      }
    });
    $('#user-edit .cancel-edit').click(Users.hideEdit);
    return false;
  },

  // ajaxSubmit callback for user editing
  updatePage: function(data){
    if(data.status == 'success'){
      $('#user-show').html(data.replace_show);
      $('#user-edit-template').html(data.replace_edit);
      $('#user-show .edit-link a').click(Users.showEdit);
      Users.hideEdit();
    } else {
      $('#user-edit .error').remove();
      $list = $('<ul/>');
      $.each(data.errors, function(i){
        $list.append('<li>' + data.errors[i] + '</li>');
      });
      $errors = $('<p class="error"></p>');
      $errors.append($list);
      $errors.insertBefore('#user-edit form');
    }
  },

  toggleAddSites: (function(){ 
    var originalAddText = false;
    return function(evnt){
      if($('#add-site').css('display') == 'none'){
        if(!originalAddText){
          originalAddText = $('#add-sites-link').text();
        }
        $('#add-sites-link').text('[X] Close');
        $('#add-site').slideDown('slow', function(){
          $('#site_url').focus();
          $.Autocomplete.positionAll();
        });
      } else {
        $('#add-sites-link').text(originalAddText);
        $('#add-site').slideUp('slow');
      }
      return false;
    };
  })(),

  updateParticipatesAs: function($el){
    return function(data, textStatus){
      if(data && data.status === 'success'){
        $new = $(data.html);
        $el.replaceWith($new);
        $el = $new;
        $new.parent().closest('div')
          .find('div.foreign-login')
            .jqmAddTrigger($new.find('a.foreign-login'))
            .jqmHide()
            .find('.progress').remove().end()
            .find('input').show()
              .find('[name=subscription[username]]')
                .val(data.username);
      }
    };
  },

  attachModals: function($modals){
    $modals.each(function(){
      $parent = $(this).parent().closest('div');
      $(this).jqm({trigger: $parent.find('a.foreign-login')});
      $(this).find('form').ajaxForm({
        beforeSubmit: function(formData, $form, options){
          $form.find('input[type=button], input[type=submit]').hide();
          $form.append('<div class="progress">Saving...</div>');
        },
        success: Users.updateParticipatesAs($parent.find('.participates-as')),
        dataType: 'json'
      });
    });
  }
};

var Search = {
  addMetro: function(evnt){
    var $tile = $('<li>' + evnt.data.tile + '</li>').addClass('metro tile');
    $('#metros-list').append($tile.fadeIn());
    Search.attachTileEvents($tile);
  },

  addSite: function(evnt){
    var $tile = $('<li>' + evnt.data.tile + '</li>').addClass('site tile');
    $('#sites-list').append($tile.fadeIn());
    Search.attachTileEvents($tile);
  },

  removeTile: function(evnt){
    $tile = evnt.data;
    $tile.fadeOut(function(){ $(this).remove(); });
    return false;
  },

  attachTileEvents: function($tile){
    $tile.find('a.name').click(function(){ return false; });
    $tile.find('a.remove').bind('click', $tile, Search.removeTile);
  },

  addSuggestion: function(evnt){
    var $tile = $(evnt.target).closest('.tile'); 
    var url = $tile.find('a.name').attr('href') + '/tile';
    $.getJSON(url, function(data){
      $tile.fadeOut(function(){
        $tile.remove();
        if(!$('.suggestions .tile').length){
          $('.suggestions').slideUp(function(){ $(this).remove(); });
        }
      });
      $newTile = $('<li class="tile">' + data.tile + '</li>');
      Search.attachTileEvents($newTile);
      $('#metros-list').append($newTile.fadeIn());
    });
    return false;
  }
};


// Adapted from:
// <http://dren.ch/js/strftime.js>
// <http://whytheluckystiff.net/ruby/strftime.js>
var DateTools = {
  pad: function (num, n,p) {
    var s = '' + num;
    p = p || '0';
    while (s.length < n) s = p + s;
    return s;
  },

  months: [
    'January', 'February', 'March', 'April', 'May', 'June', 'July',
    'August', 'September', 'October', 'November', 'December'
  ],

  weekdays: [
    'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
  ],

  dpm: [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ],

  formatFuncs: {
    A: function (d) { return DateTools.weekdays[d.getDay()] },
    a: function (d) { return DateTools.weekdays[d.getDay()].substring(0,3); },
    B: function (d) { return DateTools.months[d.getMonth()] },
    b: function (d) { return DateTools.months[d.getMonth()].substring(0,3); },
    C: function (d) { return Math.floor(d.getFullYear()/100); },
    c: function (d) { return d.toString(); },
    D: function (d) {
      return DateTools.formatFuncs.m(d) + '/' +
        DateTools.formatFuncs.d(d) + '/' + DateTools.formatFuncs.y(d);
    },
    d: function (d) { return DateTools.pad(d.getDate(),2,'0'); },
    e: function (d) { return DateTools.pad(d.getDate(),2,' '); },
    F: function (d) {
      return DateTools.formatFuncs.Y(d) + '-' + DateTools.formatFuncs.m(d) + '-' +
        DateTools.formatFuncs.d(d);
    },
    H: function (d) { return DateTools.pad(d.getHours(),2,'0'); },
    I: function (d) { return DateTools.pad((d.getHours() % 12 || 12),2); },
    j: function (d) {
      var t = d.getDate();
      var m = d.getMonth() - 1;
      if (m > 1) {
        var y = d.getYear();
        if (((y % 100) == 0) && ((y % 400) == 0)) ++t;
        else if ((y % 4) == 0) ++t;
      }
      while (m > -1) t += DateTools.dpm[m--];
      return DateTools.pad(t,3,'0');
    },
    k: function (d) { return DateTools.pad(d.getHours(),2,' '); },
    l: function (d) { return DateTools.pad((d.getHours() % 12 || 12),2,' '); },
    M: function (d) { return DateTools.pad(d.getMinutes(),2,'0'); },
    m: function (d) { return DateTools.pad((d.getMonth()+1),2,'0'); },
    n: function (d) { return "\n" },
    p: function (d) { return (d.getHours() > 11) ? 'PM' : 'AM' },
    R: function (d) {
      return DateTools.formatFuncs.H(d) + ':' + DateTools.formatFuncs.M(d);
    },
    r: function (d) {
      return DateTools.formatFuncs.I(d) + ':' + DateTools.formatFuncs.M(d) + ':' +
        DateTools.formatFuncs.S(d) + ' ' + DateTools.formatFuncs.p(d);
    },
    S: function (d) { return DateTools.pad(d.getSeconds(),2,'0'); },
    s: function (d) { return Math.floor(d.getTime()/1000); },
    T: function (d) {
      return DateTools.formatFuncs.H(d) + ':' + DateTools.formatFuncs.M(d) + ':' +
          DateTools.formatFuncs.S(d);
    },
    t: function (d) { return "\t"; },
    u: function (d) { return(d.getDay() || 7); },
    v: function (d) {
      return DateTools.formatFuncs.e(d) + '-' + DateTools.formatFuncs.b(d) + '-' +
        DateTools.formatFuncs.Y(d);
    },
    w: function (d) { return d.getDay(); },
    X: function (d) { return d.toTimeString(); }, // wrong?
    x: function (d) { return d.toDateString(); }, // wrong?
    Y: function (d) { return d.getFullYear(); },
    y: function (d) { return DateTools.pad((d.getYear() % 100),2); },
    '%': function (d) { return '%'; }
  },

  classFormats: {
    _default: {recent: '%B %e, %l:%M %p', old: '%B %e, %Y, %l:%M %p'},
    at: {recent: '%B %e at %l:%M %p', old: '%B %e, %Y at %l:%M %p'}
  },

  strftime: function (dt, fmt){
    if(!fmt){
      fmt = DateTools.classFormats['_default'];
    } else if(fmt.indexOf('%') === -1){
      fmt = DateTools.classFormats[fmt];
    }

    if(typeof(fmt) == 'object'){
      if(dt.getYear() == (new Date()).getYear()){
        fmt = fmt.recent;
      } else {
        fmt = fmt.old;
      }
    }

    var funcs = DateTools.formatFuncs;
    for(var s in funcs){
      if(funcs.hasOwnProperty(s) && s.length == 1){
        fmt = fmt.replace('%' + s, funcs[s](dt));
      }
    }
    return fmt;
  },

  /**
   * The contents of elements with a class of "ts-{$utc_timestamp}" will be replaced
   * with the formatted local time. To specify a pattern, choose a name to
   * identify the pattern by, add the pattern to DateTools.classFormats with your
   * chosen name, and add the class "fmt-{$format_name}" to the element.
   */
  replaceTimes: function(){
    $('[class*=ts-]').each(function(){
      var classes = $(this).attr('class').split(' ');
      var ts = null;
      var fmt = null;
      $.each(classes, function(i){
        var match = /^ts-(.+)$/.exec(classes[i]);
        if(match){
          ts = parseInt(match[1]) * 1000;
        } else {
          match = /^fmt-(.+)$/.exec(classes[i]);
          if(match){
            fmt = match[1];
          }
        }
      });

      if(ts !== null){
        $(this).html(DateTools.strftime(new Date(ts), fmt));
      }
    });
  }
};

