if( typeof( api ) == "undefined" ){ api = {}; api.ns = 'api'; }

/**
 * Channel Model
 * Instantiated with channel dictionary, instantiates api.Review and api.User objects that
 * are included with the channel.
 * @author Philipp Pfeiffenberger philippp@slide.com
 * @constructor
 */
api.Channel = function( channel_dictionary ){

  var i;
  this.relevance_by_type = {
    'direct' : [], // Directed by channel owner. This must come first
    'review' : [],
    'star' : [],
    'received_invite' : []
  };

  this.relevance_by_time = [];
  this.relevance_time_map = {};

  attributes = ['reviews',
                'ratings',
		'pcid',
		'channel_id',
		'count',
		'total_review_count',
		'images',
		'modified',
		'link',
		'latest_review',
		'owner_token',
		'user_token',
		'flag_token',
		'submit_token',
		'moderator_token',
		'privacy',
		'owner_user_id',
		'owner_puid',
		'relevance',
		'stars',
		'users',
		'time',
		'title',
		'submitter',
		'submitter_puid',
		'needs_approval',
		'width',
		'height',
		'partition',
		'type'];

  // Copy all supported attributes
  for( i = 0; i < attributes.length; i++ ){
    this[attributes[i]] = channel_dictionary[attributes[i]];
  }
  this.id = attributes['channel_id'];

  // Instantiate all api.Review objects and create the review_id to channel mapping
  for( i = 0; i < this.reviews.length; i++ ){
    this.reviews[i] = new api.Review( this.reviews[i] );
    api.Channel.review_channel[this.reviews[i]['prid']] = this;
  }

  // Instantiate api.User objects for stars
  for( i = 0; i < this.stars.length; i++){
    this.stars[i] = new api.User( this.stars[i] );
  }

  // Map channel relevance, if present.
  var reduce_time = {};
  for( i = 0; this.relevance && i < this.relevance.length; i++ ){
    var r_type = this.relevance[i]['type'];
    var r_type_arr = this.relevance_by_type[r_type];
    r_type_arr[r_type_arr.length] = this.relevance[i]['user_id']; // user A, user B, user C (relevant_action) this (channel)
    if( !reduce_time[r_type] || this.relevance[i]['time'] > reduce_time[r_type] ){
      // Record most recent timestamp for each relevance type to determine top heading
      reduce_time[r_type] = this.relevance[i]['time'];
    }
  }
  this.relevance_time_map = reduce_time;

  // Build sorting map and sort!
  for( var relevance in reduce_time ){
    this.relevance_by_time[this.relevance_by_time.length] = {
      'type' : relevance,
      'time' : reduce_time[relevance]
    };
  }
  this.relevance_by_time.sort( function(apple,orange){
    var comparator = orange['time'] > apple['time'];
    return comparator ? 1:-1;
  }); // Relevance by time is now sorted for most recent relevance type first

  this._uniquify_stars();
};


api.Channel.prototype = {

  loaded_image_count : 0,

  /**
   * Indicates whether a channel has the specified relevance type
   * @return Boolean
   */
  has_relevance_type : function(type){
    return this.relevance_by_type[type].length > 0;
  },

  /**
   * Channel's title link
   * @return String HTML Anchor tag for channel title and link
   */
  click_title : function() {
    var title = this.title;
    if( this.title.length === 0 ){
      title = translate('Untitled');
    }

    if(this.title.length > 90 ){
	title = this.title.substring(0,90)+"...";
    }
    return '<a href="'+this.link+'">'+title+'</a>';
  },

  preload_images : function(){
    for (var i=1; i<this.images.length; i++) {
      var tmpImg = new Image();
      tmpImg.src = this.images[i]['url'];
    }
  },

  /**
   * Override this function to have specific on_load effects for each channel
   */
  on_images_loaded : function(){
    if( api.Channel.Template.instance ){
      api.Channel.Template.instance.on_images_loaded();
    }
  },

  /**
   * Fired whenever an image is loaded. Triggers on_images_loaded once all images have been loaded
   */
  image_loaded : function(){
    this.loaded_image_count++;
    if( this.loaded_image_count == this.images.length ){
      this.on_images_loaded();
      api.Channel.Template.instance._render_play_button(this.channel_id);
      this.loaded_image_count = 0;
    }
  },

  _uniquify_stars : function(){
    var visited_anonymous_star_names = {};
    var visited_star_ids = {};
    uniquified_stars = [];
    jQuery.each(this['stars'], function(which_star, each_star){
      if( each_star['user_id'] === '0' ){
	if( typeof(each_star['display_name']) == 'undefined' ){
          each_star['display_name'] = 'anonymous';
	}
	if( !visited_anonymous_star_names[each_star['display_name']] ){
          visited_anonymous_star_names[each_star['display_name']] = true;
          uniquified_stars.push(each_star);
	}
      }
      else if( !visited_star_ids[each_star['user_id']] ){
	uniquified_stars.push(each_star); // todo: this will assign by ref - make it do a deep copy
	visited_star_ids[each_star['user_id']] = true;
      }
    });
    this.stars = uniquified_stars;
  },

  /**
   * Removes all known references to this channel
   */
  _destroy : function(){
    for( var i = 0; i < this.reviews.length; i++){
      api.Channel.review_channel[this.reviews[i].id] = null;
    }
    api.Channel.channels[this.id] = null;

  },

  'submit_to_group' : function( puid, options ){
    var group = new api.Group({'puid':puid});
    var passed_options = {submitter:options.submitter};
    group.submit_channels(this, passed_options);
  }

};

/**
 * Channels accessible by channel ID
 */
api.Channel.channels = {};

/**
 * Channels accessible by relevant review ID
 */
api.Channel.review_channel = {};

////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
// AJAX CALLS //// AJAX CALLS //// AJAX CALLS //// AJAX CALLS //
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////

/**
 * Retrieve a set of channels for the specified user.
 * @param puid Pickled User ID
 * @param psid Pickled Sequence ID
 * @param tab May be 'profile', 'friends', 'live', or for groups: 'mys', 'pop', 'sub', or 'new'
 * @param on_fetch Callback for channel retrieval, receives (channels, users, li_nxuid)
 */
api.Channel.fetch = function(puid, psid, tab, on_fetch, ajaxParams) {

  if( typeof( ajaxParams ) == 'undefined' ){ ajaxParams = {}; }
  // Lock
  if( api.Channel._fetch_lock[tab]){
    return false;
  }
  api.Channel._fetch_lock[tab] = true;
  ajaxParams['puid'] = puid;
  ajaxParams['psid'] = psid;

  var orig_tab = tab;
  if( tab.indexOf( 'profile' ) === 0 ){
    var parts = tab.split("_");
    if( parts.length == 1 ){
      parts = [tab, 'all'];
    }
    tab = parts[0];
    ajaxParams['channel_type'] = parts[1];
  }
  ajaxParams['tab'] = tab;
  if( tab == 'live' || tab == 'profile' || tab == 'friends' || tab == 'favorite' ){
    ajaxParams['xaction'] = 'get_channels_'+tab;
  }else{
    ajaxParams['xaction'] = 'get_channels_group_'+tab;
  }
  AppBase.post(
    '/profile',
    ajaxParams,
    function(response){ api.Channel._on_fetch( on_fetch, response, orig_tab); },
    function(){api.Channel._fetch_lock[tab] = false;} // be sure to unlock on fail
  );
  return false;
};

api.Channel._fetch_lock = {};

api.Channel._on_fetch = function( callback, result, orig_tab ){
  api.Channel._fetch_lock[orig_tab] = false;
  if( result && result['error'] === 'group_removed' ){ bu.relocate('/profile?puid='+Groups.puid); return; }

  for( var user_id in result['users'] ){
    result['users'][user_id] = new api.User( result['users'][user_id] );
  }

  for( var i = 0; i < result['channels'].length; i++ ){
    var cid = result['channels'][i]['channel_id'];
    result['channels'][i] = new api.Channel( result['channels'][i]);
    api.Channel.channels[cid] = result['channels'][i];
  }

  // Attempt to automatically update template factory
  if( api.Channel.Template.instance !== null){
    if( result.hasOwnProperty('li_nxuid')){
      api.Channel.Template.instance.nxuid = result['li_nxuid'];
    }
    if( result.hasOwnProperty('users')){
      api.Channel.Template.instance.add_users( result['users'] );
    }
  }

  callback( result['channels'], result['users'], result['li_nxuid'], result['psid'], orig_tab);
};

/**
 * Remove a Channel owned by the current user.
 * @param nxcid Encrypted channel ID, includes delete permission.
 * @param on_remove Callback for channel removal (optional).
 */
api.Channel.remove = function( nxcid, on_remove ){
  if( typeof( on_remove ) == "undefined" ) {on_remove = function(){};}

  AppBase.post(
    '/profile',
    {
      'xaction' : 'remove_channel',
      'permission' : nxcid
    },
    function(result) {
      api.Channel._on_remove( on_remove, result );
    }
  );
  return false;
}; api.Channel._on_remove = function( callback, result ){ callback( result ); };

/**
 * Toggle public/private of a Channel owned by the current user.
 * @param nxcid Encrypted channel ID, includes permission.
 * @param old_privacy Current channel privacy setting
 * @param on_toggle_privacy Callback for channel privacy change (optional).
 */
api.Channel.toggle_privacy = function( nxcid, old_privacy, on_toggle_privacy ){
  if( typeof( on_toggle_privacy ) == "undefined" ) {on_toggle_privacy = function(){};}

  AppBase.post(
    '/profile',
    {
      'xaction' : 'toggle_channel_privacy',
      'old_privacy' : old_privacy,
      'permission' : nxcid
    },
    function(result){ api.Channel._on_toggle_privacy( on_toggle_privacy, result ); }
  );
  return false;
};
api.Channel._on_toggle_privacy = function(callback, result){
  // Redraw the channel if it exists
  var chan = api.Channel.channels[result['cid']];
  if( result['new_privacy'] == 'a' ){
    chan.privacy = 'public';
  }else{
    chan.privacy = 'invite';
  }
  var new_elem = api.Channel.Template.instance.render_channel( chan );
  $('#'+result['cid']).before(new_elem).remove();
  callback( result );
};

////////// todo: migrate functionality into instance instance methods (above)
//
/**
 * Submit the channel to the specified group.
 * @param nxcid Encrypted channel ID
 * @param puid Pickled ID of the specified group. (Can be found in Profile.puid when the group is shown.)
 * @param on_submit_group Callback for group submission (optional).
 */
api.Channel.submit_group = function(nxcid, puid, on_submit_group) {
  if( typeof( on_submit_group ) == "undefined" ) {on_submit_group = function(){};}

  AppBase.post(
    '/groups',
    {
      'xaction' : 'submit_channel',
      'puid' : puid,
      'nxcid' : nxcid
    },
    function( result ){
      api.Channel._on_submit_group( on_submit_group, result );
    }
  );

}; api.Channel._on_submit_group = function( callback, result ){ callback( result ); };

/**
 * Remove the channel from the specified group.
 * @param nxcid Encrypted channel ID.
 * @param puid Pickled ID of the group.
 * @param on_remove_group Callback for channel removal from group (optional).
 */
api.Channel.remove_group = function(nxcid, puid, on_remove_group) {
  if( typeof( on_remove_group ) == "undefined" ) {on_remove_group = function(){};}

  AppBase.post(
    '/groups',
    {
      'xaction' : 'remove_channel',
      'puid' : puid,
      'nxcid' : nxcid
    },
    function( result ){ api.Channel._on_remove_group( result, on_remove_group ); }
  );
  return false;
}; api.Channel._on_remove_group = function( result, callback ){ callback( result ); };

/**
 * Flag the channel in the context of the specified group.
 * @param nxcid Encryped channel ID.
 * @param puid Pickled ID of the group.
 * @param on_flag_group Callback invoked afte a channel is flagged (optional).
 */
api.Channel.flag_group = function(nxcid, puid, on_flag_group)
{  if( typeof( on_flag_group ) == "undefined" ) {on_flag_group = function(){};}

  AppBase.post(
    '/groups',
    {
      'xaction' : 'flag_channel',
      'puid' : puid,
      'nxcid' : nxcid
    },
    function( response ){ api.Channel._flag_group( on_flag_group, response ); }
  );
  return false;
}; api.Channel._flag_group = function( callback, response ){ callback( response ); };

/**
 * Flag the channel in the context of the live feed.
 * @param cid Raw ID of channel to flag.
 * @param puid Pickled ID of the group.
 * @param on_flag_live Callback invoked afte a channel is flagged (optional).
 */
api.Channel.flag_live = function(cid, puid, on_flag_live)
{  if( typeof( on_flag_group ) == "undefined" ) {on_flag_group = function(){};}

  AppBase.post(
    '/profile',
    {
      'xaction' : 'flag_live_channel',
      'puid' : puid,
      'cid' : cid
    },
    function( response ){ api.Channel._flag_live( on_flag_live, response ); }
  );
  return false;
}; api.Channel._flag_live = function( callback, response ){ callback( response ); };

/**
 * Approve the channel for inclusion in the specified group.
 * @param nxuid Encrypted channel ID
 * @param puid Pickled ID of the group
 * @param on_approve_group Callback invoked after a channel is approved (optional).
 */
api.Channel.approve_group = function(nxcid, puid, on_approve_group) {
  if( typeof( on_approve_group ) == "undefined" ) {on_approve_group = function(){};}

  AppBase.post(
    '/groups',
    {
      'xaction' : 'approve_channel',
      'puid' : puid,
      'nxcid' : nxcid
    },
    function( result ){ api.Channel._on_approve_group( on_approve_group, result );}
  );
  return false;
}; api.Channel._on_approve_group = function( callback, result ){ callback( result ); };

//
////////// end todo migrate


////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
// TEMPLATE RENDERING // TEMPLATE RENDERING //// TEMPLATE RENDERING // TEMPLATE RENDERING //
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Template factory for Channels provides HTML elements allowing the display and control of Channel
 * objects. The parameters view and puid must be provided by the implementation, an honest effort
 * is made to automatically set nxuid on a fetch request.
 *
 * @param view View of the channel listing may be "user_profile", "group_profile", "user_homeview", or "user_homeview_live"
 * @param puid Pickled ID of the user/group being shown
 * @param nxuid Encrypted user ID with permissions of the currently logged in user (auto-set on api.Channel.fetch)
 * @param users Array of users related to Channels (auto-set on api.Channel.fetch)
 */
api.Channel.Template = function( view, puid, nxuid, users ){

  if( api.Channel.Template.instance !== null ){
    alert("api.Channel.Template may only be instantiated once!");
  }

  if( typeof( view ) == "undefined" ){
    this.view = '';
  }else{
    this.view = view;
  }

  if( typeof( nxuid ) == "undefined" ){
    this.nxuid = 0;
  }else{
    this.nxuid = nxuid;
  }

  if( typeof( puid ) == "undefined" ){
    this.puid = 0;
  }else{
    this.puid = puid;
  }

if( typeof( users ) == "undefined" ){
    this.users = [];
  }else{
    this.users = users;
  }
  api.Channel.Template.instance = this;
};

api.Channel.Template.instance = null;

api.Channel.Template.prototype = {

  /**
   * Render the slideshow in a popup. If false, we redirect to the channels' link attribute
   */
  show_in_popup : false,

  /**
   * Renders a Channel. This includes user controls (options menu), channel
   * header/title, and the channel contents.
   */
  render_channel : function( channel ){

        var channel_div = DIV({
                                'class':'story_frame',
				'id':channel['channel_id']
                              },
                              DIV({
                                    'class':'story_body'
                                  },
                                  this.render_user_controls(channel),
                                  this.render_header( channel ),
                                  this.render_contents( channel )
                                 ),
                              DIV({"class":"breakline"})
                             );
        return channel_div;
  },

  render_channel_thumb : function( channel, click_href, onclick, viewing_now ){
    if( typeof( click_href ) == 'undefined' ){ click_href = '#'; }
    if( typeof( onclick ) == 'undefined' ){ onclick = 'return true'; }

    var subtext = ''; var title = '';
    if( channel.type == 'G' ){
	subtext = SPAN({},"("+translate("Image_count",channel.count)+")");
    }
    if( viewing_now ){
      subtext = SPAN({'class':'info_highlight'}, translate('now_showing'));
      onclick = false;
      title = channel.title;
      if(title.length > 90 ){
	title = channel.title.substring(0,90)+"...";
      }
      title = SPAN({},title);
    }else{
      title = $(channel.click_title())[0];
    }
    return DIV({'class':'channel_thumb'},
               this._render_images( channel, 1, click_href, onclick ),
               DIV({'class':'small'},
                 DIV({'class':'thumb_title'},
                     title
                    ),
                    subtext
                 )
              );
  },


  /**
   * Render the header section of the channel. This includes the headline, which
   * explains why the channel is relevant and who is in the channel. It may
   * also include privacy information and approval options when appropriate.
   * @param channel api.Channel instance for which a header is needed
   * @return DIV containing header information
   */
  render_header : function( channel ){
    var headline = DIV({});

    for( var i = 0; i < channel.relevance_by_time.length; i++ ){
      var typename = channel.relevance_by_time[i]['type'];
      if( channel.has_relevance_type( typename ) ){
	headline.appendChild(this.render_headline( channel, typename, i ));
      }
    }

    if (channel.privacy != 'public') {
      var message = (this.view == 'group_profile') ? 'private_channel_group' : 'private_channel';
      headline.appendChild(DIV({'class':'small approval'}, translate(message)));
    }
    if (channel.needs_approval) {
      headline.appendChild(DIV({'class':'small approval'}, translate('needs_approval')));
    }
    return headline;
  },

  /**
   * Render the user controls / options for the Channel, based on channel attributes.
   * @param channel api.Channel instance for which controls are sought
   * @return DIV containing options block
   */
  render_user_controls : function( channel ){
    var menu_options = [];
    if (channel['moderator_token']) {
      if (channel['needs_approval']) {
	this._add_channel_lister_option(menu_options, channel, 'approve_for_group_moderator');
      }
      this._add_channel_lister_option(menu_options, channel, 'remove_from_group_moderator');
    }
    if (this.view == "group_profile" && channel['flag_token']) {
      this._add_channel_lister_option(menu_options, channel, 'flag_group_channel');
    }
    if (this.view == "user_homeview_live"){
      this._add_channel_lister_option(menu_options, channel, 'flag_live_channel');
    }
    if (channel['submit_token']) {
      this._add_channel_lister_option(menu_options, channel, 'remove_from_group_submitter');
    }
    if (channel['owner_token']) {
      this._add_channel_lister_option(menu_options, channel, 'edit_show');
      if (channel.type == 'G') {
	this._add_channel_lister_option(menu_options, channel, 'add_stars');
	this._add_channel_lister_option(menu_options, channel, 'save_show');
      }
      this._add_channel_lister_option(menu_options, channel, 'get_code');
      this._add_channel_lister_option(menu_options, channel, 'change_privacy');
      this._add_channel_lister_option(menu_options, channel, 'delete_show');
    }

    if (channel['user_token']) {
      this._add_channel_lister_option(menu_options, channel, 'remove_from_stars');
    }

    if ((typeof Groups !== 'undefined') && Groups.is_moderator && (channel.submitter_puid !== AppBase.user.puid) ) {
      this._add_channel_lister_option(menu_options, channel, 'ban_member');
    }

    var channel_menus = [];
    var parental = DIV({ 'class':'channel_menus right channel_options_link' });
    if (menu_options.length) {
      var channel_options_menu_wrapper = DIV({'class':'channel_options_menu_wrapper', 'style':'float:right'});
      channel_menus.push(channel_options_menu_wrapper);
      AppBase.create_menu(channel_options_menu_wrapper, 'options_'+channel['channel_id'], 'channel_options', menu_options);
    }
    if( AppBase.user ){
      channel_menus.push(
        DIV({'style':'float:right;'},
          A(
            {
              'onclick':function(){
                var sharer = Sharer.initialize({
                  'to_share' : channel,
                  'pcid' : channel.pcid,
                  'do_forward_to_live' : false,
                  'on_finished' : Sharer.thank_and_close
                });
                var abbrev_name = lu.abbreviate(channel.title);
                var thumb_url = AppBase.redeco( channel.images[0]['url'], {'width':'45','height':45});

                sharer.find("#header_image_container").html(IMG({'src':thumb_url, 'class':'thumb'}));
                var share_header = DIV({'id':'share_header'},
                                     H2({'style':'display:inline;'},translate('invite_to_see_channel', abbrev_name))
                );
                sharer.find('#header_container').html(share_header);
                var on_close = function(){$("#sharer #share_header").remove();$("#sharer .thumb").remove();};
                bu.popup(sharer, {'dim':true, 'closable':true, 'padding':'10px', 'classname':'channel_sharer', 'on_close' : on_close, 'width':'380'});
                return false;
              },
              'href':'javascript: void(0)'
            },
            translate('share')
          )
        )
      );
    }
    jQuery.each(channel_menus, function( which_menu, each_menu ){
      if( which_menu !== 0 ){
        $(parental).append(
          DIV({'class':'channel_menu_divider', 'style':'float:right; margin:0px 10px;'},
            '|'
          )
                           );
      }
      $(parental).append(each_menu);
    });
    return parental;
  },

  /**
   * Render the contents of the channel. This includes the thumbnail images, the average rating,
   * the title of the channel and the relevant review, if the Channel is shown because of a review
   * relationship.
   * @return DIV element containing the channel information
   */
  render_contents : function(channel) {
    var reviewTemplate = new api.Review.Template( this.nxuid );
    var channelDiv = DIV({"class":"story", "id":"story_"+channel['channel_id']});
    var play_onclick = "api.Channel.Template.instance._render_rate(\""+channel['channel_id']+"\", \"slideticker\"); return false;";
    var play_href = "#";
    var img_onclick = "api.Channel.Template.instance._render_rate(\""+channel['channel_id']+"\", \"grid\", \"__iid__\"); return false;";
    var img_href = "#";

    if( !this.show_in_popup ){
      play_href = channel.link;
      play_onclick = true;
      img_onclick = true;
      img_href = channel.link+"?map=2&iid=__iid__";
    }
    channelDiv.appendChild(this._render_images(channel,
                                               5,
                                               play_href,
                                               play_onclick,
                                               img_href,
                                               img_onclick
                                              ));

    if( this.view == 'user_homeview_live' ){
      if( this.users[channel.owner_user_id].fan_permission ){
	channelDiv.appendChild(this._render_fan_owner(channel));
      }
    }

    channelDiv.appendChild(this._render_title(channel));
    channelDiv.appendChild(this._render_stars(channel.stars));

    if( !channel['ratings'].hasOwnProperty('hidden') && (AppBase.user || channel['total_review_count'] > 0) ){
      channelDiv.appendChild(DIV({
        "id":"channel_"+channel["channel_id"]+"_review_container",
        "class":"review_container"},
          reviewTemplate.render_review_section(channel['reviews'], channel['total_review_count'], channel['channel_id'])
        ));
    }

    channelDiv.appendChild(DIV({"class":"breakline"}));
    return channelDiv;
  },

  /**
   * Render the relation between the channel and users for
   * the specified typename. For example, a typename of "review" would generate
   * a one-line associative description for any of the users that have reviewed
   * the channel.
   * @param channel api.Channel instance for which we need a headline
   * @param typename Type of relation sought (review, star, direct)
   * @param headline_count index of the headline (0 is first/top, etc)
   */
  render_headline : function( channel, typename, headline_count ){
    var nameNode = SPAN({});
    var headline = this._generate_headline_hash( channel, typename );
    var dateNode = "";
    if( this.view.indexOf('user_homeview_live') == -1 && headline_count === 0){
      dateNode = SPAN({'class':'small date'},AppBase.datestring(channel.relevance_time_map[typename]));
    }
    nameNode.innerHTML = translate(headline['text'], headline['name']);
    headline = DIV({'class':'story_header'},
                 IMG({'src':image_url(headline['image'])}),
                   "&nbsp;&nbsp;",
                   nameNode,
                   dateNode
                 );
    if( typename == 'review' ){
      headline.appendChild(this._render_first_review(channel.latest_review));
    }

    return headline;
  },

  /**
   * Generates singular headline hash from channel relevance hashes
   * @param channel api.Channel instance receiving headline, contains relevance data
   * @param typename Type of relevance sought (review, star, direct)
   */
  _generate_headline_hash : function(channel, typename) {
    var names = [];
    // For groups
    // TODO: generate proper relevance for group channels
    // For profile / homeview
    if( typename == 'direct' ){
      var name = this.users[channel.owner_user_id].click_name();
      var lead = (channel['modified']) ? 'Modified_' : 'Created_';
      return {'text':lead+channel['type'],
              'image':'slidecom/created.gif',
              'name':name
      };
    }
    if( typename == 'received_invite' ){
      for( var i=0; i<channel.relevance_by_type[typename].length; i++ ){
        names[names.length] = this.users[channel.relevance_by_type[typename][i]].click_name();
      }
      return {'text':'Received_invite_'+channel['type'],
              'image':'slidecom/starred.gif',//todo: get an image for this
              'name':names.join(", ")};
    }
    if( typename == 'star' ){
      for( var i=0; i<channel.relevance_by_type[typename].length; i++ ){
        names[names.length] = this.users[channel.relevance_by_type[typename][i]].click_name();
      }
      return {'text':'Starred_'+channel['type'],
              'image':'slidecom/starred.gif',
              'name':names.join(", ")};
    }
    if( typename == 'review'){
      for( var i=0; i<channel.relevance_by_type[typename].length; i++ ){
        names[names.length] = this.users[channel.relevance_by_type[typename][i]].click_name();
      }
      return {'text':'Reviewed_'+channel['type'],
              'image':'slidecom/reviewed.gif',
              'name':names.join(", ")};
    }
    return {'text':'Unknown',
            'image':'blank.gif',
            'name':'unknown'};
  },

  /**
   * All potential channel options
   */
  _channel_lister_options : {
    'save_show': function(channel) {
	return {'name':'save_show',
		'href':Serdes.make_command_url('/save/zipped/'+channel['owner_token']+'/photos.zip', {}),
		'onclick':'return true',
		'img':Serdes.make_static_url('/images/slidecom/actions/download.gif')
	       };
    },
    'edit_show': function(channel) {
	return {'name':'edit_show',
		'href':api.Channel.Template._make_arrange_url(channel),
		'onclick':'return true',
		'img':Serdes.make_static_url('/images/slidecom/actions/edit.gif')
	       };
    },
    'add_stars': function(channel) {
	return {'name':'add_stars',
		'href':Serdes.make_command_url('/credits', {'nxcid':channel['owner_token']}),
		'onclick':'return true',
		'img':Serdes.make_static_url('/images/slidecom/actions/stars.gif')
	       };
    },
    'get_code': function(channel) {
	return {'name':'get_code',
		'href':Serdes.make_command_url('/mscd', {'nxcid':channel['owner_token']}),
		'onclick':'return true',
		'img':Serdes.make_static_url('/images/slidecom/actions/embed.gif')
	       };
    },
    'delete_show': function(channel) {
	return {'name':'delete_show',
		'href':'#',
		'onclick': function() {
		  if (window.confirm(translate('Are_you_sure_owner'))) {
		    api.Channel.remove(channel['owner_token'],
					       function(){ $('#'+channel['channel_id']).fadeOut();});
		  }
		  return false;
	    	},
		'img':Serdes.make_static_url('/images/slidecom/actions/delete.gif')
	       };
    },
    'remove_review': function(channel) {
        return {'name':'remove_review',
		'href':'#',
		'onclick': function() {
			if (window.confirm(translate('Are_you_sure_reviewer'))) {
			  api.Channel.remove(channel['latest_review']['permission'],
					     function(){ $('#'+channel['channel_id']).fadeOut();}
					     );
			}
			return false;
	    	},
		'img':Serdes.make_static_url('/images/slidecom/actions/delete.gif')
	       };
    },
    'change_privacy': function(channel) {
	return {'name':'change_privacy_from_' + channel['privacy'],
		'href':'#',
		'onclick': function() {
			if (window.confirm(translate('Are_you_sure_change_privacy_from_'+channel['privacy']))) {
		    		api.Channel.toggle_privacy(channel['owner_token'], channel['privacy']);
			}
			return false;
	    	},
		'img':Serdes.make_static_url('/images/slidecom/actions/privacy.gif')
	       };
    },
    'remove_from_group_submitter': function(channel) {
	return {'name':'remove_from_group_submitter',
		'href':'#',
		'onclick': function() {
			if (window.confirm(translate('Are_you_sure_submitter'))) {
		    		api.Channel.remove_group(channel['submit_token'],
							 api.Channel.Template.instance.puid,
							 function(){ $('#'+channel['channel_id']).fadeOut();}
							);
			}
			return false;
	    	},
		'img':Serdes.make_static_url('/images/slidecom/actions/delete.gif')
	       };
	},
    'flag_group_channel': function(channel) {
	return {'name':'flag_channel',
		'href':'#',
		'onclick': function() {
		  if (window.confirm(translate('Are_you_sure_flag'))) {
		    api.Channel.flag_group(channel['flag_token'],
					   api.Channel.Template.instance.puid,
					   function(){ $('#'+channel['channel_id']).fadeOut();}
					  );
		  }
		  return false;
		},
		'img':Serdes.make_static_url('/images/slidecom/actions/flag.gif')
	       };
    },
    'flag_live_channel': function(channel) {
	return {'name':'flag_channel',
		'href':'#',
		'onclick': function() {
		  if (window.confirm(translate('Are_you_sure_flag'))) {
		    api.Channel.flag_live( channel['channel_id'],
					   api.Channel.Template.instance.puid,
					   function(){ $('#'+channel['channel_id']).fadeOut();}
					  );
		  }
		  return false;
		},
		'img':Serdes.make_static_url('/images/slidecom/actions/flag.gif')
	       };
    },
    'approve_for_group_moderator': function(channel) {
	return {'name':'approve_for_group_moderator',
		'href':'#',
		'onclick': function() {
			api.Channel.approve_group( channel['moderator_token'],
						   api.Channel.Template.instance.puid,
						   function(){ $('#'+channel['channel_id']).fadeOut();}
						 );
			return false;
	    	},
		'img':Serdes.make_static_url('/images/slidecom/actions/stars.gif')
	       };
    },
    'remove_from_group_moderator': function(channel) {
	return {'name':'remove_from_group_moderator',
		'href':'#',
		'onclick': function() {
			if (window.confirm(translate('Are_you_sure_moderator'))) {
		    		api.Channel.remove_group(channel['moderator_token'],
							 api.Channel.Template.instance.puid,
							 function(){ $('#'+channel['channel_id']).fadeOut();}
							);
			}
			return false;
	    	},
		'img':Serdes.make_static_url('/images/slidecom/actions/delete.gif')
	       };
    },
   'remove_from_stars': function(channel) {
	    return {'name':'remove_from_stars',
		    'href':'#',
		    'onclick': function() {
		    	if (window.confirm(translate('Are_you_sure_star'))) {
				api.Channel.remove(channel['user_token'],
						   function(){ $('#'+channel['channel_id']).fadeOut(); }
						  );
		    	}
		    	return false;
		    },
		    'img':Serdes.make_static_url('/images/slidecom/actions/delete.gif')
		   };

	},
	'ban_member': function(channel) {
		return {'name':'ban_member',
			'href':'#',
			'onclick': function() {
				Groups.on_click_ban(channel.submitter_puid);
				return false;
			},
			'img':Serdes.make_static_url('/images/slidecom/actions/delete.gif')
		};

	}
  },

  _add_channel_lister_option : function(menu_list, channel, option){
    opt = this._channel_lister_options[option](channel);
    menu_list.push( opt );
  },

  _render_stars : function(stars) {
    var starsDiv = DIV({'class':'stars'});
    if (stars.length > 0) {
      starsDiv.appendChild(DIV({'style':'float:left; padding:0px 3px;'}, translate('Starring')));
      for (var i=0; i<stars.length; i++) {
	  var starDiv = DIV({'class':'star_div'});
	  var sep = (i+2 == stars.length) ? ' and ' : (i+1 == stars.length) ? '' : ', ';
	  starDiv.innerHTML = stars[i].click_name() + sep;
	  starsDiv.appendChild(starDiv);
      }

	starsDiv.appendChild(BR({'class':'clear'}));
    }else if(this.view != "user_homeview_live"){
      //Hackedy hack! This keeps the big stars (from render_review) from colliding with the JS reviews -- philippp
      starsDiv.appendChild(DIV({style:'height:15px;'}));
    }
    return starsDiv;
  },

  _render_title : function(channel) {
    var titleDiv = DIV({'class':'show_title'});
    var title = channel.click_title();
    var name = this.users[channel.owner_user_id].click_name();
    var count = '';
    var dateNode = '';
    if( channel.type == 'G' ){
	count = "("+translate("Image_count",channel.count)+")";
    }
    titleDiv.innerHTML = '<div class="byline">' + translate('Directed',title,name,count)+"</div>";
    return titleDiv;
  },


  /**
   * Render the channel's images
   */
  _render_images : function(channel, max_length, play_href, play_onclick, img_href, img_onclick){
    var view = this.view;
    var li_nxuid = this.li_nxuid;
    var img_class;
    var swf_view;
    var cid = channel['channel_id'];
    this.view == "user_homeview_live" ? img_class = '' : img_class = 'images';
    if( typeof( max_length ) == 'undefined' || max_length > channel.images.length ){
      max_length = channel.images.length;
    }
    var firstcell = "";
    if( play_onclick !== false ){
      firstcell = "<table cellspacing='0' cellpadding='0'><tr><td>"
		+ "<a class='playicon' id='pbd_"+cid+"' style='display:none;' href='"+play_href+"' onclick='"+play_onclick+"'><img src='"+Serdes.make_static_url('images/slidecom/play.png')+"' id='play_button_"+cid+"'/></a>"
		+ "<a href='"+play_href+"' onclick='"+play_onclick+"' class='photo_button'><img src='"+channel.images[0]['url']+"' id='play_icon_"+cid+"' onload='api.Channel.Template.instance._render_play_button(\""+channel['channel_id']+"\"); api.Channel.channels[\""+channel['channel_id']+"\"].image_loaded();'/></a></td>";
    }else{
      firstcell = "<table cellspacing='0' cellpadding='0'><tr><td>"+
		    "<img src='"+channel.images[0]['url']+"' id='play_icon_"+cid+"'/></td>";
    }
    var imagesDiv = DIV({'class':img_class});
    var cells = firstcell;
    var img_href_orig = img_href;
    var img_onclick_orig = img_onclick;
    for (var i=1; i<max_length; i++) {
      var ciid = channel.images[i]['ciid'];
      if( typeof( img_href_orig ) == 'string' ){ img_href = img_href_orig.replace(/__iid__/, ciid); }
      if( typeof( img_onclick_orig ) == 'string' ){ img_onclick = img_onclick_orig.replace(/__iid__/, ciid); }
      cells += "<td><a href='"+img_href+"' onclick='"+img_onclick+"' class='photo_link'><img src='"+channel.images[i]['url']+"' onload='api.Channel.channels[\""+channel['channel_id']+"\"].image_loaded();'/></a></td>";
    }
    cells += "</tr></table>";
    imagesDiv.innerHTML = cells;
//    alert(cells);
    return imagesDiv;
  },

  _render_play_button : function( channel_id ){
    var play_icon = $('#play_icon_'+channel_id);
    var play_button_div = $('#pbd_'+channel_id);
    var play_button = $('#play_button_'+channel_id);
    var button_size = 24;
    var margin_x = (play_icon.width() - button_size)/2 + 2; // +2 to compensate for photo_link border
    var margin_y = (play_icon.height() - button_size)/2 + 2;
    if( margin_x < 0 ){ margin_x = 0; }
    if( margin_y < 0 ){ margin_y = 0; }

    if( play_button.length === 0 ){ return; } // Safety hack, ensures we don't fire after a channel has been removed
    play_button[0].style.display = 'block';
    play_button[0].style.marginLeft = margin_x+"px";
    play_button[0].style.marginTop = margin_y+"px";

    play_icon.bind('mouseover', {'target' : play_button[0]}, function(e){
		     e.data.target.src = Serdes.make_static_url('/images/slidecom/play_over.png');
		   });

    play_icon.bind('mouseout', {'target' : play_button[0]}, function(e){
		     e.data.target.src = Serdes.make_static_url('/images/slidecom/play.png');
		   });

    play_button_div.bind('mouseover', {'target' : play_button[0]}, function(e){
		     e.data.target.src = Serdes.make_static_url('/images/slidecom/play_over.png');
		   });

    play_button_div.bind('mouseout', {'target' : play_button[0]}, function(e){
		     e.data.target.src = Serdes.make_static_url('/images/slidecom/play.png');
		   });
    play_button_div.fadeIn();

  },

  /**
   * Renders the review that the profile owner / homeview friend created.
   * This review is shown by itself at the top of the channel, seperate from the AJAX review section.
   * @param latest_review The profile owner's review in the channel, taken from channel.latest_review
   * @return DIV containing the latest relevant review
   */
  _render_first_review : function(latest_review) {
    var reviewsDiv = DIV({});
    if (latest_review) {
      var reviewDiv = DIV({'class':'first_review_div'});

      if (latest_review.stars != '0') {
	for (var i=0; i < Number(latest_review.stars); i++) {
	  reviewDiv.appendChild(SPAN({},
				     IMG({
					   'src':Serdes.make_static_url('/images/slidecom/staron_small.gif'),
					   'width':'11',
					   'height':'10'
					 })
				    ));
	}
	for (;i<5;i++) {
	  reviewDiv.appendChild(SPAN({},
				     IMG({
					   'src':Serdes.make_static_url('/images/slidecom/staroff_small.gif'),
					   'width':'11',
					   'height':'10'
					 })
				    ));
	}
      }
      var min_msg = latest_review.message.replace(/\s/g, '');
      if( min_msg.length > 0){
	reviewDiv.appendChild(SPAN({}, ' &nbsp; "' + latest_review.message + '"'));
      }
      reviewsDiv.appendChild(reviewDiv);
    }
    return reviewsDiv;
  },

  /**
   * Render a flash dialogue allowing the user to rate the slideshow
   */
  _render_rate : function(channel, swf_view, ciid){
    $('select').css("visibility", "hidden");
    if( typeof( channel ) == "string" ){
      channel = api.Channel.channels[channel];
    }

    this._render_slideticker(channel, swf_view, ciid);
    if( channel.ratings.hasOwnProperty('hidden') ){
      this._clear_review_swf();
    }else{
      this._render_review_swf(channel);
    }
    $('#inline_rate').show();
    getObject('inline_rate_container').style.top = (GetScrollY() + 50) + 'px';
  },

  /**
   * Renders a link in a DIV, allowing the user to fan the owner of the specified channel.
   * The link changes to a confirmation text after the fan action completes
   * @param channel owner of this channel is fanned by current user
   * @return DIV or placeholder SPAN if fanning is not an option
   */
  _render_fan_owner : function(channel){
    var to_ret = SPAN({});
    var owner_user = this.users[channel.owner_user_id];
    if( owner_user.fan_permission ){
	var fp = owner_user.fan_permission;
	var fan_added_txt = translate("fan_added",owner_user.display_name);
	to_ret = DIV({'class':'fan_button '+fp, 'id' : 'fan_'+channel['channel_id']},
		   INPUT({'type':'submit',
			  'onclick' : function(){ api.User.fan({'fan_permission':fp,'on_fan':function(){$('.'+fp).text(fan_added_txt);}});
						  return false;},
			  'value' : translate('subscribe_to_pictures', owner_user.display_name),
			  'class' : 'slide_button'
		     }
		   )
		 );
    }
    return to_ret;
  },

  _render_review_swf : function(channel){
    var nxuid = (this.nxuid) ? this.nxuid : 'none';
    var callback = "on_flash_review";
    var swf_path = Serdes.make_static_url("/widgets/reviewBox.swf");
    var fo = new FlashObject(swf_path,'review_swf', 400, 300, '8', '');
    fo.addParam("quality", "high");
    fo.addParam("wmode", "transparent");
    fo.addParam("allowScriptAccess", "always");
    fo.addVariable("nxuid",nxuid);
    if( AppBase.user ){
      fo.addVariable('display_name', AppBase.user.display_name);
    }
    fo.addVariable("view",this.view);
    fo.addVariable("callback",callback);
    fo.addVariable("channel_id",channel['channel_id']);
    fo.write('review_holder');
  },

  _slideticker_size_factor : 1.422220, //scale up to 640

  _ticker_std_height : 370,

  _ticker_std_width : 450,

  /**
   * Adds the channels Ticker flash object to the ticket_holder component
   * @nodoc
   */
  _render_slideticker : function(channel, view, ciid, adsense_channel, options){

    view = lu.defaulted( view, 'slideticker' );
    options = lu.defaults( options, {
                             'min_h':this._ticker_std_height,
                             'min_w':this._ticker_std_width,
                             'target_id':'ticker_holder',
                             'flashVars':{},
                             'map':null
                           });

    this._render_slideticker_header( channel, view );

    var size_factor = this._slideticker_size_factor; //scale up to 640 width
    var w = channel.width * size_factor;
    var h = channel.height * size_factor;
    var site = channel.partition;
    if(options.map){
      var map = options.map;
    }else if( view == 'slideticker' ){
      var map = "530100100";
    }else if( view == 'no_click' ){
      var map = "030100100";
      view = 'slideticker';
    }else if( view == 'click_through' ){
      var map = "130100100";
      view = 'slideticker';
    }else{
      var map = "130100100";
    }
    var swf = this._get_ticker(channel, view);

    if(view == 'grid' && (w < options.min_w || h < options.min_h)){
      h = size_factor * options.min_h;
      w = size_factor * options.min_w;
    }

    if(w > options.min_w * size_factor || h > options.min_h * size_factor){
      var sw = options.min_w*size_factor/w;
      var sh = options.min_h*size_factor/h;
      if(sw < sh){
	w = w * sw;
	h = h * sw;
      }else{
	w = w * sh;
	h = h * sh;
      }
    }

    var fo = new FlashObject(swf ,'slideticker', w, h, '8', '');
    fo.addParam("quality", "high");
    fo.addParam("wmode", "transparent");
    fo.addParam("salign", "l");
    fo.addParam("scale", "scale");
    fo.addParam("align", "middle");
    fo.addParam("allowScriptAccess", "always");
    fo.addParam("allowFullScreen", "true");
    fo.addVariable("allowingFullScreen", "true");
    fo.addVariable("channel", channel['channel_id']);
    fo.addVariable("s", "1");
    fo.addVariable("inline_rating", "true");
    fo.addVariable("site",site);
    fo.addVariable("map", map);
    for( fv in options.flashVars ){
      fo.addVariable(fv, options.flashVars[fv]);
    }
    if( ciid ){
      fo.addVariable("ciid", ciid);
    }
    if( typeof(adsense_channel) != 'undefined' ){
      fo.addVariable("adsense_channel", adsense_channel);
      fo.addVariable("adsense","True");
      fo.addVariable("adtype","fullscreen");
    }
    fo.write(options.target_id);
  },

  _render_slideticker_header : function( channel, view ){
    if( typeof(Profile) == "undefined" || typeof(Profile.pouid) == "undefined" ){
      var pouid = "";
    }else{
      var pouid = Profile.pouid;
    }
    if( !api.Channel.Template.instance.users[ channel.owner_user_id] || pouid == channel.owner_puid){
      return;
    }
    var user = api.Channel.Template.instance.users[channel.owner_user_id];
    var header_holder = $('#header_holder');
    $('#director_name', header_holder).empty().text( user.display_name );
    $('#director_profile_name', header_holder).empty().text( translate( 'view_profile_of', user.display_name ) );
    if( user.mugshot_url && header_holder.length > 0){
      var newImg = IMG({'src':user.mugshot_url, 'alt':'mugshot'});
      $('#director_mug', header_holder).show().empty().append( $(newImg) );
    }else{
      $('#director_mug', header_holder).hide();
    }
    if( user.link && header_holder.length){
      header_holder.bind('click',
			 function(){
			   window.location = user.link;
		       });
    }
    header_holder.show();
  },

  /**
   * Removes the channels Review flash object from the review_holder component
   * @nodoc
   */
  _clear_review_swf : function(){
    $('#review_holder').empty();
  },


  _flash_refresh : function(){
    hide_rate();
  },

  /**
   * Retrieves the correct Flash component ticker for the channel's type
   * @nodoc
   * @return String URL to the swf component
   */
  _get_ticker : function(channel, ticker_view){
    if (channel.type=='A' || channel.type=='P') {
	return 'http://'+channel.partition+'/widgets/themepic.swf';
    }
    if (channel.type=='C') {
	return 'http://'+channel.partition+'/widgets/slidemap.swf';
    }
    if (channel.type=='Q') {
	return 'http://'+channel.partition+'/widgets/sf.swf';
    }

    if( typeof( ticker_view ) == 'undefined' ){
      return 'http://'+channel.partition+'/widgets/slideticker.swf';
    }else{
      return 'http://'+channel.partition+'/widgets/'+ticker_view+'.swf';
    }

  },

  add_users : function( users ){
      this.users = jQuery.extend( this.users, users );
  },

  set_view : function( view ){
    this.view = view;
  },

  set_puid : function( puid ){
    this.puid = puid;
  },

  set_nxuid : function( nxuid ){
    this.nxuid = nxuid;
  },

  on_images_loaded : function(){}

}; // api.Channel.Template.prototype

/**
 * Generates URL for channel editing (arranging).
 * @param channel Targeted Channel to edit
 * @return Arrange URL for the specified channel
 */
api.Channel.Template._make_arrange_url = function( channel ){
  if (channel.type=='A' || channel.type=='P') {
    return Serdes.make_command_url('/pic_arrange', {'nxcid':channel['owner_token']});
  }
  if (channel.type=='C') {
    return Serdes.make_command_url('/guest_arrange', {'nxcid':channel['owner_token']});
  }
  if (channel.type=='Q') {
    return Serdes.make_command_url('/video_arrange', {'nxcid':channel['owner_token']});
  }
  return Serdes.make_command_url('/arrange', {'nxcid':channel['owner_token']});
};

hide_rate = function(){
  $('select').css("visibility", "visible");
  getObject('ticker_holder').innerHTML = '';
  $('#inline_rate').hide();
  $('#header_holder').hide();
  $('#header_holder').unbind('click');
};

hideMenu = function(){}; // called by grid view
