/*
to do:

ESSENTIAL
--alphebetize
--abstract out necessary CSS classnames into defaults
--test everywhere
--comment
--get rid of duplicate ids

NICE
--make scrollbars always appear on scrollboxes
--better factoring of parallel functions
--better factoring of template helpers

*/

(function($) {

  //add some additional template helpers
  //for the template plugin;
  //use these to format our markup
  $.extend($.template.helpers, {
    addConditionalClass: function(value, trueClass, falseClass) {
      return value === true ? trueClass : falseClass;
    }
    , addIfTrue: function(value, conditionalText) {
      return value === true ? conditionalText : '';
    }
    , addIfFalse: function(value, conditionalText) {
      return value === false ? conditionalText : '';
    }
    , pluralize: function(value, label) {
      return value + " " + label + (value != 1 ? "s" : "");
    }
    , count: function(value, label) {
      var r = value.length || 0;
      if (label) {
        r = $.template.helpers.pluralize.call(this, r, label);
      }
      return r;
    }
  });
  
  //a reusable class to add
  //a classic Web 2.0 yellow fade
  //to DOM elements that have changed
  SignalChange = $.klass({
    defaults: {
      duration: 1250//length of the fadeout
      , defaultBorderColor: "#ccc"
      , defaultBackgroundColor: "#eee"
      , highlightBorderColor: "#000"
      , highlightBackgroundColor: "#ffa"
    }
    , initialize: function(options) {
      var opts = $.extend({}, this.defaults, options);
      $.extend(this, opts);
    }
    , onredraw: function() {
      var that = this;
      this.element
        .stop("onredraw")//cancel previous events of this type
        .css({
          backgroundColor: that.highlightBackgroundColor
          , borderColor: that.highlightBorderColor
        })
        .animate({
          backgroundColor: that.defaultBackgroundColor
          , borderColor: that.defaultBorderColor
        }, this.duration)
      ;
    }
  });

  //main workhorse class
  //knits together all the interactions
  Subscriptions = $.klass({
    defaults: {
      rightArrow: '&raquo;'
      , leftArrow: '&laquo;'
      , formSelector: 'form:eq(0)'
    }
    , initialize: function(options) {
      
      var opts = $.extend({}, this.defaults, options);
      var self = this;

      //grab relevant DOM nodes
      this.ineligibleNode = this.element.find('#groupsOff .scrollbox');
      this.eligibleNode = this.element.find('#groupsOn .scrollbox');
      this.unsubscribedNode = this.element.find('#membersOff .scrollbox');
      this.subscribedNode = this.element.find('#membersOn .scrollbox');
      this.submitForm = $(opts.formSelector);

      //map and extend data collections
      this.groups = $.map(opts.data.groups, function(group) {
        return $.extend({}, {eligibleNow: group.eligibleAtLoad}, group);
      });
      this.members = $.map(opts.data.members, function(member) {
        return $.extend({}, {
          subscribedNow: member.subscribedAtLoad
          , groupCount: self.getGroupCount(member.id)
        }, member);
      });

      //build event handlers using livequery
    
      //TODO: define this as a reusable behavior outside the main
      //class, then call it directly instead of via this wrapper
      
      //handle click events that add or remove eligibility/subscription
      var handleClicks = function(selector,filter,prop,val) {
        //grab the dom node that will trigger the event
        var $el = $(selector);

        //bind behavior using livequery
        $el.livequery('click', function(event) {
          var $this = $(this);

          //figure out the id of this group or member
          var id = $this.parents('.item').attr("id");
          
          //filter our collection down to matching elements
          var set = self[filter].call(self, id);

          //reset the value
          set[prop] = val;
          
          //redraw the dom nodes
          self.redrawDOMNodes(function() {
            
            var $scrollboxes = self.element.find('.scrollbox');
            switch(selector) {
             case "#groups .addLink":
              $scrollboxes = $scrollboxes.filter(':eq(1),:eq(2)');
             break;
             case "#groups .removeLink":
              $scrollboxes = $scrollboxes.filter(':eq(0),:eq(2),:eq(3)');
             break;
             case "#members .addLink":
              $scrollboxes = $scrollboxes.filter(':eq(3)');
             break;
             case "#members .removeLink":
              $scrollboxes = $scrollboxes.filter(':eq(2)');
             break;
            }
            $scrollboxes.trigger('redraw');
          });
          
        });
      };
      
      //handle specific click types
      handleClicks('#groups .addLink', 'getGroupByID', 'eligibleNow', true);
      handleClicks('#groups .removeLink', 'getGroupByID', 'eligibleNow', false);
      handleClicks('#members .addLink', 'getMemberByID', 'subscribedNow', true);
      handleClicks('#members .removeLink', 'getMemberByID', 'subscribedNow', false);
      
      //handle form submission
      this.submitForm.livequery('submit', function(event) {
        self.setSerializedFormFieldValues();
      });
    
      //html templates for constructing checkboxes
      this.tmplGroupChooser = $.template(
        '<div class="item ${eligibleAtLoad:addConditionalClass(initOn,initOff)}" id="${id}">' +
        ' <div class="link addLink">${eligibleNow:addIfFalse(' + opts.rightArrow + ')}</div> ' +
        ' <div class="link removeLink">${eligibleNow:addIfTrue(' + opts.leftArrow + ')}</div> ' +
        '${name}' +
        ' <span class="link info seeMemberLink">(${members:count(member)})</span>' +
        '</div>'
      )
      this.tmplMemberChooser = $.template(
        '<div class="item ${subscribedAtLoad:addConditionalClass(initOn,initOff)}" id="${id}">' +
        ' <div class="link addLink">${subscribedNow:addIfFalse(' + opts.rightArrow + ')}</div> ' +
        ' <div class="link removeLink">${subscribedNow:addIfTrue(' + opts.leftArrow + ')}</div> ' +
        '${name}' +
        ' <span class="info">(${groupCount:pluralize(group)})</span>' +
        '</div>'
      )
      this.tmplMemberByGroupChooser = $.template(
        '<div id="members_group_${id}" class="set">' +
        '<h6>${name} (${members:count(member)})</h6>' +
        '${memberMarkup}' +
        '</div>'
      
      )
      this.tmplMemberViewWrapper = $.template(
        '<div class="set viewMembers">' + 
        '<div class="right link hideMemberLink">close</div>' + 
        '<h6>${group} (${members:count(member)})</h6>' +
        '${memberMarkup}' +
        '</div>'
      )
      this.tmplMemberView = $.template(
        '<div class="item">' +
        '${name}' +
        ' <span class="info">(${groupCount:pluralize(group)})</span>' +
        '</div>'
      )
      
      //set up our modal dialog
      $('#memberList').jqm({
        modal: false
      });
      
      //now bind the triggers to show and hide it
      $('.seeMemberLink').livequery('click', function(event) { 
        var groupID = $(this).parents('.item').attr("id");
        $('#memberList')
          .html(self.getMemberList(groupID))
          .jqmShow()
        ;
        return false; 
      });
      $('.hideMemberLink').livequery('click', function(event) {
        $('#memberList')
          .html("")
          .jqmHide()
        ;
      });
    


      //build the actual interface
      this.redrawDOMNodes();
      this.appendSerializedFormFields();

    }

    , redrawDOMNodes: function(callback) {
      //build the multiselect for groups
      this.buildIneligibleGroupChooser();
      this.buildEligibleGroupChooser();
    
      //build the multiselect for members
      this.buildUnsubscribedMemberChooser();
      this.buildSubscribedMemberChooser();
      
      if (callback) {
        callback.call(this);
      }
    }

    , getChooser: function(coll, template) {
      var Chooser = '';
      $.each(coll, function() {
        Chooser += template.apply(this);
      });
      return Chooser;
    }
    , buildChooser: function(el, coll, template) {
      el.html(this.getChooser.call(this, coll, template));
    }
    , buildIneligibleGroupChooser: function() {
      this.buildChooser.call(this, this.ineligibleNode, this.getIneligibleGroups(), this.tmplGroupChooser);
    }
    , buildEligibleGroupChooser: function() {
      this.buildChooser.call(this, this.eligibleNode, this.getEligibleGroups(), this.tmplGroupChooser);
    }

    , getMemberByGroupChooser: function(coll, test) {
      var self = this;

      var Chooser = [];

      $.each(coll, function() {

        var members = $.map(this.members, function(item) {
          var member = self.getMemberByID.call(self, item);
          if (!test || member[test.prop] === test.val) {
            return self.tmplMemberChooser.apply(member);
          }
        });

        var group = self.tmplMemberByGroupChooser.apply({
          id: this.id
          , name: this.name
          , members: members 
          , memberMarkup: members.join("")
        });

        Chooser.push(group);

      });
      return Chooser.join("");
    }
    , buildSubscribedMemberChooser: function() {
      this.subscribedNode.html(
        this.getMemberByGroupChooser(
          this.getEligibleGroups()
          , {prop: 'subscribedNow', val: true}
        )
      );
    }
    , buildUnsubscribedMemberChooser: function() {
      this.unsubscribedNode.html(
        this.getMemberByGroupChooser(
          this.getEligibleGroups()
          , {prop: 'subscribedNow', val: false}
        )
      );
    }
  
    //create tooltip to show members in a group

    , getMemberList: function(groupID) {
      var self = this;
      var members = $.map(
        self.getGroupByID(groupID).members
        , function(memberID) {
          return self.tmplMemberView.apply(self.getMemberByID(memberID));
        }
      )
      var group = self.getGroupByID(groupID).name;
      var memberList = this.tmplMemberViewWrapper.apply({
        group: group
        , members: members
        , memberMarkup: members.join("")
      });
      return memberList;
    }
    
    //find out how many groups a member belongs to
    , getGroupCount: function(memberId) {
      return $.grep(this.groups, function(group) {
        var s = $.grep(group.members, function(member) {
          return member == memberId;
        });
        return s.length > 0;
      }).length;
    }
    
    //lookup methods
    , getByID: function(coll, id) {
      for (var i = 0, j = this[coll].length; i < j; i++) {
        var item = this[coll][i];
        if (item.id == id) {
          return item;
        }
      }
    }
    , getMemberByID: function(id) {
      return this.getByID.call(this, "members", id);
    }
    , getGroupByID: function(id) {
      return this.getByID.call(this, "groups", id);
    }
    , getSubGroup: function(coll, prop, state) {
      return $.grep(coll, function(n) {
        return n[prop] === state;
      });
    }
    , getSubscribedMembers: function() {
      return this.getSubGroup(this.members, "subscribedNow", true); 
    }
    , getUnsubscribedMembers: function() {
      return this.getSubGroup(this.members, "subscribedNow", false); 
    }
    , getEligibleGroups: function() {
      return this.getSubGroup(this.groups, "eligibleNow", true); 
    }
    , getIneligibleGroups: function() {
      return this.getSubGroup(this.groups, "eligibleNow", false); 
    }
    
    , serialize: function(coll) {
      return $.map(coll, function(item) {
        return item.id;
      }).join(",");
    }
    , serializeSubscribedMembers: function() {
      return this.serialize(this.getSubscribedMembers());
    }
    , serializeUnsubscribedMembers: function() {
      return this.serialize(this.getUnsubscribedMembers());
    }
    , serializeEligibleGroups: function() {
      return this.serialize(this.getEligibleGroups());
    }
    , serializeIneligibleGroups: function() {
      return this.serialize(this.getIneligibleGroups());
    }
    , appendSerializedFormFields: function() {
      this.submitForm.append(
        '<input type="hidden" name="addGroups" id="addGroups" value=""/>' +
        '<input type="hidden" name="removeGroups" id="removeGroups" value=""/>' +
        '<input type="hidden" name="addMembers" id="addMembers" value=""/>' +
        '<input type="hidden" name="removeMembers" id="removeMembers" value=""/>'
      );
    }
    , setSerializedFormFieldValues: function() {
      $('#addMembers').val(this.serializeSubscribedMembers());
      $('#removeMembers').val(this.serializeUnsubscribedMembers());
      $('#addGroups').val(this.serializeEligibleGroups());
      $('#removeGroups').val(this.serializeIneligibleGroups());
    }
  });

}(jQuery));
