/**
 * Key concepts:
 *   - query embeds must be stored for retrieval on callback
 *   - overlays must be stored for each query embed
 *   - query embeds communicate with solr; normal embeds do not
 *   - we use swfobject to embed SWFs in the page
 
 * Objects:
 *   - Overlay (singleton, namespace)
 *   - Overlay.Embed (class)
 *   - Overlay.QueryEmbed (class, inherits from Overlay.Embed)
 *   - Overlay.Overlay (class)
 */ 

"use strict";

//====================================================================
// Overlay Namespace
//====================================================================
var Overlay = Overlay || {
  /**
   * Power constructor which creates a new Embed and writes the embed
   * to the page. Returns the Embed instance object.
   * 
   * Example:
   *
   *   Overlay.embed("embed1", "ZAPPOS", "8a6e3fab...", {...});
   */ 
  embed: function (id, client, overlayGuid, playerOptions, embedOptions) {
    var embed = new Overlay.Embed(id, client, playerOptions, embedOptions);
    
    // TODO: This is a hack for backwards compatibility with deployed
    // embeds. In a future iteration, overlayGuid should be removed as
    // a parameter and passed in flashVars instead.
    embed.playerOptions.flashVars.OVERLAY_GUID = overlayGuid;

    embed.write();
    return embed;
  },

  /**
   * Power constructor which creates a new QueryEmbed, stores the 
   * query embed for callback retrieval and performs the query and
   * embedding. Returns the QueryEmbed instance object.
   * 
   * Example:
   *
   *   Overlay.queryEmbed("embed1", "ZAPPOS", {...}, {...});
   */
  queryEmbed: function (id, client, queryOptions, playerOptions, embedOptions) {
    var embed = new Overlay.QueryEmbed(id, client, queryOptions, playerOptions, embedOptions);
    Overlay.QueryEmbed.embeds[embed.id] = embed;
    embed.queryEmbed();
    return embed;
  },
  
  /**
   * Called after receiving a response from solr following a query
   * embed. The solr response data (in JSON format) passed to this
   * function call. The DOM ID of the embed is passed (embedId) such
   * that we can retrieve the original QueryEmbed instance object.
   */
  loadJSON: function (data) {
    var embedId    = data.responseHeader.params.embedId;
    var queryEmbed = Overlay.QueryEmbed.embeds[embedId];

    if (queryEmbed) {
      queryEmbed.handleQueryResponse(data);
    }
  },
  
  /**
   * Load a javascript file at the specified url into the page. We use
   * this to include SWFObject, as well as for sending query requests
   * to Solr.
   *
   * Example:
   *
   *   Overlay.loadScript("http://example.com/test.js");
   *
   * Appends the following tag to the document head:
   *
   *   <script src="http://example.com/test.js"></script> 
   */ 
  loadScript: function (url) {
    var script = document.createElement("script");
    script.src = url;
    document.getElementsByTagName("head")[0].appendChild(script);
    return script;
  },
  
  /**
   * Merges the specified default options into the target if they
   * don't already exist on the target. Returns the target with merged
   * options. This function is recursive on options that contain sub
   * options. 
   * 
   * Example:
   *
   *   Overlay.mergeOptions({ height: 90 }, { width: 120 })
   *
   * Returns:
   *
   *   { height: 90, width: 120 }
   */
  mergeOptions: function (target, defaults) {    
    target = target || {};

    for (var option in defaults) {
      if (typeof defaults[option] === "object" && target[option] !== undefined) {
        Overlay.mergeOptions(target[option], defaults[option]);
      } else {
        if (target[option] === undefined) {
          target[option] = defaults[option];
        }
      }
    }

    return target;
  },
  
  /**
   * Takes an options object and returns the JSON string
   * representation of the object.
   */
  optionsToString: function (options) {
    var jsonObjects = [];
    var value;

    if (typeof options === "string") {
      return "\"" + options + "\"";
    }
    
    for (var option in options) {
      if (options[option] === null) {
        value = null;
      } else if (typeof options[option] === "string") {
        value = "\"" + options[option] + "\""; 
      } else if (typeof options[option] === "object") {
        if (options[option] instanceof Array) {
          if (options[option].length === 0) {
            value = "[]";
          } else {
            var array_objs = [];
            for (var i = 0; i < options[option].length; i++) {
              array_objs.push(Overlay.optionsToString(options[option][i]));
            }
            value = "[" + array_objs.join(",") + "]";
          }
        } else {
          value = Overlay.optionsToString(options[option]);
        }
      } else {
        value = null;        
      }
      
      if (value !== null) {
        jsonObjects.push("\"" + option + "\": " + value);
      }
    }

    return "{" + jsonObjects.join(",") + "}";
  }
};

//====================================================================
// Overlay.Embed Class
//====================================================================
Overlay.Embed = function (id, client, playerOptions, embedOptions) {
  // DOM id of the div for the embed (e.g. "embed1")
  this.id = id;
  
  // The client identifier string (e.g. ZAPPOS)
  this.client = client;
  
  // Merge custom player options with defaults
  this.playerOptions = Overlay.mergeOptions(playerOptions, Overlay.Embed.defaultPlayerOptions);
  
  // Merge custom embed options with defaults
  this.embedOptions = Overlay.mergeOptions(embedOptions, Overlay.Embed.defaultEmbedOptions);

  // Assign base flash vars
  this.playerOptions.flashVars.CLIENT   = this.client;
  this.playerOptions.flashVars.PARTNER  = this.client;
  this.playerOptions.flashVars.PAGE_URL = this.pageUrl();
};

/**
 * Class variable
 *
 * TODO: Document me
 */
Overlay.Embed.defaultPlayerOptions = {
  width: 680,
  height: 582,
  playerURL: "http://content.retail.overlay.tv/flash/players/defaults/retail_player.swf",
  expressInstallURL: "http://static.retail.overlay.tv/expressinstall.swf",
  flashVersion: "9",
  
  flashParams: {
    allowfullscreen: true,
    allowScriptAccess: "always"
  },
  
  flashVars: {
    // The client identifier string (e.g. ZAPPOS). Gets set when an
    // embed object is initialized. Do not assign manually.
    CLIENT: null,
    
    // The client identifier string (needed for legacy players).
    // Gets set when an embed object is initialized. Do not assign
    // manually.
    PARTNER: null,
    
    // The guid of the primary overlay. Logic determines which
    // overlay is primary. For a QueryEmbed, can be assigned
    // manually if you want to force a primary overlay, regardless
    // of query results.
    OVERLAY_GUID: null,
         
    // The current page url used to create the share link. Gets set
    // when an embed object is initialized. Do not assign manually.
    PAGE_URL: null,
    
    PLAYLISTS: { premium: [], ugc: [] },
    
    THUMBNAIL_BASE_URL: "http://content.retail.overlay.tv/overlays/images"
  }
};

/**
 * Class variable
 *
 * TODO: Document me
 */
Overlay.Embed.defaultEmbedOptions = {
  // The post-embed callback. Allow clients to pass in a function
  // that is called once the embed occurs such that they can perform
  // inspection on the embed object and take some kind of action.
  embedCallback: null
};

/**
 * Returns the url of the current page for sharing. We append an
 * embedId to the url such that, when there are multiple embeds on
 * a page, we know to which embed the share link is targetted.
 *
 * Example page url:
 *
 *   http://example.com/?embedId=e1
 */
Overlay.Embed.prototype.pageUrl = function () {
  return escape(window.location.toString() + "?embedId=" + this.id);
};

/**
 * TODO: Document me
 */
Overlay.Embed.prototype.primaryOverlayGuid = function () {
  if (this.sharedOverlayGuid()) {
    return this.sharedOverlayGuid();
  } else if (this.playerOptions.flashVars.OVERLAY_GUID) {
    return this.playerOptions.flashVars.OVERLAY_GUID;
  } else {
    return null;
  }
};

/**
 * Extracts and returns the shared overlay guid parameter (if present)
 * from the current window location. Returns null otherwise.
 *
 * An overlay guid is passed in a share url to allow us to override
 * the primary overlay. This functionality is required for players
 * that display multiple overlays (e.g. a carousel palyer); it ensures
 * that the desired (shared) overlay is played first.
 *
 * Example: For the following window location:
 *
 *   http://example.com/?embedId=e1&overlay_guid=8a6e3fab-3d02-41e0-a476-b788ec51d412
 *
 * This function returns:
 *
 *   "8a6e3fab-3d02-41e0-a476-b788ec51d412"
 */
Overlay.Embed.prototype.sharedOverlayGuid = function () {
  // Extract the overlay guid from the window location
  var guidPattern = /[\?&]overlay_guid=([^&#]*)/i;
  var guidMatch   = window.location.toString().match(guidPattern);
  
  // Extract the embed id from the window location
  var embedIdPattern = /[\?&]embedId=([^&#]*)/i;
  var embedIdMatch   = window.location.toString().match(embedIdPattern);

  // An embedId must be present in the URL and match the id of this
  // embed. If it matches, then the overlay guid is for this embed.
  // If it doesn't match, then the guid must be for a different embed
  // (there may be multiple embeds on the page).
  if (guidMatch && embedIdMatch && embedIdMatch[1] === this.id) {
    return guidMatch[1];
  } else {
    return null;
  }
};

Overlay.Embed.prototype.write = function () {
	if( navigator.userAgent.match(/webOS/i) ||
		navigator.userAgent.match(/iPhone/i) ||
		navigator.userAgent.match(/iPod/i) ||
		navigator.userAgent.match(/iPad/i) ||
		navigator.userAgent.match(/Android/i)
		) {
		this.writeHTML5Embed();
	} else {
		this.writeSWFEmbed();
	}



}

Overlay.Embed.prototype.writeHTML5Embed = function () {
	Overlay.Embed.prototype.videoWidth = this.playerOptions.width;
	Overlay.Embed.prototype.videoHeight = this.playerOptions.height;
	Overlay.Embed.prototype.videoDivId = this.id;
	Overlay.loadScript("http://content.retail.overlay.tv/overlays/jsonp/" + this.primaryOverlayGuid());
}

var jsonpCallback = function (data) {
	var video = document.createElement('video');
	video.controls = true;
	video.poster = "http://content.retail.overlay.tv/overlays/images/large/" + data.guid + ".png";


	if(navigator.userAgent.match(/Android/i) ){
		for (var url in data.video) {
			var videoElement = data.video[url];
			if(videoElement.profile_title.match('H.264 / ANDROID')){
				video.src = url;
				video.addEventListener('click',function(){
				   video.play();
				},false);
				break;
			}
		}

	}else{
		for (var url in data.video) {
			var videoElement = data.video[url];
			if(videoElement.profile_title.match('H.264 / AAC')){
				video.src = url;
				break;
			}
		}

	}
	
	video.width = Overlay.Embed.prototype.videoWidth;
	video.height = Overlay.Embed.prototype.videoHeight;

	var parent = document.getElementById(Overlay.Embed.prototype.videoDivId);
	if (parent) {
		parent.appendChild(video);
	} else {
		document.body.appendChild(video);
	}
}

/**
 * Instance method
 *
 * TODO: Document me
 */
Overlay.Embed.prototype.writeSWFEmbed = function () {
  var self = this;
  var embedCallback = this.embedOptions.embedCallback;

  var doEmbedSWF = function () {
    // Do the embedding
    swfobject.embedSWF(
      self.playerOptions.playerURL,
      self.id,
      self.playerOptions.width,
      self.playerOptions.height,
      self.playerOptions.flashVersion,
      self.playerOptions.expressInstallURL,
      self.playerOptions.flashVars,
      self.playerOptions.flashParams
    );
    
    if (typeof embedCallback === "function") {
      embedCallback(self);
    }
  };
  
  // Finalize the OVERLAY_GUID flashVar 
  this.playerOptions.flashVars.OVERLAY_GUID = this.primaryOverlayGuid();
  
  // Finalize the PLAYLISTS flashVar
  this.playerOptions.flashVars.PLAYLISTS = escape(Overlay.optionsToString(this.playerOptions.flashVars.PLAYLISTS));

  if (typeof swfobject === "undefined") {
    Overlay.SWFObject.load(doEmbedSWF);
  } else {
    doEmbedSWF();
  }
};

//====================================================================
// Overlay.QueryEmbed Class
//====================================================================
Overlay.QueryEmbed = function (id, client, queryOptions, playerOptions, embedOptions) {
  // Call Overlay.Embed constructor in context of this QueryEmbed
  Overlay.Embed.call(this, id, client, playerOptions, embedOptions);
  
  // Merge custom query options with defaults
  this.queryOptions = Overlay.mergeOptions(queryOptions, Overlay.QueryEmbed.defaultQueryOptions);
  
  // Overlay results for a query embed are stored here
  this.overlays = [];
  
  // Set to true after retrying a query to prevent query loops
  this.retried = false;
};

Overlay.QueryEmbed.prototype = new Overlay.Embed();

/**
 * Class variable
 * 
 * TODO: Document me
 */
Overlay.QueryEmbed.defaultQueryOptions = {
  // TODO: Document me
  criteria: null,
  
  // TODO: Document me
  embedIfNoResults: true,
  
  // TODO: Document me
  default_channel: null,
  
  // The overlay guid passed to the player (via flashVars) when all
  // other methods to choose a primary overlay guid fail.
  default_overlay: null,
  
  // Specifies how overlay results for a query embed are sorted.
  sortMethod: "random",
  
  // TODO: Document me
  text: null
};

/**
 * Class variable
 *  
 * QueryEmbeds are stored such that they can be retreived when the
 * loadJSON callback is invoked following a solr query response.
 * QueryEmbeds are indexed by their embed ID.
 */
Overlay.QueryEmbed.embeds = {};

/**
 * Class variable
 *
 * We must track if Solr queries have responsed or not, such that we
 * may display the default overlay there is no valid response from
 * Solr. This object is a key/value pair; the key is the embed ID, and
 * the value is true/false.
 */
Overlay.QueryEmbed.acknowledgements = {};

/**
 * Class variable
 *
 * Time (in milliseconds) to wait after sending a query before
 * checking for an acknowledgement. Queries are considered failed if
 * there is no acknowledgement after the timeout is reached.
 */
Overlay.QueryEmbed.timeout = 5000;

/**
 * Class variable
 * 
 * The search URL for our Solr installation.
 */
Overlay.QueryEmbed.solrBaseUrl = "http://search.retail.overlay.tv/solr/search";

/**
 * Class method
 *
 * TODO: Document me
 */
Overlay.QueryEmbed.acknowledgeResponse = function (id) {
  this.acknowledgements[id] = true;
};

/**
 * Class method
 *
 * TODO: Document me
 */
Overlay.QueryEmbed.checkForAcknowledgement = function (id) {
  var queryEmbed = Overlay.QueryEmbed.embeds[id];
  
  if (this.acknowledgements[id] === false) {
    queryEmbed.write();
  }  
};

/**
 * Class method
 *
 * TODO: Document me
 */
Overlay.QueryEmbed.waitForAcknowledgement = function (id) {
  var expression = "Overlay.QueryEmbed.checkForAcknowledgement('" + id + "')";
  this.acknowledgements[id] = false;
  setTimeout(expression, this.timeout);
};

/**
 * Instance method
 *
 * TODO: Document me
 */
Overlay.QueryEmbed.prototype.handleQueryResponse = function (data) {
  // Acknowledge that the query returned successfully, otherwise
  // the default overlay will be shown on timeout.
  Overlay.QueryEmbed.acknowledgeResponse(this.id);
  
  this.parseQueryData(data);
  
  // Handle special case where no overlays were found but a default
  // channel is configured. This essentially does the query embed
  // again with new query criteria specifying the default channel.
  if (this.overlays.length === 0 && this.queryOptions.default_channel && this.retried === false) {
    this.retried = true;
  
    // Reset the query options; scope results to the default channel
    this.queryOptions.text = null;
    this.queryOptions.criteria = {
     channels: this.queryOptions.default_channel
    };
  
    // Have to re-store the updated query embed
    Overlay.QueryEmbed.embeds[this.id] = this;
    
    // Re-do the query and return.
    this.queryEmbed();
    return;
  }
  
  this.write();
};

/**
 * Instance method
 *
 * The primary overlay guid is chosen as follows:
 *
 *   1. If get a guid in a "share" url, use it
 *   2. If OVERLAY_GUID is defined in flashVars, use it
 *   3. If have multiple premium overlays, use the guid of the first
 *      premium overlay
 *   4. Otherwise use the guid of the default overlay guid
 */
Overlay.QueryEmbed.prototype.primaryOverlayGuid = function () {
  if (this.sharedOverlayGuid()) {
    return this.sharedOverlayGuid();
  } else if (this.playerOptions.flashVars.OVERLAY_GUID) {
    return this.playerOptions.flashVars.OVERLAY_GUID;
  } else if (this.premiumOverlays().length > 0) {
    return this.premiumOverlays()[0].guid;
  } else {
    return this.queryOptions.default_overlay;
  }
};

/**
 * TODO: Document me
 */
Overlay.QueryEmbed.prototype.sortedOverlays = function () {
  var overlays   = this.overlays;
  var sortMethod = this.queryOptions.sortMethod;
  
  switch (sortMethod) {
  case "random":
    return overlays;
  case "ranked":
    var rankByScore = function (a, b) { return a.score - b.score; };
    return overlays.sort(rankByScore);
  default:
    if (typeof sortMethod === "function") {
      return sortMethod(overlays);
    } else {
      return overlays;  // fallback
    }
  }
};
  
/**
 * TODO: Document me
 */
Overlay.QueryEmbed.prototype.premiumOverlays = function () {
  var overlays = this.sortedOverlays();
  var results  = [];
  
  for (var i in overlays) {
    if (overlays[i].type === "premium") {
      results.push(overlays[i]);
    }
  }
  
  return results;
};

/**
 * TODO: Document me
 */
Overlay.QueryEmbed.prototype.ugcOverlays = function () {
  var overlays = this.sortedOverlays();
  var results  = [];
  
  for (var i in overlays) {
    if (overlays[i].type === "ugc") {
      results.push(overlays[i]);
    }
  }
  
  return results;
};

/**
 * TODO: Document me
 */
Overlay.QueryEmbed.prototype.parseQueryData = function (data) {
  // Instantiate overlays from the solr search results
  for (var i = 0; i < data.response.docs.length; i++) {
    var overlay = new Overlay.Overlay();
    overlay.populateFromSolrData(data.response.docs[i]);
    this.overlays.push(overlay);
  }
};

/**
 * TODO: Document me
 */
Overlay.QueryEmbed.prototype.queryEmbed = function () {
  Overlay.loadScript(this.queryUrl());
  Overlay.QueryEmbed.waitForAcknowledgement(this.id);
};

/**
 * Builds a URL path from the QueryEmbed's query options.
 *
 * Example URL:
 *
 *   "/ZAPPOS/type_q/Channel/ta_q/tag1/tag2"
 */
Overlay.QueryEmbed.prototype.queryUrl = function () {
  var criterias = [];
  var url       = Overlay.QueryEmbed.solrBaseUrl;
  
  // Add client to the url
  url += "/" + this.client;
  
  // Add the specified model type (e.g. Channel) to the query URL if
  // the type option is specified.
  if (this.queryOptions.type) {
    url += "/type_q/" + escape(this.queryOptions.type);
  }

  // TODO: Document me  
  for (var criteria in this.queryOptions.criteria) {
    criterias.push(criteria);
  }
  
  // TODO: Document me
  for (var i = 0; i < criterias.sort().length; i++) {
    url += this.urlChunkFor(criterias[i].substring(0,2) + "_q", this.queryOptions.criteria[criterias[i]]);
  }
  
  // TODO: Document me
  if (this.queryOptions.text) {
    url += "/te_q/" + escape(this.queryOptions.text);
  }
  
  // TODO: Document me
  url += "?embedId=" + this.id;

  return url;
};

/**
 * TODO: Document me
 */
Overlay.QueryEmbed.prototype.urlChunkFor = function (type, params) {
  return "/" + type + "/" + params.sort().join("/");
};

/**
 * TODO: Document me
 */
Overlay.QueryEmbed.prototype.write = function () {
  var embedCallback = this.embedOptions.embedCallback;
  
  // Don't do the embedding if there are no results and the embed is
  // configured to NOT show the embed if there are no results.
  if (this.overlays.length === 0 && this.queryOptions.embedIfNoResults === false) {
    if (typeof embedCallback === "function") {
      embedCallback(this);
    }
    return;
  }
  
  // Pass additional OPTIONS to the player via flashVars. Options used
  // include: default_overlay and critera.
  this.playerOptions.flashVars.OPTIONS = escape(Overlay.optionsToString(this.queryOptions));
  
  // Pass the playlists to the player via flashVars
  this.playerOptions.flashVars.PLAYLISTS.premium = this.premiumOverlays();
  this.playerOptions.flashVars.PLAYLISTS.ugc     = this.ugcOverlays();
  
  // Append the chosen primary overlay to the premium playlist
  var primaryGuid = this.primaryOverlayGuid();

  if(primaryGuid != this.premiumOverlays()[0].guid) {
    this.playerOptions.flashVars.PLAYLISTS.premium.splice(0, 0, { guid: this.primaryOverlayGuid() });
  }
  
  // Call Overlay.Embed.write() in context of this QueryEmbed
  new Overlay.Embed().write.call(this);
};

//====================================================================
// Overlay.Overlay Class
//====================================================================
/**
 * TODO: Document me
 */
Overlay.Overlay = function () {
  this.created_at   = null;
  this.description  = null;
  this.guid         = null;
  this.score        = null;       // the solr query score
  this.submitted_by = null;
  this.title        = null;
  this.type         = "premium";  // e.g. "premium", "ugc"
};

Overlay.Overlay.prototype = {
  /**
   * Data from solr looks like:
   *  
   *   { guid_guid: "d74e5a50-b7c5-401a-a726-0b5be87b5b52",
   *     overlay_type_s: "premium",
   *     score: 0.7001375,
   *     created_at_d: ["2010-02-03T20:50:20Z"],
   *     description_t: ["Description for Overlay 3."],
   *     title_t: ["Overlay 3"] }
   *
   * Flash players need a normalized form of this data.
   */
  populateFromSolrData: function(data) {
    this.created_at   = data.created_at_d[0];
    this.guid         = data.guid_guid;
    this.score        = data.score;
    this.submitted_by = data.submitted_by_s;
    this.title        = data.title_t[0];
    this.type         = data.overlay_type_s;

	// Overlays may not have tags
    if (data.solr_tag_s_mv !== undefined) {
      this.tags = data.solr_tag_s_mv.join(",");
    }

    // Descriptions are optional and thus may not be present
    if (data.description_t !== undefined) {
      this.description = data.description_t[0];
    }
  }
};

//====================================================================
// Overlay.SWFObject Class
//====================================================================
Overlay.SWFObject = {
  scriptUrl: "http://content.retail.overlay.tv/swfobject.js",
  
  load: function (readyCallback) {
    var script = Overlay.loadScript(this.scriptUrl);

    script.onload = function () {
      script.onloadDone = true;
      swfobject.callDomLoadFunctions();
      
      if (typeof readyCallback === "function") {
        readyCallback();
      }
    };

    script.onreadystatechange = function () {
      if ((script.readyState === "loaded" || script.readyState == "complete") && !script.onloadDone) {
        script.onloadDone = true;
        swfobject.callDomLoadFunctions();
        
        if (typeof readyCallback === "function") {
          readyCallback();
        }
      }
    };

    document.getElementsByTagName("head")[0].appendChild(script);
  }
};

