"use strict";
/**
 * 3min JS $Revision: 1.21 $
 * <p>
 * ThreeminPlayer v1.2: 3minutes Flash-Plugin connection and control.
 * <p>
 * 
 * @author Alexander Lehnert <a dot lehnert at telekom dot de>
 * @since 23.02.2009
 * @version 1.0
 * @copyright 2009 DTAG P&I Berlin
 */
  
/**
 * Create a player instance for a given SWF name. The {@ThreeminPlayer} is a wrapper class to hold one instance of the 3min-Flash-Player.
 * It controlls the acccess of the player function like {#playMedia(uint)}, {#stop()} and {#play()} and create a JS to AS bridge to interact
 * with the Flash-Player. to create a {@ThreeminPlayer} embedd him over: <p>
 * <code>
 * var threeminPlayer = new ThreeminPlayer();
 * threeminPlayer.embedd( "flash_div" );  // holds the div to embedd the <OBJECT> into
 * </code>
 * <p>
 * alos You can use a spezial *.swf URL and a {@PlayerConfig} like:<p>
 * <code>
 * var config = new PlayerConfig();
 * config.setPlayerName( "MY_3MIN_PLAYER" );
 * 
 * var threeminPlayer = new ThreeminPlayer();
 * threeminPlayer.setConfig( config );
 * threeminPlayer.embedd( "flash_object", "http://foo.bar.com" );
 * </code>
 * 
 * @depends prototype.js
 * @depends swfobject.js 
 * 
 * @param {String} swfName to connect with (@Optional you can let the {@ThreeminPlayer} define a default name)
 */
var ThreeminPlayer = function(swfName){

  // remember swf-name
  this._swfName = swfName;
  
  // create event listener List
  this._callbacks = new Array();
  
  // player config
  this._config = new PlayerConfig();
  
  // current media as JSON
  this._jsonMedia = null;
  
  this._canPlayMedia = true;
  
  // setup player name
  if( null != swfName )
    this._config.setPlayerName( swfName );
  else
    this._swfName = this._config.getPlayerName();
    
  // register in global Player Map
  ThreeminPlayer.PLAYER_MAP[ swfName ] = this;
  ThreeminPlayer.PLAYER_COUNT          = 0;
  
};        

/**
 * Embedd the 3min-Player-SWF in the given div with the current configuration {@see ThreeminPlayer.setConfig}.
 * 
 * @param {String} divID if the div to embedd the Palyer 
 */
ThreeminPlayer.prototype.embedd = function( divID, swfURL ) {
  var playerVersion = swfobject.getFlashPlayerVersion(); // returns a JavaScript object
  var majorVersion = playerVersion.major; // access the major, minor and release version numbers via their respective properties

  // console.log( playerVersion[0] + "." + playerVersion[1] + "." + playerVersion[2] + " \n " + majorVersion );
  swfobject.embedSWF( ( null == swfURL ? ThreeminPlayer.SWF_PLAYER_URL : swfURL ), 
                      divID, 
                      this._config.getDimWidth(),
                      this._config.getDimHeight(), 
                      "9.0.115", // Bugfix for IE6 Fullscreen in transparent mode. First Version with H264 support is "9.0.115" 
                      ThreeminPlayer.SWF_INSTALL_URL, 
                      this._config.getFlashVars(),
                      this._config.getParameter(), 
                      this._config.getAttributes() );
};

/**
 * Setup the {@see PlayerConfig} to manage the Layout and Setup of the 3min Player
 * 
 * @param {PlayerConfig} config to use by this Player
 */
ThreeminPlayer.prototype.setConfig = function( config ) {
  this._config = config;
  this._swfName = this._config.getPlayerName();
  ThreeminPlayer.PLAYER_MAP[ this._swfName ] = this;
};

ThreeminPlayer.prototype.getConfig = function(config){
  return this._config;
};

/**
 * Play a media by his ID. Id must be an <code>uint</code>. If the ID bad or not exist
 * the player proceed the {@see PlayerEvent.PLAYER_ERROR_MID} event.
 * 
 * @param {uint} mid ID of the media to play 
 */
ThreeminPlayer.prototype.playMedia = function( mid ) {
  return callJStoAS(mid, ThreeminPlayer.PLAY_MEDIA, this._swfName );
};

/**
 * Dispose the player -> flush the current Streaming cache and close the RTMP[X] connection. Use this function by body=onbeforeunload
 * to allow a fast page switch
 */
ThreeminPlayer.prototype.dispose = function() {
  callJStoAS( ThreeminPlayer.MSG_NONE, ThreeminPlayer.DISPOSE, this._swfName );
}

/**
 * Stopp the playback of the current media. Proceed a {@see PlayerEvent.PLAYER_ERROR_MID} event.
 * If no ID set in the player (nothing is playing).
 */
ThreeminPlayer.prototype.stop = function() {
  callJStoAS( ThreeminPlayer.MSG_NONE, ThreeminPlayer.STOP_MEDIA, this._swfName );
};

/**
 * Play the current media. Only if you have set a media ID or press Stop in running clip
 */
ThreeminPlayer.prototype.play = function() {
  callJStoAS( ThreeminPlayer.MSG_NONE, ThreeminPlayer.PLAY_CURRENT_MEDIA, this._swfName );
};

/**
 * Returns the current active ID of the media from the player
 */
ThreeminPlayer.prototype.getMediaId = function() {
  return this._jsonMedia.id;
};

/**
 * Returns the active media from the player as JSON
 */
ThreeminPlayer.prototype.getMedia = function() {
  return this._jsonMedia;
};

ThreeminPlayer.prototype.canPlayMedia = function() {
  return this._canPlayMedia;
};

ThreeminPlayer.prototype.allowPlayMedia = function( value ) {
  this._canPlayMedia = value;
};

/**
 * changes the HTML header
 */
ThreeminPlayer.prototype.changeHeader = function( arr ) {
 
  if(arr[0]['channel']) {
    $("media_header_channel").innerHTML = unescape( this.getMedia().channelName );
    $("media_header_channel").href      = unescape( this.getMedia().channelHref );
  }
  
  if(arr[0]['subchannel']) {
    $("media_header_subchannel").innerHTML = unescape( this.getMedia().subchannelName );
    $("media_header_subchannel").href      = unescape( this.getMedia().subchannelHref );
    $("media_header_text").href            = this.getMedia().href;
    $("subchannel_info_link").href      = unescape( this.getMedia().subchannelHref );
    
    if($("subchannel_link_add") && $("subchannel_link_del")){
      $("subchannel_link_add").writeAttribute("onclick", "change_subscription('"+this.getMedia().subchannelID+"', 'add_subscription', 'head', this); return false;"); 
      $("subchannel_link_del").writeAttribute("onclick", "change_subscription('"+this.getMedia().subchannelID+"', 'del_subscription', 'head', this); return false;");
      
      $('subchannel_link_add').hide();
      $('subchannel_link_del').hide();
      
      if(this.getMedia().isSubscript)
        threemin_appear('subchannel_link_del');
      else
        threemin_appear('subchannel_link_add');
    }
  }
  
  if(!this.getMedia().isPartOfSeason && this.getMedia().prefixTitle != '')
    $("media_header_text").innerHTML    = unescape( this.getMedia().prefixTitle ) + arr[0]['episode_separator'] + unescape( this.getMedia().title );
  else if(this.getMedia().isSerial && this.getMedia().prefixSeason != '')
    $("media_header_text").innerHTML    = unescape( this.getMedia().prefixSeason ) + arr[0]['episode_separator'] + unescape( this.getMedia().title );  
  else
    $("media_header_text").innerHTML    = unescape( this.getMedia().title );  
};

ThreeminPlayer.prototype.setupDisplayAd = function() {
  if(typeof(ad_holder) == 'object') {
      callJStoAS( ThreeminPlayer.MSG_NONE, ThreeminPlayer.SETUP_DISPLAY_AD_BEGIN, this._swfName );
      for(var _key in ad_holder){
        switch(_key) {
          case "Leaderboard":   
            callJStoAS( ad_holder[_key], ThreeminPlayer.SETUP_DISPLAY_AD_LEADERBOARD, this._swfName );
            break;
          case "Skyscraper": 
            callJStoAS( ad_holder[_key], ThreeminPlayer.SETUP_DISPLAY_AD_SKYSCRAPER, this._swfName );
            break;
        }
      }
      callJStoAS( ThreeminPlayer.MSG_NONE, ThreeminPlayer.SETUP_DISPLAY_AD_END, this._swfName );
      delete ad_holder;
    }
}

/**
 * Gets simple class name of the ThreeminPlayer
 */
ThreeminPlayer.prototype.getSimpleName = function() {
  return "[ThreeminPlayer]";
};

/**
 * @override obj.toString()
 */
ThreeminPlayer.prototype.toString = function() {
  return this.getSimpleName() + " for SWF: " + this._swfName;
};

/**
 * Get the access name of the used player SWF
 * 
 * @return {String} swfName
 */ 
ThreeminPlayer.prototype.getSWFName = function() {
  return this._swfName;
};

/**
 * Register a event listener for {@see PlayerEvent} with a given delegate function.
 * 
 * @param {String} type from {@see PlayerEvent} to register for
 * @param {Function} delegate to call by Event
 */
ThreeminPlayer.prototype.addEventListener = function( type, delegate ) {
  if( null == delegate )
    return;
  
  // alert( type + " -> " + delegate ) ;
  var typedList = this._callbacks[ type ];
  if (null == typedList) {
    typedList = new Array();
    this._callbacks[type] = typedList = new Array();
  }
  typedList.push( delegate );
};

/**
 * Notify callbacks for givent Event Command.
 * 
 * @param {String} cmd to notify listener for
 */   
ThreeminPlayer.prototype.notify = function( cmd, args ) {
  // only own events
  if( cmd == PlayerEvent.PLAYER_SYNC_MEDIA_COMPLETE ) {
    
    var mediaJsonString = callJStoAS( ThreeminPlayer.MSG_NONE, ThreeminPlayer.GET_MEDIA, this._swfName );
    this._jsonMedia = JSON.parse( mediaJsonString );
    // alert( "2)\n" + this._jsonMedia );
  }
  
  // console.log( cmd, args );
  
  // safety type
  var type = null;
  switch( cmd ) {
    case PlayerEvent.PLAYER_START:        type = PlayerEvent.PLAYER_START;        break;
    case PlayerEvent.PLAYER_READY:        type = PlayerEvent.PLAYER_READY;        break;
    case PlayerEvent.PLAYER_PAUSE:        type = PlayerEvent.PLAYER_PAUSE;        break;
    case PlayerEvent.PLAYER_RESUME:       type = PlayerEvent.PLAYER_RESUME;       break;
    case PlayerEvent.PLAYER_STOP:         type = PlayerEvent.PLAYER_STOP;         break;
    case PlayerEvent.PLAYER_COMPLETE:     type = PlayerEvent.PLAYER_COMPLETE;     break;
    case PlayerEvent.PLAYER_REQUEST_PREW: type = PlayerEvent.PLAYER_REQUEST_PREW; break;
    case PlayerEvent.PLAYER_REQUEST_NEXT: type = PlayerEvent.PLAYER_REQUEST_NEXT; break;
    case PlayerEvent.PLAYER_ERROR:        type = PlayerEvent.PLAYER_ERROR;        break;
    case PlayerEvent.PLAYER_LOADING:      type = PlayerEvent.PLAYER_LOADING;      break;
    case PlayerEvent.PLAYER_LOADING_FIN:  type = PlayerEvent.PLAYER_LOADING_FIN;  break;
    case PlayerEvent.PLAYER_CLIP_CLICK:    type = PlayerEvent.PLAYER_CLIP_CLICK;    break;
    case PlayerEvent.PLAYER_CLIP_CLICK:    type = PlayerEvent.PLAYER_CLIP_CLICK;    break;
    case PlayerEvent.PLAYER_PLAY_MEDIA:    type = PlayerEvent.PLAYER_PLAY_MEDIA;   break;
    case PlayerEvent.PLAYER_PLAY_PROMO:    type = PlayerEvent.PLAYER_PLAY_PROMO;    break;
    case PlayerEvent.PLAYER_SYNC_MEDIA_COMPLETE:type = PlayerEvent.PLAYER_SYNC_MEDIA_COMPLETE; break;
    
    case PlayerEvent.PLAYER_ENTER_FULLSCREEN:    type = PlayerEvent.PLAYER_ENTER_FULLSCREEN;  break;
    case PlayerEvent.PLAYER_LEAVE_FULLSCREEN:    type = PlayerEvent.PLAYER_LEAVE_FULLSCREEN;  break;
    
    // Playlist-Player Add Commands
    case PlayerEvent.PLAYER_REQUEST_AD:           
      type = PlayerEvent.PLAYER_REQUEST_AD;         
      // this.setupDisplayAd();
      break;
    case PlayerEvent.PLAYER_REQUEST_AD_TIMEOUT:   type = PlayerEvent.PLAYER_REQUEST_AD_TIMEOUT; break;
    
    // Known Errors
    case PlayerEvent.PLAYER_ERROR_MID:            type = PlayerEvent.PLAYER_ERROR_MID;          break;
    case PlayerEvent.PLAYER_ERROR_NOTHING_PLAY:   type = PlayerEvent.PLAYER_ERROR_NOTHING_PLAY; break;
    
    case PlayerEvent.SCREENSHOT_SAVED:            type = PlayerEvent.SCREENSHOT_SAVED; break;
    case PlayerEvent.OPEN_MEDIATHEK:              type = PlayerEvent.OPEN_MEDIATHEK; break;
  }
  // unknown type ???
  if( type == null ) {
    // console.warn( "Unknown Player-CMD: " + cmd );
    return;
  }
  
  // create an fire event
  var event = new PlayerEvent( type );
  var typedList = this._callbacks[ type ];
  var delegate  = null;
  
  // notify registered listener
  // for (var index in typedList) {
  for( var index =0; index <typedList.length; index++) {
    delegate = typedList[ index ];
    delegate( event );
  }
};

/** URL of the 3min API-Player */
ThreeminPlayer.SWF_PLAYER_URL  = "http://www.3min.de/flash/3minPlayer.swf?v=1.4.1"; 

/** URL of the Flash express install SWF */
ThreeminPlayer.SWF_INSTALL_URL = "http://www.3min.de/flash/expressInstall.swf"

/** Global Map with all ThreeMin Player */
ThreeminPlayer.PLAYER_MAP = new Array();
/** global count of Player */
ThreeminPlayer.PLAYER_COUNT = 0;

/** SWF msg NONE */
ThreeminPlayer.MSG_NONE = "NULL";

/** SWF cmdto dispose Playback*/
ThreeminPlayer.DISPOSE = "DISPOSE";

/** SWF cmd to begin Play */
ThreeminPlayer.PLAY_MEDIA = "PLAY_MEDIA";
/** SWF cmd to stop Play */
ThreeminPlayer.STOP_MEDIA = "STOP_MEDIA";
/** SWF cmd to begin Play a specific media by his ID */
ThreeminPlayer.PLAY_CURRENT_MEDIA = "PLAY_CURRENT_MEDIA";
/** SWF cmd to get the current MID */
ThreeminPlayer.GET_MID    = "GET_MID";
/** SWF cmd to get the current Media as JSON */
ThreeminPlayer.GET_MEDIA    = "GET_MEDIA";

/** Setup display ad begin */
ThreeminPlayer.SETUP_DISPLAY_AD_BEGIN = "SETUP_DISPLAY_AD_BEGIN";
/** Setup display ad end */
ThreeminPlayer.SETUP_DISPLAY_AD_END = "SETUP_DISPLAY_AD_END";

ThreeminPlayer.SETUP_DISPLAY_AD_SKYSCRAPER   = "SETUP_DISPLAY_AD_SKYSCRAPER";
ThreeminPlayer.SETUP_DISPLAY_AD_LEADERBOARD     = "SETUP_DISPLAY_AD_LEADERBOARD";

/** Return CMD by unknown message */
ThreeminPlayer.UNKNOWN_MESSAGE_ERROR = "UNKNOWN_MESSAGE_ERROR";
/** Handshake SWF call he is redy */
ThreeminPlayer.HANDSHAKE_SWF = "SWF_IS_REDY";
/** Handshake ThreeminPlayer call he is redy */
ThreeminPlayer.HANDSHAKE_JS  = "JS_IS_REDY";

/**
 * Create a PlayerEvent instance for given type
 * 
 * @param {String} type of the Event
 */
var PlayerEvent = function( type ) {
  this._type = type;
};
  
/**
 * Get the event type
 * 
 * @return {String} type of the event
 */
PlayerEvent.prototype.getType = function( ) {
  return this._type;
};
  
/**
 * @override obj.toString()
 */
PlayerEvent.prototype.toString = function() {
  return "[PlayerEvent] " + this._type;
};

/** SWF-CMD: Player ist started */
PlayerEvent.PLAYER_START        = "PLAYER_START";
/** SWF-CMD: Player complete load and ready to Play */
PlayerEvent.PLAYER_READY        = "PLAYER_READY";
/** SWF-CMD: Player is paused */
PlayerEvent.PLAYER_PAUSE        = "PLAYER_PAUSE";
/** SWF-CMD: Player is resumed */
PlayerEvent.PLAYER_RESUME       = "PLAYER_RESUME";
/** SWF-CMD: Player is stopped */
PlayerEvent.PLAYER_STOP         = "PLAYER_STOP";
/** SWF-CMD: Player is requesting prew Media */
PlayerEvent.PLAYER_REQUEST_PREW = "PLAYER_REQUEST_PREW";
/** SWF-CMD: Player is requesting next Media */
PlayerEvent.PLAYER_REQUEST_NEXT = "PLAYER_REQUEST_NEXT";
/** SWF-CMD: Player has complete played Media*/
PlayerEvent.PLAYER_COMPLETE     = "PLAYER_COMPLETE";
/** SWF-CMD: Player begin to play media */
PlayerEvent.PLAYER_PLAY_MEDIA   = "PLAYER_PLAY_MEDIA";
/** SWF-CMD: Player begin to play preroll*/
PlayerEvent.PLAYER_PLAY_PROMO   = "PLAYER_PLAY_PROMO";
  
/** SWF-CMD: Player is loading media */
PlayerEvent.PLAYER_LOADING      = "PLAYER_LOADING";
/** SWF-CMD: Player finished loading media */
PlayerEvent.PLAYER_LOADING_FIN  = "PLAYER_LOADING_FIN";

/** SWF-CMD: Player finished loading media */
PlayerEvent.PLAYER_SYNC_MEDIA_COMPLETE = "PLAYER_SYNC_MEDIA_COMPLETE";

/** SWF-CMD: Player opening URL */
PlayerEvent.PLAYER_CLIP_CLICK = "PLAYER_CLIP_CLICK";

/** SW-CMD: Enter fullscreen */
PlayerEvent.PLAYER_ENTER_FULLSCREEN = "PLAYER_ENTER_FULLSCREEN";
/** SW-CMD: Leave fullscreen */
PlayerEvent.PLAYER_LEAVE_FULLSCREEN = "PLAYER_LEAVE_FULLSCREEN";
  
/** SWF-CMD: Player has detect a error */
PlayerEvent.PLAYER_ERROR              = "PLAYER_ERROR";
/** SWF-CMD: Player has no media ID */
PlayerEvent.PLAYER_ERROR_MID          = "PLAYER_ERROR_MID";
/** SWF-CMD: Player has nothing to play */
PlayerEvent.PLAYER_ERROR_NOTHING_PLAY = "PLAYER_ERROR_NOTHING_PLAY";

/** SWF-CMD: Playlist player request ad */
PlayerEvent.PLAYER_REQUEST_AD = "PLAYER_REQUEST_AD";
/** SWF-CMD: Playlist player request Ad timeout reached */
PlayerEvent.PLAYER_REQUEST_AD_TIMEOUT = "PLAYER_REQUEST_AD_TIMEOUT";

/** SWF CMD Screenshot saved */
PlayerEvent.SCREENSHOT_SAVED = "SCREENSHOT_SAVED";
/** SWF CMD to open the mediathek */
PlayerEvent.OPEN_MEDIATHEK = "OPEN_MEDIATHEK";

/**
 * Calls the given swf file with a command and a parameter.
 * 
 * @param parameter to proceed to SWF
 * @param command to invoke by the SWF-Player like PLAY_MEDIA
 * @param swfName name choose by initialize the ThreeminPlayer. The Identify the SWF inside the Doom-Tree and let us get a referenz to them.
 */ 
function callJStoAS( parameter, command, swfName ) {
  try {
    //console.log( (command ? command : ThreeminPlayer.CMD) + ':' + parameter );
    return getSwfContainer(swfName).callJStoAS( (command ? command : ThreeminPlayer.CMD) + ':' + parameter );
  } 
  catch(error) {
    console.error( error );
    return -1;
  }
};

/**
 * Calls come from Actionscript and proceed a parameter value as message
 * 
 * @param swfName name of the SWF Container
 * @param cmd to execute or proceeed
 * @param args Optional argument
 */ 
function call3minJSPlayerControl( swfName, cmd, args ) {
  if( cmd == ThreeminPlayer.HANDSHAKE_SWF ) {
    return ThreeminPlayer.HANDSHAKE_JS;
  }
  
  var player = ThreeminPlayer.PLAYER_MAP[ swfName ];
  // console.log( swfName + " - " + cmd + " - " + args + " - " + player );
  if( null != player ) {
    player.notify( cmd, args );
    return "OK";
  }
  return "NO_PLAYER_FOUND";
};

/**
 * Gets the reference to the requested swf file. The name is set by 
 * startup over new SWFObject( "foo.swf", "NAME", ... );
 * 
 * @param swfName name choose by initialize SWFObject (swfobject.js)
 * @return reference to the requested swf file
 */ 
function getSwfContainer( swfName ) {
    /*
     * This is important because Microsoft uses "Document" when
     * referring to the page and Mac uses "Window."
     */
    var isIE = (window == null); // navigator.appName("Microsoft") != -1;
    return (isIE) ? window[swfName] : document[swfName];
};

/**
 * Create a Player configuration to setup parameter for the ThreeminPlayer create function
 */
var PlayerConfig = function() {
  
  // basic setup
  this._baseHost    = "http://www.3min.de/";                        // standart host
  this._player_name = "3min_Player_" + ThreeminPlayer.PLAYER_COUNT; // Name of SWF to autentificate by ThreeminPlayer
  this._useLoggedIn = "false";                                      // determinates if the user is logged in
  this._localConnectionName = "";
  
  // click targets
  this._videoLink = null;
  this._tabLink   = null;
  
  // playlist settings
  this._playlistNavType = PlayerConfig.PLAYLIST_NAV_STANDARD;
  this._playlists     = new Array();
  this._playlistCount = 0;
  
  this._plugins      = new Array();
  this._pluginsCount = 0;
  
  // create dafault configuration
  this._mediaid          = "0";         // start media-ID for player. Is Optional you can the id after the Redy event
  this._i18n_descriptor  = "de";        // lang to use
  this._show_logo        = "true";      // determinates if we show the 3min logo in the upper right area of the Video-Area
  this._show_options     = "true";      // determinates if we show send / add and embedd option on the right side of the control
  this._show_currently_playing = "true";// determinates if the currently playing media is visible in the upper left corner
  this._allow_fullscreen = "true";      // determinates if we show the upper right Fullscreen-Button
  this._show_mediathek   = "false";     // determinates if we show the Mediathek
  this._autostart        = "false";     // loads automaticly the mediaid clip and start him
  
  // debug options
  this._debug_level     = "warn";   // Player Debug level -> Default = error
  this._maintenanceMode = "false";  // Player maintenance mode -> enable the Stream and RAM usage in the Player
  
  this._useTestAddLevelIdEW = "test";
  this._useTestAddLevelIdLC = "test";
  
  this._useTestAddCallsEW = "false";  // Player use Test Add-Calls EyeWonder
  this._useTestAddCallsLC = "false";  // Player use Test Add-Calls LighningCast
  this._useTestAddCalls   = "false";  // flag if every type of test ad calls in use
  
  // create default settings
  this._default_Bandwithdetection = "true";                    // enables / disable bandwithdetection
  this._default_showTimeRemaining = "true";                    // time state +/-
  this._default_volume            = "0.5";                    // startup volume
  this._default_quality           = PlayerConfig.HIGH_QUALITY;// startup quality
  
  // create defaults
  this._endScenarioStrict = "false";
  this._endScenario       = PlayerConfig.END_SCENARIO_RELATED; // end scenario -> wath is shown by clip end
  this._defaultStartup    = PlayerConfig.FIRST_MEDIA;          // default starup position of Playlist
  
  // create default device identifyer
  this._deviceIdentifyer = PlayerConfig.DEVICE_EXTERN;
  
  // create default html params
  this._dimWidth  = "848";
  this._dimHeight = "480";
  this._divBg     = null; //"0x000000";
  
  // create default Ad-Params
  this._prerollRatio        = 0;        // Modullo ratio to play preroll Video-Ads for
  this._prerollOnFirstClip  = "false";  // play preroll on the first clip
  
  // video ad visualizer URL
  this._vavURL = null;
};
  
/**
 * @override obj.toString()
 */
PlayerConfig.prototype.toString = function() {
  return "[PlayerConfig] " + this.getFlashVars();
};

PlayerConfig.prototype.getBaseHost = function() {
  return this._baseHost;
};

/**
 * Get the Name of the Player
 * 
 * @return {String} player Name
 */
PlayerConfig.prototype.getPlayerName = function(){
  return this._player_name;
};

/**
 * Get the width of the Player (HTML-Div width)
 * 
 * @return {String} player width
 */
PlayerConfig.prototype.getDimWidth = function(){
  return this._dimWidth;
};

/**
 * Get the height of the Player (HTML-Div height)
 * 
 * @return {String} player height
 */
PlayerConfig.prototype.getDimHeight = function() {
  return this._dimHeight;
};

/**
 * Get the flash vars
 * 
 * @return {Object} flash vars
 */
PlayerConfig.prototype.getFlashVars = function() {
  var vars = {
      // changeable config vars
      media_id:                   this._mediaid,
      i18n_descriptor:            this._i18n_descriptor,
      show_logo:                  this._show_logo,
      show_options:               this._show_options,
      show_currently_playing:     this._show_currently_playing,
      show_fullscreen:            this._allow_fullscreen,
      show_mediathek:             this._show_mediathek,
      autostart:                  this._autostart,
      default_quality:            this._default_quality, 
      default_showTimeRemaining:  this._default_showTimeRemaining, 
      default_volume:             this._default_volume,
      default_bandwith_detection: this._default_Bandwithdetection, 
      is_logged_in:               this._useLoggedIn,
      player_name:                this._player_name,
      local_connection_name:      this._localConnectionName,
      end_scenario:               this._endScenario,
      end_scenario_strict:        this._endScenarioStrict,
      default_playlist_startup:   this._defaultStartup,
      device_identifyer:          this._deviceIdentifyer,
      nav_type_playlist:          this._playlistNavType,
      
      // debuging Options
      debug_level:                this._debug_level,
      maintance_mode:             this._maintenanceMode,
      
      test_add_calls:             this._useTestAddCalls,
      test_add_calls_ew:          this._useTestAddCallsEW,
      test_add_calls_lc:          this._useTestAddCallsLC,
      test_add_level_id_ew:       this._useTestAddLevelIdEW,
      test_add_level_id_lc:       this._useTestAddLevelIdLC,
      
      // Ad-Params
      promo_ratio:                this._prerollRatio,
      promo_on_first_clip:        this._prerollOnFirstClip,
            
      // unchangeable URL-Vars
      base_host:                  this._baseHost
    };
    
    // add visualizer URL => if exist
    if( this._vavURL != null )
      vars["vav_plugin"] = this._vavURL;
     
    // add click targets
    if( this._videoLink != null )
       vars["video_link_url"] = this._videoLink;
     if( this._tabLink != null )
       vars["tab_link_url"]   = this._tabLink;
      
    // append playlist
    var plist;
    for( var entry in this._playlists ) {
      plist = this._playlists[ entry ];
      
      if (plist  && ( typeof plist == 'object' ) ) {
        // check if alredy registed and seperate multiple Plalyist by ';'
        if( vars[plist.type] != null )
          vars[plist.type] = vars[plist.type] + ";" + "[" + plist.index + "|" + plist.name + "|" + plist.params + "|" + plist.active + "]";
        else
          vars[plist.type] = "[" + plist.index + "|" + plist.name + "|" + plist.params + "|" + plist.active  + "]";
      }
    }
    
    // proceed plugins
    if( this._pluginsCount > 0 ) {
      var plugin;
      for( var pluginEntry in this._plugins ) {
        plugin = this._plugins[ pluginEntry ];
        
        if (plugin  && ( typeof plugin == 'object' ) ) {
          // check if alredy registed and seperate multiple Plugins by ';'
          if( vars["plugins"] != null )
            vars["plugins"] = vars["plugins"] + ";" + "[" + plugin.url + "|" + plugin.clazz + "]";
          else 
            vars["plugins"] = "[" + plugin.url + "|" + plugin.clazz + "]";
        }
      }
      
    }
    return vars;
};

/**
 * Get the parameter
 * 
 * @return {Object} parameter vars
 */
PlayerConfig.prototype.getParameter = function() {
  return {
      quality:           "high",                 // Flash Params => quality
      allowfullscreen:   this._allow_fullscreen, // Allow or deny Fullscreen. Note if you deny FS but use the show_fullscreen option you get an SecurityError by clicking this button
      allowScriptAccess: "always",                // allow SWF to communicate with JS Functions
      bgcolor:           this._divBg, //( null == this._divBg ? "" :  this._divBg ),            // Backgroundcolor of the Player -> is showing while the SWF is loading. Use the Color of the underlaying DIv or so on.
      wmode:             "opaque", //( null == this._divBg ? "opaque" : "normal" ),
      scale:             "noborder"
    };
};

/**
 * Get the attributes
 * 
 * @return {Object} attribute vars
 */
PlayerConfig.prototype.getAttributes = function() {
  return {
      id:   this._player_name, // setup SWF ID
      name: this._player_name  // setup SWF Name to connect SWF with ThreeminPlayer JS
    };
};

/**
 * @param {String} baseHost  Basic Host URL
 */
PlayerConfig.prototype.setBaseHost = function( baseHost ) {
  if( baseHost.lastIndexOf("/") != baseHost.length-1 )
    baseHost += "/";
  this._baseHost = baseHost;
};

/**
 * @param {String} videoClickLink URL
 */
PlayerConfig.prototype.setClickTargetVideo = function( link ) {
  this._videoLink = link;
};

/**
 * @param {String} tabClickLink URL
 */
PlayerConfig.prototype.setClickTargetTab = function( link ) {
  this._tabLink = link;
};

/**
 * @param {Boolean} userLoggedIn determinates if the user is logged in
 */
PlayerConfig.prototype.setUserLoggedIn = function( userLoggedIn ) {
  this._useLoggedIn = ""+userLoggedIn;
};

/**
 * @param {String} playlistNavType to use by Player-Playlist can be PlayerConfig.PLAYLIST_NAV_STANDARD xor PlayerConfig.PLAYLIST_NAV_OVERLAY 
 */
PlayerConfig.prototype.setPlaylistNavigationType = function( type ) {
  this._playlistNavType = type;
};

/**
 * @param {String} playlistURL  to use by requesting a Playlist
 */
PlayerConfig.prototype.addPlaylist = function( playlistType, playlistName, values, activeFlag ) {
//  for( var entry in this._playlists ) {
//    if( entry == playlistType )
//      return; // also registered
//  }
  
  this._playlists.push( {index: this._playlistCount, type: playlistType, name: escape( playlistName ), params: values, active: activeFlag } );
  // this._playlists[ playlistType ] = {index: this._playlistCount, type: playlistType, name: playlistName, params: values};
  this._playlistCount++;
};

/**
 * @param {String} pluginURL to load by the Player
 */
PlayerConfig.prototype.registerPlugin = function( pluginUrl, className ) {
  this._plugins.push( {url: pluginUrl, clazz: className} );
  this._pluginsCount++;
}

/**
 * @param {String} divBg Hex string of the background colot
 */
PlayerConfig.prototype.setDivBgColor = function( divBg ) {
  this._divBg = divBg;
};

/**
 * @param {uint} width to use
 */
PlayerConfig.prototype.setDimension = function( width, height ) {
  this._dimWidth  = ""+width;
  this._dimHeight = ""+height;
};
  
/**
 * @param {String} mid to set for playing
 */
PlayerConfig.prototype.setMediaID = function( mid ) {
  this._mediaid = mid;
};

/**
 * @param {String} i18n is the sued language Descripto by ISO (de, en, fr, ... )
 */
PlayerConfig.prototype.setI18NDescriptor = function( i18n ) {
  this._i18n_descriptor = i18n;
};

/**
 * @param {String} debugLevel is the standart debug fopr Player (debug, info, warn, error, fatal)
 */
PlayerConfig.prototype.setDebugLevel = function( debugLevel ) {
  this._debug_level = debugLevel;
};

/**
 * @param {Boolean} value determintes if the player use Test-Add-Calls for EyeWonder
 */
PlayerConfig.prototype.useTestAddCallsEW = function( value, levelID ) {
  this._useTestAddCallsEW = ""+value;
  this._useTestAddCalls = this._useTestAddCallsLC || this._useTestAddCallsEW;
  
  this._useTestAddLevelIdEW = levelID;
  
};

/**
 * @param {Boolean} value determintes if the player use Test-Add-Calls for LighningCast
 */
PlayerConfig.prototype.useTestAddCallsLC = function( value, levelID ) {
  this._useTestAddCallsLC = ""+value;
  this._useTestAddCalls = this._useTestAddCallsLC || this._useTestAddCallsEW;
  
  this._useTestAddLevelIdLC = levelID;
};

/**
 * @param {Boolean} value determinates if the player run in the maintenance mode
 */
PlayerConfig.prototype.setMaintenanceMode = function( value ) {
  this._maintenanceMode = ""+value;
};

/**
 * @param {String} playerName setups the name for the Palyer
 */
PlayerConfig.prototype.setPlayerName = function( playerName ) {
  this._player_name = playerName;
};

/**
 * @param {Boolean} showObtions determinates show options state
 */
PlayerConfig.prototype.showLogo = function( showLogo ) {
  this._show_logo = ""+showLogo;
};

/**
 * @param {Boolean} showOptions determinates show options state
 */
PlayerConfig.prototype.showOption = function( showOptions ) {
  this._show_options = ""+showOptions;
};

/**
 * @param {Boolean} showCurrentlyPlaying determinates to show the currently Playing Media in the upper left corner
 */
PlayerConfig.prototype.showCurrentlyPlaying = function( showCurrentlyPlaying ) {
  this._show_currently_playing = ""+showCurrentlyPlaying;
};

/**
 * @param {Boolean} allowFullscreen determinates show fullscreen button
 */
PlayerConfig.prototype.allowFullscreen = function( allowFullscreen ) {
  this._allow_fullscreen = ""+allowFullscreen;
};

/**
 * @param {Boolean} allowFullscreen determinates show fullscreen button
 */
PlayerConfig.prototype.showMediathek = function( value ) {
  this._show_mediathek = ""+value;
};

/**
 * @param {String} lcName a Name postfix to append to the LocalConnection of the threeminPlayer. That is nessesary 
 *                        to allow multiply insances of the player with different local connections. If you don't set 
 *                        this attribute only one LocalConnection is allowef.
 */
PlayerConfig.prototype.setLocalConnectionName = function( lcName ) {
  this._localConnectionName = lcName;
};

/**
 * @param {Boolean} autostart determinates autostart state
 */
PlayerConfig.prototype.setAutostart = function( autostart ) {
  this._autostart = ""+autostart;
};

/**
 * @param {String} quality Video-Qality to use available ar PlayerConfig.HIGH_QUALITY, PlayerConfig.MEDIUM_QUALITY, PlayerConfig.LOW_QUALITY
 */
PlayerConfig.prototype.setDefaultQuality = function( quality ) {
  this._default_quality = quality;
};

/**
 * @param {float} volume to use as value between 0.0 and 1.0
 */
PlayerConfig.prototype.setDefaultVolume = function( volume ) {
  this._default_volume = volume;
};

/**
 * @param {Boolean} value setup show time remaining
 */
PlayerConfig.prototype.setDefaultShowTimeRemaining = function( value ) {
  this._default_showTimeRemaining = ""+value;
};

/**
 * @param {Boolean} value setup player to use automaticly bandwith detection
 */
PlayerConfig.prototype.setDefaultUseBandwithDetection = function( value ) {
  this._default_Bandwithdetection = ""+value;
};              

/**
 * @param {Boolean} value determinates if the preroll video add should be play on the first clip
 */
PlayerConfig.prototype.setPrerollPlayOnFirstClip = function( value ) {
  this._prerollOnFirstClip = ""+value;
};              

/**
 * @param {uint} value determinates the modulo on play preroll clips
 */
PlayerConfig.prototype.setPrerollPlayRatio = function( value ) {
  this._prerollRatio = value;
};

/**
 * @param {String} end scenarion constant to use
 */
PlayerConfig.prototype.setEndScenario = function( scenario ) {
  this._endScenario = scenario;
};

/**
 * @param {Boolean}end scenarion strict mode to use
 */
PlayerConfig.prototype.setStrictEndScenario = function( value ) {
  this._endScenarioStrict = ""+value;
};

/**
 * @param {String} end scenarion constant to use
 */
PlayerConfig.prototype.setDefaultStartup = function( defaultStartup ) {
  this._defaultStartup = defaultStartup;
};

/**
 * @param {String} end scenarion constant to use
 */
PlayerConfig.prototype.setDeviceIdentifyer = function( identifyer ) {
  this._deviceIdentifyer = identifyer;
};

/**
 * @param {String} vavURL to load VideoAddVisualizer
 */
PlayerConfig.prototype.setVAV = function( vavURL ) {
  this._vavURL = vavURL;
};

/** BACKWARD COMPATIBILITY TO INTGRATION VERSION 0.7 **/
PlayerConfig.prototype.showMediaInfo  = function( value ) { /* pass */};
PlayerConfig.prototype.showNextButton = function( value ) { /* pass */};
PlayerConfig.prototype.showPrevButton = function( value ) { /* pass */};

// Identifyer
PlayerConfig.DEVICE_EXTERN   = "web_extern";   // web_extern => Default
PlayerConfig.DEVICE_EMBEDDED = "web_embedded"; // web_embedded
PlayerConfig.DEVICE_VOD      = "web_vod";      // web_vod
PlayerConfig.DEVICE_PLAYLIST = "web_playlist"; // web_playlist
PlayerConfig.DEVICE_MY_3MIN  = "web_my_3min";  // web_playlist
PlayerConfig.DEVICE_ADMIN    = "web_admin";    // web_admin
PlayerConfig.DEVICE_SHOWROOM = "web_showroom"; // web_admin

// Playback Constants
PlayerConfig.LAST_MEDIA      = "default_play_last_media";
PlayerConfig.FIRST_MEDIA     = "default_play_first_media";

// Quality constants
PlayerConfig.HIGH_QUALITY   = "HQ";  // high quality   = 1200 kb/s
PlayerConfig.MEDIUM_QUALITY = "MQ"; // medium quality = 900 kb/S
PlayerConfig.LOW_QUALITY    = "LQ"; // low quality    = 600 kb/s

// end scenarion is shown by clip end
PlayerConfig.END_SCENARIO_RESET     = "end_scenario_reset";
PlayerConfig.END_SCENARIO_RELATED   = "end_scenario_related";
PlayerConfig.END_SCENARIO_UPCOMMING = "end_scenario_upcomming";
PlayerConfig.END_SCENARIO_NONE      = "end_scenario_none";

// playlist Navigation Types
PlayerConfig.PLAYLIST_NAV_STANDARD = "plalyist_nav_standart";
PlayerConfig.PLAYLIST_NAV_OVERLAY  = "plalyist_nav_overlay";

// playlist types
PlayerConfig.PLAYLIST_SUBCHANNEL = "playlist_subchannel";
PlayerConfig.PLAYLIST_RELATED    = "playlist_related";
PlayerConfig.PLAYLIST_TIMELINE   = "playlist_timeline";
PlayerConfig.PLAYLIST_USER_PLIST = "playlist_user_playlist";
PlayerConfig.PLAYLIST_USER_ABO   = "playlist_user_abo";

/** 
 * http://www.JSON.org/json2.js
 * 2009-08-17
 * 
 * @See http://www.JSON.org/js.html
 * 
 * This file creates a global JSON object containing two methods: stringify and parse. to allow JSON Parsing if no native JSON available
 * by the Browser
 */

/**
 * Create a Mediathek to control the new Window
 */
var Mediathek = function( threeminPlayder ) {
  
  // basic setup
  this._baseHost    = window.location; //"http://www.3min.de/mediathek";                        // standart host
  this._player_name = "3min_Player_" + ThreeminPlayer.PLAYER_COUNT; // Name of SWF to autentificate by ThreeminPlayer
  
  // create default html params
  this._dimWidth  = screen.width;
  this._dimHeight = screen.height;
  
  // Check OS and Screen size to calibrate Dimension
  this.setDimension( screen.width, screen.height );
  
  this._threeminPlayer = threeminPlayder;
  this._mediathekWindow = null;
  this._mediathekSWF = null;
  this._mediathekOpening = false;
};
  
/**
 * @override obj.toString()
 */
Mediathek.prototype.toString = function() {
  return "[Mediathek] for Player: " + this._player_name;
};

Mediathek.prototype.setMediathekSWF = function( mediathekSWF ) {
  // Reseive mediathekSWF ...
  this._mediathekSWF = mediathekSWF;
  
  var media = this._threeminPlayer.getMedia();
  this._mediathekSWF.sendSignal( Mediathek.SELECT, "["+media.channelID+","+media.subchannelID+",0,"+media.id+"]" );
};

Mediathek.prototype.openMedia = function( jsonMedia ) {
  var mediaMediathek = JSON.parse( jsonMedia ); 
  var mediaPlayer    = this._threeminPlayer.getMedia();
  
  // chek cif we only play a new media form Aktive list
  if ( this._threeminPlayer.canPlayMedia() && ( mediaMediathek.subchannelID == mediaPlayer.subchannelID ) ) {
     //console.warn( "PLAY: " + mediaMediathek ); 
     var ret = this._threeminPlayer.playMedia( mediaMediathek.id );
     // 
  }
  // navigate to URL
  else {
    //console.warn( "URL: " +  mediaMediathek.href );
    window.location = mediaMediathek.href;
  }
};

/**
 * @param {uint} width for the Mediathek
 * @param {uint} height for the Mediathek
 */
Mediathek.prototype.setDimension = function( width, height ) {
  this._dimWidth  = ( width  > 0 ? width  : this._dimWidth  );
  this._dimHeight = ( height > 0 ? height : this._dimHeight );
}

/**
 * @param {uint} width for the Mediathek
 * @param {uint} height for the Mediathek
 */
Mediathek.prototype.setMaxDimension = function( width, height ) {
  if( this._dimWidth > width )
    this._dimWidth = width;
  if( this._dimHeight > height )
    this._dimHeight = height;
}

/**
 * Open the Mediathek in a new Window, like a Pop Up. If the Window alredy opened the Fukus would be changed
 * to this Window.
 */
Mediathek.prototype.open = function() {
  if( this._mediathekOpening )
    return;
  
  this._mediathekOpening = true;
  
  if( null != this._mediathekWindow ) {
    
    // if the Mediathek alredy opened give him the Focus
    if( !this._mediathekWindow.closed ) {
      //Focus Mediathek
      this._mediathekWindow.focus();
      
      // Select Mediathek
      if (this._mediathekSWF) {
        var media = this._threeminPlayer.getMedia();
        this._mediathekSWF.sendSignal( Mediathek.SELECT, "["+media.channelID+","+media.subchannelID+","+media.id+"]" ); 
      }
      
      this._mediathekOpening = false;
      return;
    }
  }
  
  // Open a New Window for the Mediathek
  var options = 'resizable=1, toolbar=0, directories=0, status=0, scrollbars=0, menubar=0';
  this._mediathekWindow = window.open(this.buildTargetURL(),       // URL
                                      Mediathek.WINDOW_NAME,       // Name
                                      'left=20, top=20, width=' + this._dimWidth + ', height=' + this._dimHeight + ', ' + options);
  window.mediathek = this;
  this._mediathekWindow.focus();
  
  this._mediathekOpening = false;
};

Mediathek.prototype.buildTargetURL = function() {
  if( null != this._threeminPlayer ) {
    var media = this._threeminPlayer.getMedia();
    return this._threeminPlayer.getConfig().getBaseHost()  + "mediathek.php";
    // return this._threeminPlayer.getConfig().getBaseHost()  + "mediathek/" + media.channelID + "/" + media.subchannelID + "/" + media.id; 
  } 
  return window.location  + "/mediathek/-1/-1/-1";
};

Mediathek.WINDOW_NAME = "3min_Mediathek";
Mediathek.SELECT = "SELECT";

// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if( this.JSON ) {
  // alert( "Browser contains a Native JSON-Parser. :)"); 
}
else {
  // alert( "No Native JSON-Parser Available -> use own Implementation")
  this.JSON = {};
  
  (function(){
  
    /**
     * Format integers to have at least two digits.
     */
    function f(n){
      return n < 10 ? '0' + n : n;
    }
    
    if (typeof Date.prototype.toJSON !== 'function') {
    
      Date.prototype.toJSON = function(key){
      
        return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' +
        f(this.getUTCMonth() + 1) +
        '-' +
        f(this.getUTCDate()) +
        'T' +
        f(this.getUTCHours()) +
        ':' +
        f(this.getUTCMinutes()) +
        ':' +
        f(this.getUTCSeconds()) +
        'Z' : null;
      };
      
      String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function(key){
        return this.valueOf();
      };
    }
    
    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions
      '\b': '\\b',
      '\t': '\\t',
      '\n': '\\n',
      '\f': '\\f',
      '\r': '\\r',
      '"': '\\"',
      '\\': '\\\\'
    }, rep;
    
    /**
     * If the string contains no control characters, no quote characters, and no
     * backslash characters, then we can safely slap some quotes around it.
     * Otherwise we must also replace the offending characters with safe escape
     * sequences.
     *
     * @param {Object} string
     */
    function quote(string){
    
      escapable.lastIndex = 0;
      return escapable.test(string) ? '"' +
      string.replace(escapable, function(a){
        var c = meta[a];
        return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
      }) +
      '"' : '"' + string + '"';
    }
    
    /**
     * Produce a string from holder[key].
     *
     * @param {Object} key
     * @param {Object} holder
     */
    function str(key, holder){
    
      var i, // The loop counter.
          k, // The member key.
          v, // The member value.
          length, mind = gap, partial, value = holder[key];
      
      // If the value has a toJSON method, call it to obtain a replacement value.
      if (value && typeof value === 'object' &&
      typeof value.toJSON === 'function') {
        value = value.toJSON(key);
      }
      
      // If we were called with a replacer function, then call the replacer to
      // obtain a replacement value.
      if (typeof rep === 'function') {
        value = rep.call(holder, key, value);
      }
      
      // What happens next depends on the value's type.
      switch (typeof value) {
        case 'string':
          return quote(value);
          
        // JSON numbers must be finite. Encode non-finite numbers as null.
        case 'number':
          return isFinite(value) ? String(value) : 'null';
          
        // If the value is a boolean or null, convert it to a string. Note:
        // typeof null does not produce 'null'. The case is included here in
        // the remote chance that this gets fixed someday.
        case 'boolean':
        case 'null':
          return String(value);
          
        // If the type is 'object', we might be dealing with an object or an array or null.
        case 'object':
          // Due to a specification blunder in ECMAScript, typeof null is 'object', so watch out for that case.
          if (!value) {
            return 'null';
          }
          
          // Make an array to hold the partial results of stringifying this object value.
          gap += indent;
          partial = [];
          
          // Is the value an array?
          if (Object.prototype.toString.apply(value) === '[object Array]') {
          
            // The value is an array. Stringify every element. Use null as a placeholder
            // for non-JSON values.
            length = value.length;
            for (i = 0; i < length; i += 1) {
              partial[i] = str(i, value) || 'null';
            }
            
            // Join all of the elements together, separated with commas, and wrap them in
            // brackets.
            v = partial.length === 0 ? '[]' : gap ? '[\n' + gap +
            partial.join(',\n' + gap) +
            '\n' +
            mind +
            ']' : '[' + partial.join(',') + ']';
            gap = mind;
            return v;
          }
          
          // If the replacer is an array, use it to select the members to be stringified.
          if (rep && typeof rep === 'object') {
            length = rep.length;
            for (i = 0; i < length; i += 1) {
              k = rep[i];
              if (typeof k === 'string') {
                v = str(k, value);
                if (v) {
                  partial.push(quote(k) + (gap ? ': ' : ':') + v);
                }
              }
            }
          }
          else {
            // Otherwise, iterate through all of the keys in the object.
            for (k in value) {
              if (Object.hasOwnProperty.call(value, k)) {
                v = str(k, value);
                if (v) {
                  partial.push(quote(k) + (gap ? ': ' : ':') + v);
                }
              }
            }
          }
          
          // Join all of the member texts together, separated with commas,
          // and wrap them in braces.
          v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
          mind +
          '}' : '{' + partial.join(',') + '}';
          gap = mind;
          return v;
      } // end switch
    } // end function
    
    // If the JSON object does not yet have a stringify method, give it one.
    if (typeof JSON.stringify !== 'function') {
      JSON.stringify = function(value, replacer, space){
      
        // The stringify method takes a value and an optional replacer, and an optional
        // space parameter, and returns a JSON text. The replacer can be a function
        // that can replace values, or an array of strings that will select the keys.
        // A default replacer method can be provided. Use of the space parameter can
        // produce text that is more easily readable.
        var i;
        gap = '';
        indent = '';
        
        // If the space parameter is a number, make an indent string containing that
        // many spaces.
        if (typeof space === 'number') {
          for (i = 0; i < space; i += 1) {
            indent += ' ';
          }
        
        }
        // If the space parameter is a string, it will be used as the indent string.
        else 
          if (typeof space === 'string') {
            indent = space;
          }
        
        // If there is a replacer, it must be a function or an array.
        // Otherwise, throw an error.
        rep = replacer;
        if (replacer && typeof replacer !== 'function' &&
           (typeof replacer !== 'object' ||
           typeof replacer.length !== 'number')) {
          throw new Error('JSON.stringify');
        }
        
        // Make a fake root object containing our value under the key of ''.
        // Return the result of stringifying the value.
        return str('', {
          '': value
        });
      };
    }
    
    // If the JSON object does not yet have a parse method, give it one.
    if (typeof JSON.parse !== 'function') {
      JSON.parse = function(text, reviver){
      
        // The parse method takes a text and an optional reviver function, and returns
        // a JavaScript value if the text is a valid JSON text.
        
        var j;
        
        function walk(holder, key){
        
          // The walk method is used to recursively walk the resulting structure so
          // that modifications can be made.
          var k, v, value = holder[key];
          if (value && typeof value === 'object') {
            for (k in value) {
              if (Object.hasOwnProperty.call(value, k)) {
                v = walk(value, k);
                if (v !== undefined) {
                  value[k] = v;
                }
                else {
                  delete value[k];
                }
              }
            }
          }
          return reviver.call(holder, key, value);
        }
        // Parsing happens in four stages. In the first stage, we replace certain
        // Unicode characters with escape sequences. JavaScript handles many characters
        // incorrectly, either silently deleting them, or treating them as line endings.
        
        cx.lastIndex = 0;
        if (cx.test(text)) {
          text = text.replace(cx, function(a){
            return '\\u' +
            ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
          });
        }
        
        // In the second stage, we run the text against regular expressions that look
        // for non-JSON patterns. We are especially concerned with '()' and 'new'
        // because they can cause invocation, and '=' because it can cause mutation.
        // But just to be safe, we want to reject all unexpected forms.
        
        // We split the second stage into 4 regexp operations in order to work around
        // crippling inefficiencies in IE's and Safari's regexp engines. First we
        // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
        // replace all simple value tokens with ']' characters. Third, we delete all
        // open brackets that follow a colon or comma or that begin the text. Finally,
        // we look to see that the remaining characters are only whitespace or ']' or
        // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
        
        if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
        
          // In the third stage we use the eval function to compile the text into a
          // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
          // in JavaScript: it can begin a block or an object literal. We wrap the text
          // in parens to eliminate the ambiguity.
          
          j = eval('(' + text + ')');
          
          // In the optional fourth stage, we recursively walk the new structure, passing
          // each name/value pair to a reviver function for possible transformation.
          
          return typeof reviver === 'function' ? walk({
            '': j
          }, '') : j;
        }
        
        // If the text is not JSON parseable, then a SyntaxError is thrown.
        
        throw new SyntaxError('JSON.parse');
      };
    }
  }())
};
