// Copyright 2009 Google Inc.  All Rights Reserved.

/**
 * @fileoverview JavaScript for China "Things To Do" landing page.
 * @author zackyma@google.com (Zacky Ma)
 */

/**
 * Namespace gweb (Global namespace for Google static webpages).
 */
var gweb = gweb || {};

/**
 * Namespace gweb.thingstodo (Namespace for Things To Do landing page).
 */
gweb.thingstodo = gweb.thingstodo || {};

/**
 * Class creating "Things To Do" landing page.
 * @param {Object.<string>} config Configuration of the application.
 * @constructor
 */
gweb.thingstodo.App = function(config) {
  /**
   * Clone of external config object.
   * @type {Object.<string>}
   * @private
   */
  this.config_ = config;

  /**
   * The whole tip lists.
   * @type {Object.<Array>}
   * @private
   */
  this.tips_ = {};

  /**
   * Code (ID) of selected tip in each category.
   * @type {Object.<string>}
   * @private
   */
  this.selectedTips_ = {};

  /**
   * ID of selected category.
   * @type {string}
   * @private
   */
  this.selectedCate_ = '';

  /**
   * Code (ID) of default opening tip.
   * @type {string}
   * @private
   */
  this.defaultTip_ = '';

  /**
   * Check for tips loaded from a local JSONP file.
   * @type {boolean}
   * @private
   */
  this.tipsLoaded_ = false;
};

/**
 * Colors for tip bars.
 * @type {Array.<string>}
 * @private
 * @const
 */
gweb.thingstodo.App.COLORS_ = ['5fb7df', '017dc3', 'ed6d6e', 'e50039', '726baf',
                              'ec611e', 'f39907', '029d95', '51b948', 'e5007f'];

/**
 * Identifier of the 'special' action.
 * @type {string}
 * @private
 * @const
 */
gweb.thingstodo.App.SPECIAL_ACTION_ID_ = '__special__';

/**
 * Identifier of the screenshot.
 * @type {string}
 * @private
 * @const
 */
gweb.thingstodo.App.SCREENSHOT_MEDIA_ID_ = '__screenshot__';

/**
 * Initiate the tips lists.
 * @param {Object} json JSON feed generated by Google SpreadSheet.
 */
gweb.thingstodo.App.prototype.init = function(json) {
  this.tipsLoaded_ = true;

  this.parseTips_(json);
  this.renderTips_();

  // Get a category tab and a tip open at beginning.
  if (location.hash) {
    var params = location.hash.substring(1).split('&');
    var code = '';
    var cate = '';

    // Get category and tip IDs, which will be open.
    var catMatch = new RegExp(gweb.string.buildString(
        this.config_.idPrefix.cate, '([^\+]+)')).exec(location.hash);
    var tipMatch = new RegExp(gweb.string.buildString(this.config_.idPrefix.tip,
        '([^\+]+)')).exec(location.hash);
    cate = catMatch && catMatch[1] || this.config_.allCatId;
    code = tipMatch && tipMatch[1] || '';

    this.openCate(cate, true);
    this.toggleTip(cate, code);
  } else {
    this.openCate(this.config_.allCatId, true);

    if (this.defaultTip_) {
      this.toggleTip(this.config_.allCatId, this.defaultTip_);
    } else {
      var rand = this.getRandomTip_();
      this.toggleTip(this.config_.allCatId,
          this.tips_[this.config_.allCatId][rand].code);
    }
  }
};

/**
 * Parse the JSON object generated by Google Spreadsheet and
 * build the master tips object.
 * @param {Object} json JSON object to be parsed.
 * @private
 */
gweb.thingstodo.App.prototype.parseTips_ = function(json) {
  if (json.feed.entry.length < 0) {
    return;
  }

  this.tips_[this.config_.allCatId] = [];
  this.selectedTips_[this.config_.allCatId] = '';

  var tips = this.tips_;

  // RegExp to remove new line.
  var removeNewLineRegex = /\n/g;

  for (var i = 0, feedlen = json.feed.entry.length; i < feedlen; i++) {
    var entry = json.feed.entry[i];
    var hash = {};
    var tipsLen = tips[this.config_.allCatId].length;
    var callToActionObj = {
      action: entry.gsx$action.$t.replace(removeNewLineRegex, ''),
      url: entry.gsx$url.$t.replace(removeNewLineRegex, ''),
      query: entry.gsx$query.$t.replace(removeNewLineRegex, '')
    };

    if (callToActionObj.action != gweb.thingstodo.App.SPECIAL_ACTION_ID_ &&
        callToActionObj.query != '') {
      // When {@code callToActionObj.query} is not empty, it means the action
      // item for this tip is a search form with sample query. The the
      // {@code callToActionObj.url} will be the form's action attribute.
      // {@code callToActionObj.url} could contains parameters, like
      // www.google.com/search?hl=zh-CN. To make sure the additional parameters
      // will be passed when the form is submitted, we need to generate hidden
      // inputs to store all these additional parameter.
      callToActionObj.hiddens = callToActionObj.url.split('?')[1];
      callToActionObj.url = callToActionObj.url.split('?')[0];
    }

    hash.code = entry.gsx$code.$t.replace(removeNewLineRegex, '');

    if (tipsLen == 0 || hash.code !=
        tips[this.config_.allCatId][tipsLen - 1].code) {
      // If this is a new tip, push a new item into the array.
      hash.title = entry.gsx$title.$t.replace(removeNewLineRegex, '');
      hash.desc = entry.gsx$description.$t.replace(removeNewLineRegex, '');
      hash.media = entry.gsx$media.$t.replace(removeNewLineRegex, '');
      hash.name = entry.gsx$name.$t.replace(removeNewLineRegex, '');
      hash.status = entry.gsx$status.$t.replace(removeNewLineRegex, '');
      hash.callToActions = [callToActionObj];
      hash.hasMedia = Boolean(hash.media);

      tips[this.config_.allCatId].push(hash);

      if (entry.gsx$default.$t.replace(removeNewLineRegex, '')) {
        this.defaultTip_ = hash.code;
      }

      var tags = entry.gsx$tags.$t.replace(removeNewLineRegex, '').
          replace(/\s/g, '').split(',');
      if (tags != '') {
        for (var j = 0, tagLen = tags.length; j < tagLen; j++) {
          var tag = tags[j];
          if (!tips[tag]) {
            tips[tag] = [];
            this.selectedTips_[tag] = '';
          }
          tips[tag].push(tips[this.config_.allCatId][tipsLen]);
        }
      }
    } else {
      // If this is a existed tip, push the actionObj to the last item.
      tips[this.config_.allCatId][tipsLen - 1].
          callToActions.push(callToActionObj);
    }
  }
};

/**
 * Generate HTML code from the master tips object and
 * insert into DOM.
 * @private
 */
gweb.thingstodo.App.prototype.renderTips_ = function() {
  var tips = this.tips_;
  var html = [];

  for (var cate in tips) {
    var subtips = tips[cate];
    html.push(gweb.string.buildString('<ol id="',
        this.config_.idPrefix.cate,
        cate,
        '" class="tips">'));

    for (var i = 0, subtipslen = subtips.length; i < subtipslen; i++) {
      var subtip = subtips[i];

      // Create an object for filling tip content into templates.
      var tipObj = {
        cate: cate,
        code: subtip.code,
        color: gweb.thingstodo.App.COLORS_[i %
            gweb.thingstodo.App.COLORS_.length],
        count: i + 1,
        title: subtip.title,
        hasMedia: subtip.hasMedia ? ' hasMedia' : '',
        name: subtip.name,
        desc: subtip.desc
      };

      // Generate value for {@code tipObj.status}.
      tipObj.status = subtip.status == 'new' ?
          gweb.thingstodo.util.templatize(this.config_.tpls.statusNew) : '';

      // Generate value for {@code tipObj.mediaHtml}.
      if (subtip.hasMedia) {
        if (subtip.media == gweb.thingstodo.App.SCREENSHOT_MEDIA_ID_) {
          tipObj.mediaHtml = gweb.thingstodo.util.templatize(
              this.config_.tpls.mediaHTMLScreenshot, {
                title: subtip.title,
                code: subtip.code
          });
        } else {
          tipObj.mediaHtml = gweb.thingstodo.util.templatize(
              this.config_.tpls.mediaHTMLVideo, {
                cate: cate,
                code: subtip.code,
                media: subtip.media
          });
        }
      } else {
        tipObj.mediaHtml = '';
      }

      // Generate value for {@code tipObj.callToActions}.
      var callToAction;
      if (subtip.callToActions.length > 1) {
        var callToActionList = '';

        for (var j = 0, len = subtip.callToActions.length; j < len; j++) {
          callToAction = subtip.callToActions[j];

          if (callToAction.action != gweb.thingstodo.App.SPECIAL_ACTION_ID_) {
            if (callToAction.query == '') {
              callToActionList += gweb.thingstodo.util.templatize(
                  this.config_.tpls.actionProdListLink, {
                    cate: cate,
                    code: subtip.code,
                    url: callToAction.url,
                    action: callToAction.action
              });
            } else {
              callToActionList += gweb.thingstodo.util.templatize(
                  this.config_.tpls.actionProdListSearch, {
                    cate: cate,
                    code: subtip.code,
                    hiddens: this.genHiddens_(callToAction.hiddens),
                    url: callToAction.url,
                    query: callToAction.query,
                    action: callToAction.action
              });
            }
          } else {
            callToActionList += gweb.thingstodo.util.templatize(
                this.config_.tpls.actionProdListSpecial, {
                  special_action: callToAction.url
            });
          }
        }

        tipObj.callToActions = gweb.thingstodo.util.templatize(
            this.config_.tpls.actionProdList, {
              list: callToActionList
        });
      } else {
        callToAction = subtip.callToActions[0];

        if (callToAction.action != gweb.thingstodo.App.SPECIAL_ACTION_ID_) {
          if (callToAction.query == '') {
            tipObj.callToActions = gweb.thingstodo.util.templatize(
                this.config_.tpls.actionGButton, {
                  cate: cate,
                  code: subtip.code,
                  url: callToAction.url,
                  action: callToAction.action
            });
          } else {
            tipObj.callToActions = gweb.thingstodo.util.templatize(
                this.config_.tpls.actionSearch, {
                  cate: cate,
                  code: subtip.code,
                  url: callToAction.url,
                  hiddens: this.genHiddens_(callToAction.hiddens),
                  query: callToAction.query,
                  action: callToAction.action
            });
          }
        } else {
          tipObj.callToActions = callToAction.url;
        }
      }

      // Generate value for {@code tipObj.shareButton}.
      if (window.clipboardData) {
        tipObj.shareButton = gweb.thingstodo.util.templatize(
            this.config_.tpls.shareButton, {
              cate: cate,
              code: subtip.code
        });
      } else {
        tipObj.shareButton = '';
      }

      html.push(gweb.thingstodo.util.templatize(this.config_.tpls.tip, tipObj));
    }

    html.push('</ol>');
  }

  document.getElementById(this.config_.tipListsElemID).
      innerHTML = html.join('');
};

/**
 * Generate hidden fields for searching forms to pass parameters to search
 * properties.
 * @param {string} inputs Attached search parameters.
 * @return {string} Generated HTML code.
 * @private
 */
gweb.thingstodo.App.prototype.genHiddens_ = function(inputs) {
  var html = '';
  inputs = inputs.split('&');

  for (var k = 0, inputsLen = inputs.length; k < inputsLen; k++) {
    var input = inputs[k];

    if (input.split('=')[0] == 'q') {
      continue;
    }
    html += gweb.thingstodo.util.templatize(
        this.config_.tpls.actionSearchHidden, {
          key: input.split('=')[0],
          value: input.split('=')[1]
    });
  }

  return html;
};

/**
 * Generate index number of the random opening tip.
 * @private
 * @return {number} The index number.
 */
gweb.thingstodo.App.prototype.getRandomTip_ = function() {
  return Math.floor(Math.random() * this.config_.defaultRandomTipRange);
};

/**
 * Show or hide a tip.
 * @param {string} cate The category which opening tip is belong to.
 * @param {string} code The opening tip's code.
 * @return {boolean} Whether the location.hash would be updated.
 */
gweb.thingstodo.App.prototype.toggleTip = function(cate, code) {
  var tipIDPrefix = gweb.string.buildString(
      this.config_.idPrefix.cate, cate,
      this.config_.idConnector,
      this.config_.idPrefix.tip);
  var targetTipID = gweb.string.buildString(tipIDPrefix, code);
  var targetTipElem = document.getElementById(targetTipID);
  if (!targetTipElem) {
    // Client requested not to auto open the 1st tip ever,
    // so add 1 to {@code rand}.
    var rand = this.getRandomTip_() + 1;
    this.toggleTip(cate, this.tips_[cate][rand].code);
    return true;
  }

  if (code != this.selectedTips_[cate]) {
    var selectedTipID = gweb.string.buildString(tipIDPrefix,
        this.selectedTips_[cate]);
    if (this.selectedTips_[cate] != '') {
      closeTip(selectedTipID, gweb.string.buildString(
          this.config_.idPrefix.media, selectedTipID));
    }
    targetTipElem.className = 'on';
    this.selectedTips_[cate] = code;
    return true;
  } else {
    targetTipElem.className = '';
    closeTip(targetTipID, gweb.string.buildString(this.config_.idPrefix.media,
        targetTipID));
    this.selectedTips_[cate] = '';
    return false;
  }

  function closeTip(tipElemID, mediaElemID) {
    document.getElementById(tipElemID).className = '';
    gweb.thingstodo.util.resolveFlashBug4IE(mediaElemID);
  }
};

/**
 * Open a category tab.
 * @param {string} cate ID of category which will be opened.
 * @param {boolean} opt_notOpenTip Whether not to auto open a tip
 *     under the category.
 */
gweb.thingstodo.App.prototype.openCate = function(cate, opt_notOpenTip) {
  if (this.selectedCate_ == '' || cate != this.selectedCate_) {
    gweb.thingstodo.util.resolveFlashBug4IE(gweb.string.buildString(
        this.config_.idPrefix.media,
        this.config_.idPrefix.cate, this.selectedCate_,
        this.config_.idPrefix.tip, this.selectedTips_[this.selectedCate_]));

    if (this.selectedCate_ == '') {
      document.getElementById(this.config_.idPrefix.tab +
          this.config_.allCatId).className = '';
    } else {
      document.getElementById(this.config_.idPrefix.tab +
          this.selectedCate_).className = '';
      document.getElementById(this.config_.idPrefix.cate +
          this.selectedCate_).style.display = '';
    }
    document.getElementById(this.config_.idPrefix.tab + cate).className = 'on';
    document.getElementById(this.config_.idPrefix.cate +
        cate).style.display = 'block';

    this.selectedCate_ = cate;

    if (!opt_notOpenTip && this.selectedTips_[cate] == '') {
      this.toggleTip(cate, this.tips_[cate][1].code)
    }
  }
};

/**
 * Namespace gweb.thingstodo.util (Namespace for common methods).
 */
gweb.thingstodo.util = gweb.thingstodo.util || {};

/**
 * Generate HTML code with data and templates.
 * @param {string} tpl Template's ID.
 * @param {!Object} data Data that to be inserted into tpl.
 * @return {string} Generated HTML code.
 */
gweb.thingstodo.util.templatize = function(tpl, data) {
  var tplHTML = document.getElementById(tpl).innerHTML;
  tplHTML = tplHTML.replace(/\n/g, '').replace(/\s{2,}/g, '');

  if (data) {
    for (var mem in data) {
      var re = new RegExp('{{' + mem + '}}', 'g');
      tplHTML = tplHTML.replace(re, data[mem]);
    }
  }

  return tplHTML;
};

/**
 * Copy tip URL to Windows Clipboard.
 * @param {string} url The URL which is copied.
 */
gweb.thingstodo.util.winCopy = function(url) {
  if (window.clipboardData) {
    window.clipboardData.setData('Text', url);
  }
};

/**
 * Fixing Flash bug under IE6, which is that IE6 keeps playing flash object
 * even when its container turns to invisible.
 * @param {string} mediaContainerID ID of media Container.
 */
gweb.thingstodo.util.resolveFlashBug4IE = function(mediaContainerID) {
  var isIE = Boolean(gweb.userAgent && gweb.userAgent.IE || document.all);
  var mediaContainer = document.getElementById(mediaContainerID);

  if (isIE && mediaContainer) {
    // Empty the flash's container first, so that the flash will stop,
    // then insert the flash code to its container again to play it for
    // the next time.
    var temp = mediaContainer.innerHTML;
    mediaContainer.innerHTML = '';
    mediaContainer.innerHTML = temp;
  }
};

